[Deprecations] Logs Sources settings in all spaces (#203042)

This commit is contained in:
Alejandro Fernández Haro 2024-12-16 13:40:56 +01:00 committed by GitHub
parent af566c0398
commit 2ed34427c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 300 additions and 89 deletions

View file

@ -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;

View file

@ -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();
});

View file

@ -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,
}),
};
};

View file

@ -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<DeprecationsRegistry>;
@ -28,6 +29,7 @@ const createGetDeprecationsContextMock = () => {
const mocked: jest.Mocked<GetDeprecationsContext> = {
esClient: elasticsearchClientMock.createScopedClusterClient(),
savedObjectsClient: savedObjectsClientMock.create(),
request: httpServerMock.createKibanaRequest(),
};
return mocked;

View file

@ -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;
}
/**

View file

@ -15,6 +15,7 @@
"@kbn/core-deprecations-common",
"@kbn/core-elasticsearch-server",
"@kbn/core-saved-objects-api-server",
"@kbn/core-http-server",
],
"exclude": [
"target/**/*",

View file

@ -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;

View file

@ -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<typeof elasticsearchClientMock.createElasticsearchClient>;
let repository: SavedObjectsRepository;
let repository: ISavedObjectsRepository;
let migrator: ReturnType<typeof kibanaMigratorMock.create>;
let logger: ReturnType<typeof loggerMock.create>;
let serializer: jest.Mocked<SavedObjectsSerializer>;
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<ISavedObjectsSpacesExtension>;
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
);
});
});
});
});

View file

@ -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),
},
});
}
}

View file

@ -34,6 +34,7 @@ const createRepositoryMock = () => {
collectMultiNamespaceReferences: jest.fn(),
updateObjectsSpaces: jest.fn(),
getCurrentNamespace: jest.fn(),
asScopedToNamespace: jest.fn().mockImplementation(createRepositoryMock),
};
return mock;

View file

@ -47,6 +47,7 @@ const createSecurityExtension = (): jest.Mocked<ISavedObjectsSecurityExtension>
const createSpacesExtension = (): jest.Mocked<ISavedObjectsSpacesExtension> => ({
getCurrentNamespace: jest.fn(),
getSearchableNamespaces: jest.fn(),
asScopedToNamespace: jest.fn().mockImplementation(createSpacesExtension),
});
const create = () => ({

View file

@ -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');
});
});

View file

@ -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));
}
}

View file

@ -33,6 +33,7 @@ const create = () => {
collectMultiNamespaceReferences: jest.fn(),
updateObjectsSpaces: jest.fn(),
getCurrentNamespace: jest.fn(),
asScopedToNamespace: jest.fn().mockImplementation(create),
};
mock.createPointInTimeFinder = savedObjectsPointInTimeFinderMock.create({

View file

@ -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<SavedObjectsClientContract> = {
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<SavedObjectsClientContract>;
asScopedToNamespace: jest.fn().mockImplementation(create),
};
mock.createPointInTimeFinder = savedObjectsPointInTimeFinderMock.create({
savedObjectsMock: mock,

View file

@ -48,6 +48,7 @@ const createSecurityExtension = (): jest.Mocked<ISavedObjectsSecurityExtension>
const createSpacesExtension = (): jest.Mocked<ISavedObjectsSpacesExtension> => ({
getCurrentNamespace: jest.fn(),
getSearchableNamespaces: jest.fn(),
asScopedToNamespace: jest.fn().mockImplementation(createSpacesExtension),
});
const create = (): jest.Mocked<SavedObjectsExtensions> => ({

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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<string[]>;
/**
* 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;
}

View file

@ -27320,9 +27320,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",

View file

@ -27183,9 +27183,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": "調査",

View file

@ -26741,9 +26741,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": "调查",

View file

@ -11,12 +11,13 @@
"requiredPlugins": [
"charts",
"data",
"fieldFormats",
"fieldFormats",
"dataViews",
"discoverShared",
"logsDataAccess",
"observabilityShared",
"share",
"spaces",
"usageCollection",
"embeddable",
],
@ -27,4 +28,3 @@
"extraPublicDirs": ["common"]
}
}

View file

@ -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;

View file

@ -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<DeprecationsDetails[]> => {
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
);
}
const logView = await logViewsClient.getLogView(defaultLogViewId);
export const getLogSourcesSettingDeprecationInfo = async (
params: LogSourcesSettingDeprecationParams
): Promise<DeprecationsDetails[]> => {
const [_, pluginStartDeps] = await params.getStartServices();
if (logView && !logSourcesKibanaAdvancedSettingRT.is(logView.attributes.logIndices)) {
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 offendingSpaces = results.filter(Boolean) as string[];
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<string | undefined>) => {
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;
}
};
};

View file

@ -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;

View file

@ -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 {

View file

@ -52,5 +52,6 @@
"@kbn/field-formats-plugin",
"@kbn/embeddable-plugin",
"@kbn/saved-search-plugin",
"@kbn/spaces-plugin",
]
}

View file

@ -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) => ({

View file

@ -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(),
};
}

View file

@ -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');
});
});

View file

@ -52,4 +52,11 @@ export class SavedObjectsSpacesExtension implements ISavedObjectsSpacesExtension
);
}
}
asScopedToNamespace(namespace: string) {
return new SavedObjectsSpacesExtension({
activeSpaceId: namespace,
spacesClient: this.spacesClient,
});
}
}