Skip to content

Commit b62d8be

Browse files
committed
Adds Repository Factory Provider
Signed-off-by: Bandini Bhopi <bandinib@amazon.com>
1 parent bd7d707 commit b62d8be

7 files changed

+128
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
8787
- [Table Visualization] Move format table, consolidate types and add unit tests ([#3397](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3397))
8888
- [Multiple Datasource] Support Amazon OpenSearch Serverless ([#3957](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/3957))
8989
- Add support for Node.js >=14.20.1 <19 ([#4071](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4071))
90+
- [Saved Object Service] Add Repository Factory Provider ([#4149](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4149))
9091

9192
### 🐛 Bug Fixes
9293

src/core/server/legacy/legacy_service.ts

+2
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,8 @@ export class LegacyService implements CoreService {
275275
addClientWrapper: setupDeps.core.savedObjects.addClientWrapper,
276276
registerType: setupDeps.core.savedObjects.registerType,
277277
getImportExportObjectLimit: setupDeps.core.savedObjects.getImportExportObjectLimit,
278+
registerRepositoryFactoryProvider:
279+
setupDeps.core.savedObjects.registerRepositoryFactoryProvider,
278280
},
279281
status: {
280282
isStatusPageAnonymous: setupDeps.core.status.isStatusPageAnonymous,

src/core/server/plugins/plugin_context.ts

+1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
204204
addClientWrapper: deps.savedObjects.addClientWrapper,
205205
registerType: deps.savedObjects.registerType,
206206
getImportExportObjectLimit: deps.savedObjects.getImportExportObjectLimit,
207+
registerRepositoryFactoryProvider: deps.savedObjects.registerRepositoryFactoryProvider,
207208
},
208209
status: {
209210
core$: deps.status.core$,

src/core/server/saved_objects/saved_objects_service.mock.ts

+1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ const createSetupContractMock = () => {
7979
addClientWrapper: jest.fn(),
8080
registerType: jest.fn(),
8181
getImportExportObjectLimit: jest.fn(),
82+
registerRepositoryFactoryProvider: jest.fn(),
8283
};
8384

8485
setupContract.getImportExportObjectLimit.mockReturnValue(100);

src/core/server/saved_objects/saved_objects_service.test.ts

+62-1
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@ import { errors as opensearchErrors } from '@opensearch-project/opensearch';
4141
import { SavedObjectsService } from './saved_objects_service';
4242
import { mockCoreContext } from '../core_context.mock';
4343
import { Env } from '../config';
44-
import { configServiceMock } from '../mocks';
44+
import { configServiceMock, savedObjectsRepositoryMock } from '../mocks';
4545
import { opensearchServiceMock } from '../opensearch/opensearch_service.mock';
4646
import { opensearchClientMock } from '../opensearch/client/mocks';
4747
import { httpServiceMock } from '../http/http_service.mock';
4848
import { httpServerMock } from '../http/http_server.mocks';
4949
import { SavedObjectsClientFactoryProvider } from './service/lib';
5050
import { NodesVersionCompatibility } from '../opensearch/version_check/ensure_opensearch_version';
5151
import { SavedObjectsRepository } from './service/lib/repository';
52+
import { SavedObjectRepositoryFactoryProvider } from './service/lib/scoped_client_provider';
5253

5354
jest.mock('./service/lib/repository');
5455

@@ -169,6 +170,27 @@ describe('SavedObjectsService', () => {
169170
expect(typeRegistryInstanceMock.registerType).toHaveBeenCalledWith(type);
170171
});
171172
});
173+
174+
describe('#registerRepositoryFactoryProvider', () => {
175+
it('throws error if a repository is already registered', async () => {
176+
const coreContext = createCoreContext();
177+
const soService = new SavedObjectsService(coreContext);
178+
const setup = await soService.setup(createSetupDeps());
179+
180+
const firstRepository: SavedObjectRepositoryFactoryProvider = () =>
181+
savedObjectsRepositoryMock.create();
182+
const secondRepository: SavedObjectRepositoryFactoryProvider = () =>
183+
savedObjectsRepositoryMock.create();
184+
185+
setup.registerRepositoryFactoryProvider(firstRepository);
186+
187+
expect(() => {
188+
setup.registerRepositoryFactoryProvider(secondRepository);
189+
}).toThrowErrorMatchingInlineSnapshot(
190+
`"custom repository factory is already set, and can only be set once"`
191+
);
192+
});
193+
});
172194
});
173195

174196
describe('#start()', () => {
@@ -281,6 +303,15 @@ describe('SavedObjectsService', () => {
281303
}).toThrowErrorMatchingInlineSnapshot(
282304
`"cannot call \`registerType\` after service startup."`
283305
);
306+
307+
const customRpository: SavedObjectRepositoryFactoryProvider = () =>
308+
savedObjectsRepositoryMock.create();
309+
310+
expect(() => {
311+
setup.registerRepositoryFactoryProvider(customRpository);
312+
}).toThrowErrorMatchingInlineSnapshot(
313+
'"cannot call `registerRepositoryFactoryProvider` after service startup."'
314+
);
284315
});
285316

286317
describe('#getTypeRegistry', () => {
@@ -368,6 +399,36 @@ describe('SavedObjectsService', () => {
368399

369400
expect(includedHiddenTypes).toEqual(['someHiddenType']);
370401
});
402+
403+
it('Should not create SavedObjectsRepository when custom repository is registered ', async () => {
404+
const coreContext = createCoreContext({ skipMigration: false });
405+
const soService = new SavedObjectsService(coreContext);
406+
const coreSetup = createSetupDeps();
407+
const setup = await soService.setup(coreSetup);
408+
409+
const customRpository: SavedObjectRepositoryFactoryProvider = () =>
410+
savedObjectsRepositoryMock.create();
411+
setup.registerRepositoryFactoryProvider(customRpository);
412+
413+
const coreStart = createStartDeps();
414+
const { createInternalRepository } = await soService.start(coreStart);
415+
createInternalRepository();
416+
417+
expect(SavedObjectsRepository.createRepository as jest.Mocked<any>).not.toHaveBeenCalled();
418+
});
419+
420+
it('Should create SavedObjectsRepository when no custom repository is registered ', async () => {
421+
const coreContext = createCoreContext({ skipMigration: false });
422+
const soService = new SavedObjectsService(coreContext);
423+
const coreSetup = createSetupDeps();
424+
await soService.setup(coreSetup);
425+
426+
const coreStart = createStartDeps();
427+
const { createInternalRepository } = await soService.start(coreStart);
428+
createInternalRepository();
429+
430+
expect(SavedObjectsRepository.createRepository as jest.Mocked<any>).toHaveBeenCalled();
431+
});
371432
});
372433
});
373434
});

src/core/server/saved_objects/saved_objects_service.ts

+35-7
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ import { ISavedObjectsRepository, SavedObjectsRepository } from './service/lib/r
5656
import {
5757
SavedObjectsClientFactoryProvider,
5858
SavedObjectsClientWrapperFactory,
59+
SavedObjectRepositoryFactoryProvider,
5960
} from './service/lib/scoped_client_provider';
6061
import { Logger } from '../logging';
6162
import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry';
@@ -166,6 +167,14 @@ export interface SavedObjectsServiceSetup {
166167
* Returns the maximum number of objects allowed for import or export operations.
167168
*/
168169
getImportExportObjectLimit: () => number;
170+
171+
/**
172+
* Set the default {@link SavedObjectRepositoryFactoryProvider | factory provider} for creating Saved Objects repository.
173+
* Only one repository can be set, subsequent calls to this method will fail.
174+
*/
175+
registerRepositoryFactoryProvider: (
176+
respositoryFactoryProvider: SavedObjectRepositoryFactoryProvider
177+
) => void;
169178
}
170179

171180
/**
@@ -291,6 +300,8 @@ export class SavedObjectsService
291300
private typeRegistry = new SavedObjectTypeRegistry();
292301
private started = false;
293302

303+
private respositoryFactoryProvider?: SavedObjectRepositoryFactoryProvider;
304+
294305
constructor(private readonly coreContext: CoreContext) {
295306
this.logger = coreContext.logger.get('savedobjects-service');
296307
}
@@ -348,6 +359,15 @@ export class SavedObjectsService
348359
this.typeRegistry.registerType(type);
349360
},
350361
getImportExportObjectLimit: () => this.config!.maxImportExportSize,
362+
registerRepositoryFactoryProvider: (repositoryProvider) => {
363+
if (this.started) {
364+
throw new Error('cannot call `registerRepositoryFactoryProvider` after service startup.');
365+
}
366+
if (this.respositoryFactoryProvider) {
367+
throw new Error('custom repository factory is already set, and can only be set once');
368+
}
369+
this.respositoryFactoryProvider = repositoryProvider;
370+
},
351371
};
352372
}
353373

@@ -422,13 +442,21 @@ export class SavedObjectsService
422442
opensearchClient: OpenSearchClient,
423443
includedHiddenTypes: string[] = []
424444
) => {
425-
return SavedObjectsRepository.createRepository(
426-
migrator,
427-
this.typeRegistry,
428-
opensearchDashboardsConfig.index,
429-
opensearchClient,
430-
includedHiddenTypes
431-
);
445+
if (this.respositoryFactoryProvider) {
446+
return this.respositoryFactoryProvider({
447+
migrator,
448+
typeRegistry: this.typeRegistry,
449+
includedHiddenTypes,
450+
});
451+
} else {
452+
return SavedObjectsRepository.createRepository(
453+
migrator,
454+
this.typeRegistry,
455+
opensearchDashboardsConfig.index,
456+
opensearchClient,
457+
includedHiddenTypes
458+
);
459+
}
432460
};
433461

434462
const repositoryFactory: SavedObjectsRepositoryFactory = {

src/core/server/saved_objects/service/lib/scoped_client_provider.ts

+26-1
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,33 @@
3131
import { PriorityCollection } from './priority_collection';
3232
import { SavedObjectsClientContract } from '../../types';
3333
import { SavedObjectsRepositoryFactory } from '../../saved_objects_service';
34-
import { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry';
34+
import {
35+
ISavedObjectTypeRegistry,
36+
SavedObjectTypeRegistry,
37+
} from '../../saved_objects_type_registry';
3538
import { OpenSearchDashboardsRequest } from '../../../http';
39+
import { ISavedObjectsRepository } from './repository';
40+
import { OpenSearchClient } from '../../../opensearch';
41+
import { IOpenSearchDashboardsMigrator } from '../../migrations';
42+
43+
/**
44+
* Options passed to each SavedObjectRepositoryFactoryProvider to aid in creating the repository instance.
45+
* @public
46+
*/
47+
export interface SavedObjectsRepositoryOptions {
48+
migrator: IOpenSearchDashboardsMigrator;
49+
typeRegistry: SavedObjectTypeRegistry;
50+
includedHiddenTypes: string[];
51+
client?: OpenSearchClient;
52+
}
53+
54+
/**
55+
* Provider to invoke to a factory function for creating ISavedObjectRepository {@link ISavedObjectRepository} instances.
56+
* @public
57+
*/
58+
export type SavedObjectRepositoryFactoryProvider = (
59+
options: SavedObjectsRepositoryOptions
60+
) => ISavedObjectsRepository;
3661

3762
/**
3863
* Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance.

0 commit comments

Comments
 (0)