diff --git a/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_route_handler_context.ts b/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_route_handler_context.ts index 702a3ea10941d..b3ab51bf64e34 100644 --- a/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_route_handler_context.ts +++ b/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_route_handler_context.ts @@ -13,6 +13,7 @@ import type { DeprecationsRequestHandlerContext, DeprecationsClient, } from '@kbn/core-deprecations-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; import type { InternalDeprecationsServiceStart } from './deprecations_service'; /** @@ -25,14 +26,16 @@ export class CoreDeprecationsRouteHandlerContext implements DeprecationsRequestH constructor( private readonly deprecationsStart: InternalDeprecationsServiceStart, private readonly elasticsearchRouterHandlerContext: CoreElasticsearchRouteHandlerContext, - private readonly savedObjectsRouterHandlerContext: CoreSavedObjectsRouteHandlerContext + private readonly savedObjectsRouterHandlerContext: CoreSavedObjectsRouteHandlerContext, + private readonly request: KibanaRequest ) {} public get client() { if (this.#client == null) { this.#client = this.deprecationsStart.asScopedToClient( this.elasticsearchRouterHandlerContext.client, - this.savedObjectsRouterHandlerContext.client + this.savedObjectsRouterHandlerContext.client, + this.request ); } return this.#client; diff --git a/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_service.test.ts b/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_service.test.ts index 0ea283b6eb5d6..a517b8300e935 100644 --- a/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_service.test.ts +++ b/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_service.test.ts @@ -12,7 +12,7 @@ import { registerConfigDeprecationsInfoMock, } from './deprecations_service.test.mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; -import { httpServiceMock } from '@kbn/core-http-server-mocks'; +import { httpServerMock, httpServiceMock } from '@kbn/core-http-server-mocks'; import { coreUsageDataServiceMock } from '@kbn/core-usage-data-server-mocks'; import { configServiceMock } from '@kbn/config-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; @@ -83,12 +83,13 @@ describe('DeprecationsService', () => { it('returns client with #getAllDeprecations method', async () => { const esClient = elasticsearchServiceMock.createScopedClusterClient(); const savedObjectsClient = savedObjectsClientMock.create(); + const request = httpServerMock.createKibanaRequest(); const deprecationsService = new DeprecationsService(coreContext); await deprecationsService.setup(deprecationsCoreSetupDeps); const start = deprecationsService.start(); - const deprecationsClient = start.asScopedToClient(esClient, savedObjectsClient); + const deprecationsClient = start.asScopedToClient(esClient, savedObjectsClient, request); expect(deprecationsClient.getAllDeprecations).toBeDefined(); }); diff --git a/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_service.ts b/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_service.ts index cd8e4243e5edf..8189172a5fe12 100644 --- a/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_service.ts +++ b/packages/core/deprecations/core-deprecations-server-internal/src/deprecations_service.ts @@ -20,6 +20,7 @@ import type { DeprecationsClient, } from '@kbn/core-deprecations-server'; import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { KibanaRequest } from '@kbn/core-http-server'; import { DeprecationsFactory } from './deprecations_factory'; import { registerRoutes } from './routes'; import { config as deprecationConfig, DeprecationConfigType } from './deprecation_config'; @@ -36,7 +37,8 @@ export interface InternalDeprecationsServiceStart { */ asScopedToClient( esClient: IScopedClusterClient, - savedObjectsClient: SavedObjectsClientContract + savedObjectsClient: SavedObjectsClientContract, + request: KibanaRequest ): DeprecationsClient; } @@ -117,13 +119,19 @@ export class DeprecationsService private createScopedDeprecations(): ( esClient: IScopedClusterClient, - savedObjectsClient: SavedObjectsClientContract + savedObjectsClient: SavedObjectsClientContract, + request: KibanaRequest ) => DeprecationsClient { - return (esClient: IScopedClusterClient, savedObjectsClient: SavedObjectsClientContract) => { + return ( + esClient: IScopedClusterClient, + savedObjectsClient: SavedObjectsClientContract, + request: KibanaRequest + ) => { return { getAllDeprecations: this.deprecationsFactory!.getAllDeprecations.bind(null, { savedObjectsClient, esClient, + request, }), }; }; diff --git a/packages/core/deprecations/core-deprecations-server-internal/src/mocks/deprecations_registry.mock.ts b/packages/core/deprecations/core-deprecations-server-internal/src/mocks/deprecations_registry.mock.ts index 9280243527207..eca729871bc1e 100644 --- a/packages/core/deprecations/core-deprecations-server-internal/src/mocks/deprecations_registry.mock.ts +++ b/packages/core/deprecations/core-deprecations-server-internal/src/mocks/deprecations_registry.mock.ts @@ -12,6 +12,7 @@ import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-m import type { GetDeprecationsContext } from '@kbn/core-deprecations-server'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import type { DeprecationsRegistry } from '../deprecations_registry'; +import { httpServerMock } from '@kbn/core-http-server-mocks'; type DeprecationsRegistryContract = PublicMethodsOf; @@ -28,6 +29,7 @@ const createGetDeprecationsContextMock = () => { const mocked: jest.Mocked = { esClient: elasticsearchClientMock.createScopedClusterClient(), savedObjectsClient: savedObjectsClientMock.create(), + request: httpServerMock.createKibanaRequest(), }; return mocked; diff --git a/packages/core/deprecations/core-deprecations-server/src/contracts.ts b/packages/core/deprecations/core-deprecations-server/src/contracts.ts index 097e9f6aa0522..4391ac2c1f68f 100644 --- a/packages/core/deprecations/core-deprecations-server/src/contracts.ts +++ b/packages/core/deprecations/core-deprecations-server/src/contracts.ts @@ -11,6 +11,7 @@ import type { MaybePromise } from '@kbn/utility-types'; import type { DeprecationsDetails } from '@kbn/core-deprecations-common'; import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; /** * The deprecations service provides a way for the Kibana platform to communicate deprecated @@ -121,6 +122,7 @@ export interface GetDeprecationsContext { esClient: IScopedClusterClient; /** Saved Objects client scoped to the current user and space */ savedObjectsClient: SavedObjectsClientContract; + request: KibanaRequest; } /** diff --git a/packages/core/deprecations/core-deprecations-server/tsconfig.json b/packages/core/deprecations/core-deprecations-server/tsconfig.json index fa09534af0b92..0179bace38bed 100644 --- a/packages/core/deprecations/core-deprecations-server/tsconfig.json +++ b/packages/core/deprecations/core-deprecations-server/tsconfig.json @@ -15,6 +15,7 @@ "@kbn/core-deprecations-common", "@kbn/core-elasticsearch-server", "@kbn/core-saved-objects-api-server", + "@kbn/core-http-server", ], "exclude": [ "target/**/*", diff --git a/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.ts b/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.ts index 376eb5a2bd24f..3f7136a1a97d5 100644 --- a/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.ts +++ b/packages/core/http/core-http-request-handler-context-server-internal/src/core_route_handler_context.ts @@ -111,7 +111,8 @@ export class CoreRouteHandlerContext implements CoreRequestHandlerContext { this.#deprecations = new CoreDeprecationsRouteHandlerContext( this.coreStart.deprecations, this.elasticsearch, - this.savedObjects + this.savedObjects, + this.request ); } return this.#deprecations; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index bb9ae3dd84698..929db1f171a83 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -15,24 +15,23 @@ import { import { SavedObjectsRepository } from './repository'; import { loggerMock } from '@kbn/logging-mocks'; -import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; import { kibanaMigratorMock } from '../mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { mockTimestamp, - mappings, createRegistry, createDocumentMigrator, - createSpySerializer, } from '../test_helpers/repository.test.common'; +import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectsSpacesExtension } from '@kbn/core-saved-objects-server'; +import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; describe('SavedObjectsRepository', () => { let client: ReturnType; - let repository: SavedObjectsRepository; + let repository: ISavedObjectsRepository; let migrator: ReturnType; let logger: ReturnType; - let serializer: jest.Mocked; const registry = createRegistry(); const documentMigrator = createDocumentMigrator(registry); @@ -46,23 +45,13 @@ describe('SavedObjectsRepository', () => { migrator.runMigrations = jest.fn().mockResolvedValue([{ status: 'skipped' }]); logger = loggerMock.create(); - // create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation - serializer = createSpySerializer(registry); - - const allTypes = registry.getAllTypes().map((type) => type.name); - const allowedTypes = [...new Set(allTypes.filter((type) => !registry.isHidden(type)))]; - - // @ts-expect-error must use the private constructor to use the mocked serializer - repository = new SavedObjectsRepository({ - index: '.kibana-test', - mappings, - client, + repository = SavedObjectsRepository.createRepository( migrator, - typeRegistry: registry, - serializer, - allowedTypes, - logger, - }); + registry, + '.kibana-test', + client, + logger + ); mockGetCurrentTime.mockReturnValue(mockTimestamp); mockGetSearchDsl.mockClear(); @@ -87,4 +76,60 @@ describe('SavedObjectsRepository', () => { expect(repository.getCurrentNamespace('space-a')).toBe('space-a'); }); }); + + describe('#asScopedToNamespace', () => { + it('returns a new client with undefined spacesExtensions (not available)', () => { + const scopedRepository = repository.asScopedToNamespace('space-a'); + expect(scopedRepository).toBeInstanceOf(SavedObjectsRepository); + expect(scopedRepository).not.toStrictEqual(repository); + + // Checking extensions.spacesExtension are both undefined + // @ts-expect-error type is ISavedObjectsRepository, but in reality is SavedObjectsRepository + expect(repository.extensions.spacesExtension).toBeUndefined(); + // @ts-expect-error type is ISavedObjectsRepository, but in reality is SavedObjectsRepository + expect(scopedRepository.extensions.spacesExtension).toBeUndefined(); + // @ts-expect-error type is ISavedObjectsRepository, but in reality is SavedObjectsRepository + expect(scopedRepository.extensions.spacesExtension).toStrictEqual( + // @ts-expect-error type is ISavedObjectsRepository, but in reality is SavedObjectsRepository + repository.extensions.spacesExtension + ); + }); + }); + + describe('with spacesExtension', () => { + let spacesExtension: jest.Mocked; + + beforeEach(() => { + spacesExtension = savedObjectsExtensionsMock.createSpacesExtension(); + repository = SavedObjectsRepository.createRepository( + migrator, + registry, + '.kibana-test', + client, + logger, + [], + { spacesExtension } + ); + }); + + describe('#asScopedToNamespace', () => { + it('returns a new client with space-scoped spacesExtensions', () => { + const scopedRepository = repository.asScopedToNamespace('space-a'); + expect(scopedRepository).toBeInstanceOf(SavedObjectsRepository); + expect(scopedRepository).not.toStrictEqual(repository); + expect(spacesExtension.asScopedToNamespace).toHaveBeenCalledWith('space-a'); + + // Checking extensions.spacesExtension are both defined but different + // @ts-expect-error type is ISavedObjectsRepository, but in reality is SavedObjectsRepository + expect(repository.extensions.spacesExtension).not.toBeUndefined(); + // @ts-expect-error type is ISavedObjectsRepository, but in reality is SavedObjectsRepository + expect(scopedRepository.extensions.spacesExtension).not.toBeUndefined(); + // @ts-expect-error type is ISavedObjectsRepository, but in reality is SavedObjectsRepository + expect(scopedRepository.extensions.spacesExtension).not.toStrictEqual( + // @ts-expect-error type is ISavedObjectsRepository, but in reality is SavedObjectsRepository + repository.extensions.spacesExtension + ); + }); + }); + }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 31a3c47e9c8ee..b2b8de1b4192a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -168,7 +168,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }); } - private constructor(options: SavedObjectsRepositoryOptions) { + private constructor(private readonly options: SavedObjectsRepositoryOptions) { const { index, mappings, @@ -564,4 +564,17 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { getCurrentNamespace(namespace?: string) { return this.helpers.common.getCurrentNamespace(namespace); } + + /** + * {@inheritDoc ISavedObjectsRepository.asScopedToNamespace} + */ + asScopedToNamespace(namespace: string) { + return new SavedObjectsRepository({ + ...this.options, + extensions: { + ...this.options.extensions, + spacesExtension: this.extensions.spacesExtension?.asScopedToNamespace(namespace), + }, + }); + } } diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts index 7fc01d314abf7..5b2f2041fd340 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts @@ -34,6 +34,7 @@ const createRepositoryMock = () => { collectMultiNamespaceReferences: jest.fn(), updateObjectsSpaces: jest.fn(), getCurrentNamespace: jest.fn(), + asScopedToNamespace: jest.fn().mockImplementation(createRepositoryMock), }; return mock; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/saved_objects_extensions.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/saved_objects_extensions.mock.ts index 9dc7c0f0133c5..0cde72544bd3f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/saved_objects_extensions.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/saved_objects_extensions.mock.ts @@ -47,6 +47,7 @@ const createSecurityExtension = (): jest.Mocked const createSpacesExtension = (): jest.Mocked => ({ getCurrentNamespace: jest.fn(), getSearchableNamespaces: jest.fn(), + asScopedToNamespace: jest.fn().mockImplementation(createSpacesExtension), }); const create = () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts index 73cbb826bfdd0..9440b45269145 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts @@ -335,4 +335,12 @@ describe('SavedObjectsClient', () => { expect(client.getCurrentNamespace()).toEqual('ns'); expect(mockRepository.getCurrentNamespace).toHaveBeenCalledWith(); }); + + test('#asScopedToNamespace', () => { + const client = new SavedObjectsClient(mockRepository); + + const rescopedClient = client.asScopedToNamespace('ns'); + expect(rescopedClient).not.toStrictEqual(client); + expect(mockRepository.asScopedToNamespace).toHaveBeenCalledWith('ns'); + }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts index a4d0b44751c7f..addfefde9cfc3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts @@ -216,4 +216,9 @@ export class SavedObjectsClient implements SavedObjectsClientContract { getCurrentNamespace() { return this._repository.getCurrentNamespace(); } + + /** {@inheritDoc SavedObjectsClientContract.asScopedToNamespace} */ + asScopedToNamespace(namespace: string) { + return new SavedObjectsClient(this._repository.asScopedToNamespace(namespace)); + } } diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts index 5584d130c1091..c9b9ab4fba2a3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts @@ -33,6 +33,7 @@ const create = () => { collectMultiNamespaceReferences: jest.fn(), updateObjectsSpaces: jest.fn(), getCurrentNamespace: jest.fn(), + asScopedToNamespace: jest.fn().mockImplementation(create), }; mock.createPointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts index c83ef37a1a956..aebeeb43059f4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts @@ -8,12 +8,10 @@ */ import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server'; import { savedObjectsPointInTimeFinderMock } from './point_in_time_finder.mock'; const create = () => { - const mock = { - errors: SavedObjectsErrorHelpers, + const mock: jest.Mocked = { create: jest.fn(), bulkCreate: jest.fn(), checkConflicts: jest.fn(), @@ -33,7 +31,8 @@ const create = () => { collectMultiNamespaceReferences: jest.fn(), updateObjectsSpaces: jest.fn(), getCurrentNamespace: jest.fn(), - } as unknown as jest.Mocked; + asScopedToNamespace: jest.fn().mockImplementation(create), + }; mock.createPointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ savedObjectsMock: mock, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts index 776ecfe3a7385..da9527ec4964f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts @@ -48,6 +48,7 @@ const createSecurityExtension = (): jest.Mocked const createSpacesExtension = (): jest.Mocked => ({ getCurrentNamespace: jest.fn(), getSearchableNamespaces: jest.fn(), + asScopedToNamespace: jest.fn().mockImplementation(createSpacesExtension), }); const create = (): jest.Mocked => ({ diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts index 495d7a58b0897..ae7b7d48e0d15 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts @@ -427,4 +427,10 @@ export interface SavedObjectsClientContract { * Returns the namespace associated with the client. If the namespace is the default one, this method returns `undefined`. */ getCurrentNamespace(): string | undefined; + + /** + * Returns a clone of the current Saved Objects client but scoped to the specified namespace. + * @param namespace Space to which the client should be scoped to. + */ + asScopedToNamespace(namespace: string): SavedObjectsClientContract; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index 46ebe1e4ceee6..e2be2a89d2a9b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -552,4 +552,10 @@ export interface ISavedObjectsRepository { * namespace. */ getCurrentNamespace(namespace?: string): string | undefined; + + /** + * Returns a new Saved Objects repository scoped to the specified namespace. + * @param namespace Space to which the repository should be scoped to. + */ + asScopedToNamespace(namespace: string): ISavedObjectsRepository; } diff --git a/packages/core/saved-objects/core-saved-objects-server/src/extensions/spaces.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions/spaces.ts index c81235c08e500..eb89e778b6c0e 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/extensions/spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/extensions/spaces.ts @@ -25,4 +25,9 @@ export interface ISavedObjectsSpacesExtension { * If a wildcard '*' is used, it is expanded to an explicit list of namespace strings. */ getSearchableNamespaces: (namespaces: string[] | undefined) => Promise; + /** + * Returns a new Saved Objects Spaces Extension scoped to the specified namespace. + * @param namespace Space to which the extension should be scoped to. + */ + asScopedToNamespace(namespace: string): ISavedObjectsSpacesExtension; } diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json index 02010e5cb4425..ef70be41b9c75 100644 --- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json +++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json @@ -27444,9 +27444,6 @@ "xpack.logsShared.dataSearch.cancelButtonLabel": "Annuler la demande", "xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "Réessayer", "xpack.logsShared.dataSearch.shardFailureErrorMessage": "Index {indexName} : {errorMessage}", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.message": "Les options d'affichage des indices et des données précédemment fournies par le biais de la page des paramètres de l'interface utilisateur des logs sont désormais obsolètes. Veuillez désormais utiliser le paramètre avancé des sources de logs Kibana.", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.message.manualStepMessage": "Mettez à jour le paramètre avancé des sources de logs Kibana (via Gestion > Paramètres avancés) pour qu'il corresponde au paramètre précédemment fourni via la page des paramètres de l'interface utilisateur des logs. Ensuite, via la page des paramètres de l'interface utilisateur des logs, utilisez l'option de paramètre avancé des sources de logs Kibana.", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.title": "Paramétrage des sources de logs", "xpack.logsShared.lobs.logEntryActionsViewInContextButton": "Afficher en contexte", "xpack.logsShared.logEntryActionsMenu.apmActionLabel": "Afficher dans APM", "xpack.logsShared.logEntryActionsMenu.buttonLabel": "Examiner", diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json index fa89085fb49c7..7932bf009da71 100644 --- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json +++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json @@ -27306,9 +27306,6 @@ "xpack.logsShared.dataSearch.cancelButtonLabel": "リクエストのキャンセル", "xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "再試行", "xpack.logsShared.dataSearch.shardFailureErrorMessage": "インデックス {indexName}:{errorMessage}", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.message": "以前はログUI設定ページで提供されていたインデックスとデータ表示オプションは、現在では廃止予定です。Kibanaログソースの詳細設定を使用するように移行してください。", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.message.manualStepMessage": "ログソースKibana詳細設定([管理]>[詳細設定])を、以前にLogs UI設定ページで指定した設定と一致するように更新します。次に、ログUI設定ページで、Kibanaログソースの詳細設定オプションを使用します。", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.title": "ログソース設定", "xpack.logsShared.lobs.logEntryActionsViewInContextButton": "コンテキストで表示", "xpack.logsShared.logEntryActionsMenu.apmActionLabel": "APMで表示", "xpack.logsShared.logEntryActionsMenu.buttonLabel": "調査", diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json index 2867f92eb40b9..3877a4e30991e 100644 --- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json +++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json @@ -27387,9 +27387,6 @@ "xpack.logsShared.dataSearch.cancelButtonLabel": "取消请求", "xpack.logsShared.dataSearch.loadingErrorRetryButtonLabel": "重试", "xpack.logsShared.dataSearch.shardFailureErrorMessage": "索引 {indexName}:{errorMessage}", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.message": "以前通过日志 UI 设置页面提供的索引和数据视图选项现已弃用。请进行迁移,以使用 Kibana 日志源高级设置。", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.message.manualStepMessage": "更新日志源 Kibana 高级设置(通过“管理 > 高级设置”),以匹配以前通过日志 UI 设置页面提供的设置。然后,通过日志 UI 设置页面使用 Kibana 日志源高级设置选项。", - "xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.title": "日志源设置", "xpack.logsShared.lobs.logEntryActionsViewInContextButton": "在上下文中查看", "xpack.logsShared.logEntryActionsMenu.apmActionLabel": "在 APM 中查看", "xpack.logsShared.logEntryActionsMenu.buttonLabel": "调查", diff --git a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc index 69539098f2463..e6ee1a22edc05 100644 --- a/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc +++ b/x-pack/plugins/observability_solution/logs_shared/kibana.jsonc @@ -11,12 +11,13 @@ "requiredPlugins": [ "charts", "data", - "fieldFormats", + "fieldFormats", "dataViews", "discoverShared", "logsDataAccess", "observabilityShared", "share", + "spaces", "usageCollection", "embeddable", ], @@ -27,4 +28,3 @@ "extraPublicDirs": ["common"] } } - \ No newline at end of file diff --git a/x-pack/plugins/observability_solution/logs_shared/server/deprecations/constants.ts b/x-pack/plugins/observability_solution/logs_shared/server/deprecations/constants.ts new file mode 100644 index 0000000000000..315be28d67510 --- /dev/null +++ b/x-pack/plugins/observability_solution/logs_shared/server/deprecations/constants.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * Number of spaces to address concurrently. + * We don't want to loop through all the spaces concurrently to avoid putting too much pressure on the memory in case that there are too many spaces. + */ +export const CONCURRENT_SPACES_TO_CHECK = 500; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/deprecations/log_sources_setting.ts b/x-pack/plugins/observability_solution/logs_shared/server/deprecations/log_sources_setting.ts index c3e891edf74c9..0bccc83682382 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/deprecations/log_sources_setting.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/deprecations/log_sources_setting.ts @@ -6,39 +6,50 @@ */ import { DeprecationsDetails } from '@kbn/core-deprecations-common'; import { GetDeprecationsContext } from '@kbn/core-deprecations-server'; +import pMap from 'p-map'; +import type { Space } from '@kbn/spaces-plugin/common'; import { i18n } from '@kbn/i18n'; -import { defaultLogViewId } from '../../common/log_views'; import { MIGRATE_LOG_VIEW_SETTINGS_URL } from '../../common/http_api/deprecations'; +import { CONCURRENT_SPACES_TO_CHECK } from './constants'; +import { defaultLogViewId } from '../../common/log_views'; import { logSourcesKibanaAdvancedSettingRT } from '../../common'; import { LogsSharedPluginStartServicesAccessor } from '../types'; -export const getLogSourcesSettingDeprecationInfo = async ({ - getStartServices, - context, -}: { +export interface LogSourcesSettingDeprecationParams { context: GetDeprecationsContext; getStartServices: LogsSharedPluginStartServicesAccessor; -}): Promise => { - const [_, pluginStartDeps, pluginStart] = await getStartServices(); - const logSourcesService = - pluginStartDeps.logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( - context.savedObjectsClient - ); - const logViewsClient = pluginStart.logViews.getClient( - context.savedObjectsClient, - context.esClient.asCurrentUser, - logSourcesService - ); +} + +export const getLogSourcesSettingDeprecationInfo = async ( + params: LogSourcesSettingDeprecationParams +): Promise => { + const [_, pluginStartDeps] = await params.getStartServices(); + + const allAvailableSpaces = await pluginStartDeps.spaces.spacesService + .createSpacesClient(params.context.request) + .getAll({ purpose: 'any' }); + + const deprecationPerSpaceFactory = getLogSourcesSettingDeprecationInfoForSpaceFactory(params); + + const results = await pMap(allAvailableSpaces, deprecationPerSpaceFactory, { + concurrency: CONCURRENT_SPACES_TO_CHECK, // limit the number of spaces handled concurrently to make sure that we cover large deployments + }); - const logView = await logViewsClient.getLogView(defaultLogViewId); + const offendingSpaces = results.filter(Boolean) as string[]; - if (logView && !logSourcesKibanaAdvancedSettingRT.is(logView.attributes.logIndices)) { + if (offendingSpaces.length) { + const shortList = + offendingSpaces.length < 4 + ? offendingSpaces.join(', ') + : `${offendingSpaces.slice(0, 3).join(', ')}, ...`; + const fullList = offendingSpaces.join(', '); return [ { title: i18n.translate( 'xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.title', { - defaultMessage: 'Log sources setting', + defaultMessage: 'Log sources setting in {count} spaces: {shortList}', + values: { count: offendingSpaces.length, shortList }, } ), level: 'warning', @@ -47,19 +58,21 @@ export const getLogSourcesSettingDeprecationInfo = async ({ 'xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.message', { defaultMessage: - 'Indices and Data view options previously provided via the Logs UI settings page are now deprecated. Please migrate to using the Kibana log sources advanced setting.', + 'Indices and Data view options previously provided via the Logs UI settings page are now deprecated. Please migrate to using the Kibana log sources advanced setting in each of the following spaces: {fullList}.', + values: { fullList }, } ), correctiveActions: { - manualSteps: [ + manualSteps: offendingSpaces.map((spaceName) => i18n.translate( 'xpack.logsShared.deprecations.migrateLogViewSettingsToLogSourcesSetting.message.manualStepMessage', { defaultMessage: - 'Update the Log sources Kibana advanced setting (via Management > Advanced Settings) to match the setting previously provided via the Logs UI settings page. Then via the Logs UI settings page use the Kibana log sources advanced setting option.', + 'While in the space "{spaceName}" update the Log sources Kibana advanced setting (via Management > Advanced Settings) to match the setting previously provided via the Logs UI settings page. Then via the Logs UI settings page use the Kibana log sources advanced setting option.', + values: { spaceName }, } - ), - ], + ) + ), api: { method: 'PUT', path: MIGRATE_LOG_VIEW_SETTINGS_URL, @@ -71,3 +84,31 @@ export const getLogSourcesSettingDeprecationInfo = async ({ return []; } }; + +export const getLogSourcesSettingDeprecationInfoForSpaceFactory = ({ + getStartServices, + context, +}: LogSourcesSettingDeprecationParams): ((space: Space) => Promise) => { + return async (space) => { + const [_, pluginStartDeps, pluginStart] = await getStartServices(); + + // Get a new Saved Object Client scoped to the space.id + const spaceScopedSavedObjectsClient = context.savedObjectsClient.asScopedToNamespace(space.id); + + const logSourcesService = + pluginStartDeps.logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( + spaceScopedSavedObjectsClient + ); + const logViewsClient = pluginStart.logViews.getClient( + spaceScopedSavedObjectsClient, + context.esClient.asCurrentUser, + logSourcesService + ); + + const logView = await logViewsClient.getLogView(defaultLogViewId); + + if (logView && !logSourcesKibanaAdvancedSettingRT.is(logView.attributes.logIndices)) { + return space.name; + } + }; +}; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/routes/deprecations/migrate_log_view_settings.ts b/x-pack/plugins/observability_solution/logs_shared/server/routes/deprecations/migrate_log_view_settings.ts index f3e9db4f1a765..d6284a63e8461 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/routes/deprecations/migrate_log_view_settings.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/routes/deprecations/migrate_log_view_settings.ts @@ -5,6 +5,8 @@ * 2.0. */ +import pMap from 'p-map'; +import { CONCURRENT_SPACES_TO_CHECK } from '../../deprecations/constants'; import { defaultLogViewId } from '../../../common/log_views'; import { MIGRATE_LOG_VIEW_SETTINGS_URL } from '../../../common/http_api/deprecations'; import { logSourcesKibanaAdvancedSettingRT } from '../../../common'; @@ -23,17 +25,54 @@ export const initMigrateLogViewSettingsRoute = ({ { path: MIGRATE_LOG_VIEW_SETTINGS_URL, validate: false }, async (context, request, response) => { try { + const { elasticsearch, savedObjects } = await context.core; const [_, pluginStartDeps, pluginStart] = await getStartServices(); - const logSourcesService = - await pluginStartDeps.logsDataAccess.services.logSourcesServiceFactory.getScopedLogSourcesService( - request - ); - const logViewsClient = pluginStart.logViews.getScopedClient(request); + const allAvailableSpaces = await pluginStartDeps.spaces.spacesService + .createSpacesClient(request) + .getAll({ purpose: 'any' }); - const logView = await logViewsClient.getLogView(defaultLogViewId); + const updated = await pMap( + allAvailableSpaces, + async (space) => { + const spaceScopedSavedObjectsClient = savedObjects.client.asScopedToNamespace(space.id); - if (!logView || logSourcesKibanaAdvancedSettingRT.is(logView.attributes.logIndices)) { + const logSourcesServicePromise = + pluginStartDeps.logsDataAccess.services.logSourcesServiceFactory.getLogSourcesService( + spaceScopedSavedObjectsClient + ); + const logViewsClient = pluginStart.logViews.getClient( + spaceScopedSavedObjectsClient, + elasticsearch.client.asCurrentUser, + logSourcesServicePromise + ); + + const logView = await logViewsClient.getLogView(defaultLogViewId); + + if (!logView || logSourcesKibanaAdvancedSettingRT.is(logView.attributes.logIndices)) { + return false; + } + + const indices = ( + await logViewsClient.getResolvedLogView({ + type: 'log-view-reference', + logViewId: defaultLogViewId, + }) + ).indices; + + const logSourcesService = await logSourcesServicePromise; + await logSourcesService.setLogSources([{ indexPattern: indices }]); + await logViewsClient.putLogView(defaultLogViewId, { + logIndices: { type: 'kibana_advanced_setting' }, + }); + + return true; + }, + { concurrency: CONCURRENT_SPACES_TO_CHECK } + ); + + if (!updated.includes(true)) { + // Only throw if none of the spaces was able to migrate return response.customError({ body: new Error( "Unable to migrate log view settings. A log view either doesn't exist or is already using the Kibana advanced setting." @@ -42,17 +81,6 @@ export const initMigrateLogViewSettingsRoute = ({ }); } - const indices = ( - await logViewsClient.getResolvedLogView({ - type: 'log-view-reference', - logViewId: defaultLogViewId, - }) - ).indices; - - await logSourcesService.setLogSources([{ indexPattern: indices }]); - await logViewsClient.putLogView(defaultLogViewId, { - logIndices: { type: 'kibana_advanced_setting' }, - }); return response.ok(); } catch (error) { throw error; diff --git a/x-pack/plugins/observability_solution/logs_shared/server/types.ts b/x-pack/plugins/observability_solution/logs_shared/server/types.ts index 73365ece21a14..770229ec035d8 100644 --- a/x-pack/plugins/observability_solution/logs_shared/server/types.ts +++ b/x-pack/plugins/observability_solution/logs_shared/server/types.ts @@ -12,6 +12,7 @@ import { } from '@kbn/data-plugin/server'; import { PluginStart as DataViewsPluginStart } from '@kbn/data-views-plugin/server'; import { LogsDataAccessPluginStart } from '@kbn/logs-data-access-plugin/server'; +import type { SpacesPluginStart } from '@kbn/spaces-plugin/server'; import { LogsSharedDomainLibs } from './lib/logs_shared_types'; import { LogViewsServiceSetup, LogViewsServiceStart } from './services/log_views/types'; @@ -38,6 +39,7 @@ export interface LogsSharedServerPluginStartDeps { data: DataPluginStart; dataViews: DataViewsPluginStart; logsDataAccess: LogsDataAccessPluginStart; + spaces: SpacesPluginStart; } export interface UsageCollector { diff --git a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json index acaed5073a176..221d6db6d6466 100644 --- a/x-pack/plugins/observability_solution/logs_shared/tsconfig.json +++ b/x-pack/plugins/observability_solution/logs_shared/tsconfig.json @@ -51,5 +51,6 @@ "@kbn/field-formats-plugin", "@kbn/embeddable-plugin", "@kbn/saved-search-plugin", + "@kbn/spaces-plugin", ] } diff --git a/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.test.ts b/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.test.ts index 420809aba646e..0e421063d51a8 100644 --- a/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.test.ts +++ b/x-pack/plugins/reporting/server/deprecations/migrate_existing_indices_ilm_policy.test.ts @@ -6,7 +6,11 @@ */ import type { GetDeprecationsContext } from '@kbn/core/server'; -import { elasticsearchServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks'; +import { + elasticsearchServiceMock, + httpServerMock, + savedObjectsClientMock, +} from '@kbn/core/server/mocks'; import { getDeprecationsInfo } from './migrate_existing_indices_ilm_policy'; @@ -20,7 +24,11 @@ describe("Migrate existing indices' ILM policy deprecations", () => { beforeEach(async () => { esClient = elasticsearchServiceMock.createScopedClusterClient(); - deprecationsCtx = { esClient, savedObjectsClient: savedObjectsClientMock.create() }; + deprecationsCtx = { + esClient, + savedObjectsClient: savedObjectsClientMock.create(), + request: httpServerMock.createKibanaRequest(), + }; }); const createIndexSettings = (lifecycleName: string) => ({ diff --git a/x-pack/plugins/security/server/deprecations/kibana_user_role.test.ts b/x-pack/plugins/security/server/deprecations/kibana_user_role.test.ts index c0b1c31a91aa9..4f9d9f29253db 100644 --- a/x-pack/plugins/security/server/deprecations/kibana_user_role.test.ts +++ b/x-pack/plugins/security/server/deprecations/kibana_user_role.test.ts @@ -12,6 +12,7 @@ import type { PackageInfo, RegisterDeprecationsConfig } from '@kbn/core/server'; import { deprecationsServiceMock, elasticsearchServiceMock, + httpServerMock, loggingSystemMock, savedObjectsClientMock, } from '@kbn/core/server/mocks'; @@ -39,6 +40,7 @@ function getContextMock() { return { esClient: elasticsearchServiceMock.createScopedClusterClient(), savedObjectsClient: savedObjectsClientMock.create(), + request: httpServerMock.createKibanaRequest(), }; } diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.ts index a2a4877ac4d9a..6d5c98d2400a0 100644 --- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.ts @@ -141,3 +141,13 @@ describe('#getSearchableNamespaces', () => { ]); }); }); + +describe('#asScopedToNamespace', () => { + test('returns a extension scoped to the provided namespace', () => { + const { spacesExtension } = setup(); + const rescopedExtension = spacesExtension.asScopedToNamespace('space-a'); + expect(rescopedExtension).toBeInstanceOf(SavedObjectsSpacesExtension); + expect(rescopedExtension).not.toStrictEqual(spacesExtension); + expect(rescopedExtension.getCurrentNamespace(undefined)).toStrictEqual('namespace-for-space-a'); + }); +}); diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.ts index 4520ff5ae3353..3701922d9a776 100644 --- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.ts +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.ts @@ -52,4 +52,11 @@ export class SavedObjectsSpacesExtension implements ISavedObjectsSpacesExtension ); } } + + asScopedToNamespace(namespace: string) { + return new SavedObjectsSpacesExtension({ + activeSpaceId: namespace, + spacesClient: this.spacesClient, + }); + } }