From b02570f1dc156d914c6e95aecd50d219e946be65 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Mon, 17 Mar 2025 10:26:04 -0400 Subject: [PATCH 01/14] Inject / extract tag references --- .../dashboard_api/get_serialized_state.ts | 2 + .../content_management/dashboard_storage.ts | 106 ++++++++++++++---- .../content_management/v1/cm_services.ts | 7 -- .../content_management/v2/cm_services.ts | 11 +- .../content_management/v3/cm_services.ts | 12 +- .../server/content_management/v3/index.ts | 2 +- .../v3/transform_utils.test.ts | 2 +- .../content_management/v3/transform_utils.ts | 55 ++++++--- .../server/content_management/v3/types.ts | 14 ++- .../migrate_extract_panel_references.ts | 4 +- .../plugins/shared/dashboard/server/plugin.ts | 25 +++-- 11 files changed, 166 insertions(+), 74 deletions(-) diff --git a/src/platform/plugins/shared/dashboard/public/dashboard_api/get_serialized_state.ts b/src/platform/plugins/shared/dashboard/public/dashboard_api/get_serialized_state.ts index bb7699b078d6d..854e3fb6717e1 100644 --- a/src/platform/plugins/shared/dashboard/public/dashboard_api/get_serialized_state.ts +++ b/src/platform/plugins/shared/dashboard/public/dashboard_api/get_serialized_state.ts @@ -138,6 +138,8 @@ export const getSerializedState = ({ { embeddablePersistableStateService: embeddableService } ); + // TODO Provide tags as an array of tag names in the attribute. In that case, tag references + // will be extracted by the server. const savedObjectsTaggingApi = savedObjectsTaggingService?.getTaggingApi(); const references = savedObjectsTaggingApi?.ui.updateTagsReferences ? savedObjectsTaggingApi?.ui.updateTagsReferences(dashboardReferences, tags) diff --git a/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts b/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts index 2c1875979ad2c..9525abd8c7dfd 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts @@ -14,10 +14,13 @@ import type { Logger } from '@kbn/logging'; import { CreateResult, DeleteResult, SearchQuery } from '@kbn/content-management-plugin/common'; import { StorageContext } from '@kbn/content-management-plugin/server'; +import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server'; +import type { SavedObjectReference } from '@kbn/core/server'; +import type { Tag } from '@kbn/saved-objects-tagging-oss-plugin/common'; import { DASHBOARD_SAVED_OBJECT_TYPE } from '../dashboard_saved_object'; import { cmServicesDefinition } from './cm_services'; import { DashboardSavedObjectAttributes } from '../dashboard_saved_object'; -import { itemAttrsToSavedObjectAttrs, savedObjectToItem } from './latest'; +import { itemAttrsToSavedObject, savedObjectToItem } from './latest'; import type { DashboardAttributes, DashboardItem, @@ -37,7 +40,7 @@ const searchArgsToSOFindOptions = ( return { type: DASHBOARD_SAVED_OBJECT_TYPE, searchFields: options?.onlyTitle ? ['title'] : ['title^3', 'description'], - fields: options?.fields, + // fields: options?.fields, search: query.text, perPage: query.limit, page: query.cursor ? +query.cursor : undefined, @@ -60,21 +63,45 @@ export class DashboardStorage { constructor({ logger, throwOnResultValidationError, + savedObjectsTagging, }: { logger: Logger; throwOnResultValidationError: boolean; + savedObjectsTagging?: SavedObjectTaggingStart; }) { + this.savedObjectsTagging = savedObjectsTagging; this.logger = logger; this.throwOnResultValidationError = throwOnResultValidationError ?? false; } private logger: Logger; + private savedObjectsTagging?: SavedObjectTaggingStart; private throwOnResultValidationError: boolean; + private getTagNamesFromReferences = (references: SavedObjectReference[], allTags: Tag[]) => { + return this.savedObjectsTagging + ? this.savedObjectsTagging + .getTagsFromReferences(references, allTags) + .tags.map((tag) => tag.name) + : []; + }; + + private replaceTagReferencesByName = ( + references: SavedObjectReference[], + newTagNames: string[], + allTags: Tag[] + ) => { + const newTagsIds = newTagNames.flatMap( + (tagName) => this.savedObjectsTagging?.convertTagNameToId(tagName, allTags) ?? [] + ); + return this.savedObjectsTagging?.replaceTagReferences(references, newTagsIds) ?? references; + }; + async get(ctx: StorageContext, id: string): Promise { const transforms = ctx.utils.getTransforms(cmServicesDefinition); const soClient = await savedObjectClientFromRequest(ctx); - + const tagsClient = this.savedObjectsTagging?.createTagClient({ client: soClient }); + const allTags = (await tagsClient?.getAll()) ?? []; // Save data in DB const { saved_object: savedObject, @@ -83,7 +110,10 @@ export class DashboardStorage { outcome, } = await soClient.resolve(DASHBOARD_SAVED_OBJECT_TYPE, id); - const { item, error: itemError } = savedObjectToItem(savedObject, false); + const { item, error: itemError } = savedObjectToItem(savedObject, false, { + getTagNamesFromReferences: (references: SavedObjectReference[]) => + this.getTagNamesFromReferences(references, allTags), + }); if (itemError) { throw Boom.badRequest(`Invalid response. ${itemError.message}`); } @@ -128,6 +158,8 @@ export class DashboardStorage { ): Promise { const transforms = ctx.utils.getTransforms(cmServicesDefinition); const soClient = await savedObjectClientFromRequest(ctx); + const tagsClient = this.savedObjectsTagging?.createTagClient({ client: soClient }); + const allTags = tagsClient ? await tagsClient?.getAll() : []; // Validate input (data & options) & UP transform them to the latest version const { value: dataToLatest, error: dataError } = transforms.create.in.data.up< @@ -146,8 +178,16 @@ export class DashboardStorage { throw Boom.badRequest(`Invalid options. ${optionsError.message}`); } - const { attributes: soAttributes, error: attributesError } = - itemAttrsToSavedObjectAttrs(dataToLatest); + const { + attributes: soAttributes, + references: soReferences, + error: attributesError, + } = itemAttrsToSavedObject({ + attributes: dataToLatest, + replaceTagReferencesByName: (references: SavedObjectReference[], newTagNames: string[]) => + this.replaceTagReferencesByName(references, newTagNames, allTags), + incomingReferences: options.references, + }); if (attributesError) { throw Boom.badRequest(`Invalid data. ${attributesError.message}`); } @@ -156,10 +196,13 @@ export class DashboardStorage { const savedObject = await soClient.create( DASHBOARD_SAVED_OBJECT_TYPE, soAttributes, - optionsToLatest + { ...optionsToLatest, references: soReferences } ); - const { item, error: itemError } = savedObjectToItem(savedObject, false); + const { item, error: itemError } = savedObjectToItem(savedObject, false, { + getTagNamesFromReferences: (references: SavedObjectReference[]) => + this.getTagNamesFromReferences(references, allTags), + }); if (itemError) { throw Boom.badRequest(`Invalid response. ${itemError.message}`); } @@ -197,6 +240,8 @@ export class DashboardStorage { ): Promise { const transforms = ctx.utils.getTransforms(cmServicesDefinition); const soClient = await savedObjectClientFromRequest(ctx); + const tagsClient = this.savedObjectsTagging?.createTagClient({ client: soClient }); + const allTags = (await tagsClient?.getAll()) ?? []; // Validate input (data & options) & UP transform them to the latest version const { value: dataToLatest, error: dataError } = transforms.update.in.data.up< @@ -215,8 +260,16 @@ export class DashboardStorage { throw Boom.badRequest(`Invalid options. ${optionsError.message}`); } - const { attributes: soAttributes, error: attributesError } = - itemAttrsToSavedObjectAttrs(dataToLatest); + const { + attributes: soAttributes, + references: soReferences, + error: attributesError, + } = itemAttrsToSavedObject({ + attributes: dataToLatest, + replaceTagReferencesByName: (references: SavedObjectReference[], newTagNames: string[]) => + this.replaceTagReferencesByName(references, newTagNames, allTags), + incomingReferences: options.references, + }); if (attributesError) { throw Boom.badRequest(`Invalid data. ${attributesError.message}`); } @@ -226,10 +279,13 @@ export class DashboardStorage { DASHBOARD_SAVED_OBJECT_TYPE, id, soAttributes, - optionsToLatest + { ...optionsToLatest, references: soReferences } ); - const { item, error: itemError } = savedObjectToItem(partialSavedObject, true); + const { item, error: itemError } = savedObjectToItem(partialSavedObject, true, { + getTagNamesFromReferences: (references: SavedObjectReference[]) => + this.getTagNamesFromReferences(references, allTags), + }); if (itemError) { throw Boom.badRequest(`Invalid response. ${itemError.message}`); } @@ -278,6 +334,8 @@ export class DashboardStorage { ): Promise { const transforms = ctx.utils.getTransforms(cmServicesDefinition); const soClient = await savedObjectClientFromRequest(ctx); + const tagsClient = this.savedObjectsTagging?.createTagClient({ client: soClient }); + const allTags = (await tagsClient?.getAll()) ?? []; // Validate and UP transform the options const { value: optionsToLatest, error: optionsError } = transforms.search.in.options.up< @@ -291,16 +349,20 @@ export class DashboardStorage { const soQuery = searchArgsToSOFindOptions(query, optionsToLatest); // Execute the query in the DB const soResponse = await soClient.find(soQuery); - const hits = soResponse.saved_objects - .map((so) => { - const { item } = savedObjectToItem(so, false, { - allowedAttributes: soQuery.fields, - allowedReferences: optionsToLatest?.includeReferences, - }); - return item; - }) - // Ignore any saved objects that failed to convert to items. - .filter((item) => item !== null); + const hits = await Promise.all( + soResponse.saved_objects + .map(async (so) => { + const { item } = savedObjectToItem(so, false, { + allowedAttributes: soQuery.fields, + allowedReferences: optionsToLatest?.includeReferences, + getTagNamesFromReferences: (references: SavedObjectReference[]) => + this.getTagNamesFromReferences(references, allTags), + }); + return item; + }) + // Ignore any saved objects that failed to convert to items. + .filter((item) => item !== null) + ); const response = { hits, pagination: { diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v1/cm_services.ts b/src/platform/plugins/shared/dashboard/server/content_management/v1/cm_services.ts index 0cee0bb23f450..9ccf05a510b88 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v1/cm_services.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v1/cm_services.ts @@ -84,11 +84,4 @@ export const serviceDefinition: ServicesDefinition = { }, }, }, - mSearch: { - out: { - result: { - schema: dashboardSavedObjectSchema, - }, - }, - }, }; diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v2/cm_services.ts b/src/platform/plugins/shared/dashboard/server/content_management/v2/cm_services.ts index 3b560b8416731..e1fce210b0e19 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v2/cm_services.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v2/cm_services.ts @@ -37,7 +37,7 @@ export const serviceDefinition: ServicesDefinition = { ...serviceDefinitionV1?.create?.in, data: { schema: dashboardAttributesSchema, - up: (data: DashboardCrudTypes['CreateIn']['data']) => attributesTov3(data), + up: (data: DashboardCrudTypes['CreateIn']['data']) => attributesTov3(data, [], () => []), }, }, out: { @@ -51,18 +51,11 @@ export const serviceDefinition: ServicesDefinition = { ...serviceDefinitionV1.update?.in, data: { schema: dashboardAttributesSchema, - up: (data: DashboardCrudTypes['UpdateIn']['data']) => attributesTov3(data), + up: (data: DashboardCrudTypes['UpdateIn']['data']) => attributesTov3(data, [], () => []), }, }, }, search: { in: serviceDefinitionV1.search?.in, }, - mSearch: { - out: { - result: { - schema: dashboardSavedObjectSchema, - }, - }, - }, }; diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/cm_services.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/cm_services.ts index b2d0c55e1f9d3..36aa421ade3a0 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/cm_services.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/cm_services.ts @@ -333,6 +333,11 @@ export const searchResultsAttributesSchema = schema.object({ defaultValue: false, meta: { description: 'Whether to restore time upon viewing this dashboard' }, }), + tags: schema.maybe( + schema.arrayOf( + schema.string({ meta: { description: 'An array of tags applied to this dashboard' } }) + ) + ), }); export const dashboardAttributesSchema = searchResultsAttributesSchema.extends({ @@ -553,11 +558,4 @@ export const serviceDefinition: ServicesDefinition = { }, }, }, - mSearch: { - out: { - result: { - schema: dashboardItemSchema, - }, - }, - }, }; diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/index.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/index.ts index 7be9313c3210e..32a5469fcf72c 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/index.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/index.ts @@ -37,6 +37,6 @@ export { } from './cm_services'; export { dashboardAttributesOut, - itemAttrsToSavedObjectAttrs, + itemAttrsToSavedObject, savedObjectToItem, } from './transform_utils'; diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts index 94b5f247dd832..11a660455318c 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts @@ -16,7 +16,7 @@ import type { DashboardAttributes, DashboardItem } from './types'; import { dashboardAttributesOut, getResultV3ToV2, - itemAttrsToSavedObjectAttrs, + itemAttrsToSavedObject, savedObjectToItem, } from './transform_utils'; import { diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts index 36e9567786c0d..72cfcc96594a6 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts @@ -32,7 +32,8 @@ import type { DashboardGetOut, DashboardItem, DashboardOptions, - ItemAttrsToSavedObjectAttrsReturn, + ItemAttrsToSavedObjectParams, + ItemAttrsToSavedObjectReturn, PartialDashboardItem, SavedObjectToItemReturn, } from './types'; @@ -148,7 +149,9 @@ function panelsOut(panelsJSON: string): DashboardAttributes['panels'] { } export function dashboardAttributesOut( - attributes: DashboardSavedObjectAttributes | Partial + attributes: DashboardSavedObjectAttributes | Partial, + references: SavedObjectReference[], + getTagNamesFromReferences?: (references: SavedObjectReference[]) => string[] ): DashboardAttributes | Partial { const { controlGroupInput, @@ -164,6 +167,15 @@ export function dashboardAttributesOut( version, } = attributes; // try to maintain a consistent (alphabetical) order of keys + + // Inject any tag names from references into the attributes + const tags = new Set(); + const tagRefs = references.filter(({ type }) => type === 'tag'); + if (getTagNamesFromReferences && tagRefs.length) { + const tagNames = getTagNamesFromReferences(tagRefs); + tagNames.forEach((tagName) => tags.add(tagName)); + } + return { ...(controlGroupInput && { controlGroupInput: controlGroupInputOut(controlGroupInput) }), ...(description && { description }), @@ -175,6 +187,7 @@ export function dashboardAttributesOut( ...(refreshInterval && { refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value }, }), + tags: Array.from(tags), ...(timeFrom && { timeFrom }), timeRestore: timeRestore ?? false, ...(timeTo && { timeTo }), @@ -231,7 +244,9 @@ function kibanaSavedObjectMetaIn( return { searchSourceJSON: JSON.stringify(searchSource ?? {}) }; } -export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2['GetOut'] => { +export const getResultV3ToV2 = async ( + result: DashboardGetOut +): Promise => { const { meta, item } = result; const { attributes, ...rest } = item; const { @@ -274,11 +289,13 @@ export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2[' }; }; -export const itemAttrsToSavedObjectAttrs = ( - attributes: DashboardAttributes -): ItemAttrsToSavedObjectAttrsReturn => { +export const itemAttrsToSavedObject = ({ + attributes, + replaceTagReferencesByName, + incomingReferences = [], +}: ItemAttrsToSavedObjectParams): ItemAttrsToSavedObjectReturn => { try { - const { controlGroupInput, kibanaSavedObjectMeta, options, panels, ...rest } = attributes; + const { controlGroupInput, kibanaSavedObjectMeta, options, panels, tags, ...rest } = attributes; const soAttributes = { ...rest, ...(controlGroupInput && { @@ -294,9 +311,15 @@ export const itemAttrsToSavedObjectAttrs = ( kibanaSavedObjectMeta: kibanaSavedObjectMetaIn(kibanaSavedObjectMeta), }), }; - return { attributes: soAttributes, error: null }; + + // Tags can be specified as an attribute or in the incomingReferences. + const soReferences = + replaceTagReferencesByName && tags && tags.length + ? replaceTagReferencesByName(incomingReferences, tags) + : incomingReferences; + return { attributes: soAttributes, references: soReferences, error: null }; } catch (e) { - return { attributes: null, error: e }; + return { attributes: null, references: null, error: e }; } }; @@ -304,7 +327,7 @@ type PartialSavedObject = Omit>, 'references'> & { references: SavedObjectReference[] | undefined; }; -export interface SavedObjectToItemOptions { +interface SavedObjectToItemOptions { /** * attributes to include in the output item */ @@ -313,6 +336,7 @@ export interface SavedObjectToItemOptions { * references to include in the output item */ allowedReferences?: string[]; + getTagNamesFromReferences?: (references: SavedObjectReference[]) => string[]; } export function savedObjectToItem( @@ -332,7 +356,7 @@ export function savedObjectToItem( | SavedObject | PartialSavedObject, partial: boolean /* partial arg is used to enforce the correct savedObject type */, - { allowedAttributes, allowedReferences }: SavedObjectToItemOptions = {} + { allowedAttributes, allowedReferences, getTagNamesFromReferences }: SavedObjectToItemOptions = {} ): SavedObjectToItemReturn { const { id, @@ -344,15 +368,18 @@ export function savedObjectToItem( attributes, error, namespaces, - references, + references = [], version, managed, } = savedObject; try { const attributesOut = allowedAttributes - ? pick(dashboardAttributesOut(attributes), allowedAttributes) - : dashboardAttributesOut(attributes); + ? pick( + dashboardAttributesOut(attributes, references, getTagNamesFromReferences), + allowedAttributes + ) + : dashboardAttributesOut(attributes, references, getTagNamesFromReferences); // if includeReferences is provided, only include references of those types const referencesOut = allowedReferences diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts index 0c7144569aba2..b53b61bc6f6d0 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts @@ -33,6 +33,7 @@ import { } from './cm_services'; import { CONTENT_ID } from '../../../common/content_management'; import { DashboardSavedObjectAttributes } from '../../dashboard_saved_object'; +import { SavedObjectToItemOptions } from './transform_utils'; export type DashboardOptions = TypeOf; @@ -81,12 +82,23 @@ export type SavedObjectToItemReturn = error: Error; }; -export type ItemAttrsToSavedObjectAttrsReturn = +export interface ItemAttrsToSavedObjectParams { + attributes: DashboardAttributes; + replaceTagReferencesByName?: ( + references: SavedObjectReference[], + tagNames: string[] + ) => SavedObjectReference[]; + incomingReferences?: SavedObjectReference[]; +} + +export type ItemAttrsToSavedObjectReturn = | { attributes: DashboardSavedObjectAttributes; + references: SavedObjectReference[]; error: null; } | { attributes: null; + references: null; error: Error; }; diff --git a/src/platform/plugins/shared/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts b/src/platform/plugins/shared/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts index 091ef21322671..54c7981fd0acf 100644 --- a/src/platform/plugins/shared/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts +++ b/src/platform/plugins/shared/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts @@ -12,7 +12,7 @@ import { SavedObject, SavedObjectMigrationFn } from '@kbn/core/server'; import { extractReferences, injectReferences } from '../../../common'; import type { DashboardSavedObjectTypeMigrationsDeps } from './dashboard_saved_object_migrations'; import type { DashboardSavedObjectAttributes } from '../schema'; -import { itemAttrsToSavedObjectAttrs, savedObjectToItem } from '../../content_management/latest'; +import { itemAttrsToSavedObject, savedObjectToItem } from '../../content_management/latest'; /** * In 7.8.0 we introduced dashboard drilldowns which are stored inside dashboard saved object as part of embeddable state @@ -60,7 +60,7 @@ export function createExtractPanelReferencesMigration( { embeddablePersistableStateService: deps.embeddable } ); - const { attributes, error: attributesError } = itemAttrsToSavedObjectAttrs(extractedAttributes); + const { attributes, error: attributesError } = itemAttrsToSavedObject(extractedAttributes); if (attributesError) throw attributesError; return { diff --git a/src/platform/plugins/shared/dashboard/server/plugin.ts b/src/platform/plugins/shared/dashboard/server/plugin.ts index 37183897c0a1c..ec98305aa42f2 100644 --- a/src/platform/plugins/shared/dashboard/server/plugin.ts +++ b/src/platform/plugins/shared/dashboard/server/plugin.ts @@ -17,6 +17,7 @@ import { ContentManagementServerSetup } from '@kbn/content-management-plugin/ser import { PluginInitializerContext, CoreSetup, CoreStart, Plugin, Logger } from '@kbn/core/server'; import { registerContentInsights } from '@kbn/content-management-content-insights-server'; +import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server'; import { initializeDashboardTelemetryTask, scheduleDashboardTelemetry, @@ -42,6 +43,7 @@ interface SetupDeps { interface StartDeps { taskManager: TaskManagerStartContract; usageCollection?: UsageCollectionStart; + savedObjectsTagging?: SavedObjectTaggingStart; } export class DashboardPlugin @@ -65,17 +67,20 @@ export class DashboardPlugin }) ); - const { contentClient } = plugins.contentManagement.register({ - id: CONTENT_ID, - storage: new DashboardStorage({ - throwOnResultValidationError: this.initializerContext.env.mode.dev, - logger: this.logger.get('storage'), - }), - version: { - latest: LATEST_VERSION, - }, + core.getStartServices().then(([_, { savedObjectsTagging }]) => { + const { contentClient } = plugins.contentManagement.register({ + id: CONTENT_ID, + storage: new DashboardStorage({ + throwOnResultValidationError: this.initializerContext.env.mode.dev, + logger: this.logger.get('storage'), + savedObjectsTagging, + }), + version: { + latest: LATEST_VERSION, + }, + }); + this.contentClient = contentClient; }); - this.contentClient = contentClient; plugins.contentManagement.favorites.registerFavoriteType('dashboard'); From 16e760e2ade8ff71bac593a8293f03f9521a0ce8 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Tue, 18 Mar 2025 16:27:25 -0400 Subject: [PATCH 02/14] Fixes --- .../v3/transform_utils.test.ts | 97 ++++++++++++++++--- .../content_management/v3/transform_utils.ts | 16 ++- .../server/content_management/v3/types.ts | 1 - .../migrate_extract_panel_references.ts | 4 +- 4 files changed, 92 insertions(+), 26 deletions(-) diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts index 11a660455318c..21c647afda595 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts @@ -8,17 +8,6 @@ */ import type { SavedObject } from '@kbn/core-saved-objects-api-server'; -import type { - DashboardSavedObjectAttributes, - SavedDashboardPanel, -} from '../../dashboard_saved_object'; -import type { DashboardAttributes, DashboardItem } from './types'; -import { - dashboardAttributesOut, - getResultV3ToV2, - itemAttrsToSavedObject, - savedObjectToItem, -} from './transform_utils'; import { DEFAULT_AUTO_APPLY_SELECTIONS, DEFAULT_CONTROL_CHAINING, @@ -30,6 +19,19 @@ import { ControlGroupChainingSystem, ControlWidth, } from '@kbn/controls-plugin/common'; + +import type { + DashboardSavedObjectAttributes, + SavedDashboardPanel, +} from '../../dashboard_saved_object'; +import type { DashboardAttributes, DashboardItem } from './types'; + +import { + dashboardAttributesOut, + getResultV3ToV2, + itemAttrsToSavedObject, + savedObjectToItem, +} from './transform_utils'; import { DEFAULT_DASHBOARD_OPTIONS } from '../../../common/content_management'; describe('dashboardAttributesOut', () => { @@ -205,8 +207,8 @@ describe('dashboardAttributesOut', () => { }); }); -describe('itemAttrsToSavedObjectAttrs', () => { - it('should transform item attributes to saved object attributes correctly', () => { +describe('itemAttrsToSavedObject', () => { + it('should transform item attributes to saved object correctly', () => { const input: DashboardAttributes = { controlGroupInput: { chainingSystem: 'NONE', @@ -250,6 +252,7 @@ describe('itemAttrsToSavedObjectAttrs', () => { version: '2', }, ], + tags: [], timeRestore: true, title: 'title', refreshInterval: { pause: true, value: 1000 }, @@ -257,7 +260,7 @@ describe('itemAttrsToSavedObjectAttrs', () => { timeTo: 'now', }; - const output = itemAttrsToSavedObjectAttrs(input); + const output = itemAttrsToSavedObject({ attributes: input }); expect(output).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -284,6 +287,7 @@ describe('itemAttrsToSavedObjectAttrs', () => { "title": "title", }, "error": null, + "references": Array [], } `); }); @@ -298,7 +302,7 @@ describe('itemAttrsToSavedObjectAttrs', () => { kibanaSavedObjectMeta: {}, }; - const output = itemAttrsToSavedObjectAttrs(input); + const output = itemAttrsToSavedObject({ attributes: input }); expect(output).toMatchInlineSnapshot(` Object { "attributes": Object { @@ -312,6 +316,7 @@ describe('itemAttrsToSavedObjectAttrs', () => { "title": "title", }, "error": null, + "references": Array [], } `); }); @@ -333,6 +338,13 @@ describe('savedObjectToItem', () => { attributes, }; }; + + const getTagNamesFromReferences = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + it('should convert saved object to item with all attributes', () => { const input = getSavedObjectForAttributes({ title: 'title', @@ -396,6 +408,61 @@ describe('savedObjectToItem', () => { }); }); + it('should pass tag references to getTagNamesFromReferences', () => { + getTagNamesFromReferences.mockReturnValue(['tag1', 'tag2']); + const tagReferences = [ + { + type: 'tag', + id: 'tag1', + name: 'tag-ref-tag1', + }, + { + type: 'tag', + id: 'tag2', + name: 'tag-ref-tag2', + }, + ]; + const input = { + ...getSavedObjectForAttributes({ + title: 'dashboard with tags', + description: 'I have some tags!', + timeRestore: true, + kibanaSavedObjectMeta: {}, + panelsJSON: JSON.stringify([]), + }), + references: [ + ...tagReferences, + { + type: 'index-pattern', + id: 'index-pattern1', + name: 'index-pattern-ref-index-pattern1', + }, + ], + }; + const { item, error } = savedObjectToItem(input, false, { getTagNamesFromReferences }); + expect(getTagNamesFromReferences).toHaveBeenCalledWith(tagReferences); + expect(error).toBeNull(); + expect(item).toEqual({ + ...commonSavedObject, + references: [ + ...tagReferences, + { + type: 'index-pattern', + id: 'index-pattern1', + name: 'index-pattern-ref-index-pattern1', + }, + ], + attributes: { + title: 'dashboard with tags', + description: 'I have some tags!', + panels: [], + timeRestore: true, + kibanaSavedObjectMeta: {}, + tags: ['tag1', 'tag2'], + }, + }); + }); + it('should handle missing optional attributes', () => { const input = getSavedObjectForAttributes({ title: 'title', diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts index 72cfcc96594a6..37e920550fb2a 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts @@ -150,7 +150,7 @@ function panelsOut(panelsJSON: string): DashboardAttributes['panels'] { export function dashboardAttributesOut( attributes: DashboardSavedObjectAttributes | Partial, - references: SavedObjectReference[], + references?: SavedObjectReference[], getTagNamesFromReferences?: (references: SavedObjectReference[]) => string[] ): DashboardAttributes | Partial { const { @@ -166,16 +166,16 @@ export function dashboardAttributesOut( title, version, } = attributes; - // try to maintain a consistent (alphabetical) order of keys // Inject any tag names from references into the attributes const tags = new Set(); - const tagRefs = references.filter(({ type }) => type === 'tag'); - if (getTagNamesFromReferences && tagRefs.length) { + const tagRefs = references?.filter(({ type }) => type === 'tag'); + if (getTagNamesFromReferences && tagRefs && tagRefs.length) { const tagNames = getTagNamesFromReferences(tagRefs); tagNames.forEach((tagName) => tags.add(tagName)); } + // try to maintain a consistent (alphabetical) order of keys return { ...(controlGroupInput && { controlGroupInput: controlGroupInputOut(controlGroupInput) }), ...(description && { description }), @@ -187,7 +187,7 @@ export function dashboardAttributesOut( ...(refreshInterval && { refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value }, }), - tags: Array.from(tags), + ...(tags.size && { tags: Array.from(tags) }), ...(timeFrom && { timeFrom }), timeRestore: timeRestore ?? false, ...(timeTo && { timeTo }), @@ -244,9 +244,7 @@ function kibanaSavedObjectMetaIn( return { searchSourceJSON: JSON.stringify(searchSource ?? {}) }; } -export const getResultV3ToV2 = async ( - result: DashboardGetOut -): Promise => { +export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2['GetOut'] => { const { meta, item } = result; const { attributes, ...rest } = item; const { @@ -368,7 +366,7 @@ export function savedObjectToItem( attributes, error, namespaces, - references = [], + references, version, managed, } = savedObject; diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts index b53b61bc6f6d0..82934ef04ca14 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts @@ -33,7 +33,6 @@ import { } from './cm_services'; import { CONTENT_ID } from '../../../common/content_management'; import { DashboardSavedObjectAttributes } from '../../dashboard_saved_object'; -import { SavedObjectToItemOptions } from './transform_utils'; export type DashboardOptions = TypeOf; diff --git a/src/platform/plugins/shared/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts b/src/platform/plugins/shared/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts index 54c7981fd0acf..4654e763a7460 100644 --- a/src/platform/plugins/shared/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts +++ b/src/platform/plugins/shared/dashboard/server/dashboard_saved_object/migrations/migrate_extract_panel_references.ts @@ -60,7 +60,9 @@ export function createExtractPanelReferencesMigration( { embeddablePersistableStateService: deps.embeddable } ); - const { attributes, error: attributesError } = itemAttrsToSavedObject(extractedAttributes); + const { attributes, error: attributesError } = itemAttrsToSavedObject({ + attributes: extractedAttributes, + }); if (attributesError) throw attributesError; return { From 4ffe843611f5f5d51c9418ade22880e2aabc1570 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:38:23 +0000 Subject: [PATCH 03/14] [CI] Auto-commit changed files from 'node scripts/notice' --- src/platform/plugins/shared/dashboard/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/dashboard/tsconfig.json b/src/platform/plugins/shared/dashboard/tsconfig.json index 7f2c5c3a85b61..f97d1d3aeeb0d 100644 --- a/src/platform/plugins/shared/dashboard/tsconfig.json +++ b/src/platform/plugins/shared/dashboard/tsconfig.json @@ -83,7 +83,8 @@ "@kbn/core-rendering-browser", "@kbn/grid-layout", "@kbn/ui-actions-browser", - "@kbn/esql-types" + "@kbn/esql-types", + "@kbn/saved-objects-tagging-plugin" ], "exclude": ["target/**/*"] } From 89b1312e862462b6c16428f2ee8a50a9fc9233e1 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 18 Mar 2025 20:43:53 +0000 Subject: [PATCH 04/14] [CI] Auto-commit changed files from 'node scripts/capture_oas_snapshot --include-path /api/status --include-path /api/alerting/rule/ --include-path /api/alerting/rules --include-path /api/actions --include-path /api/security/role --include-path /api/spaces --include-path /api/streams --include-path /api/fleet --include-path /api/dashboards --update' --- oas_docs/bundle.json | 42 +++++++++++++++++++++++++++++++++ oas_docs/bundle.serverless.json | 42 +++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index c54bdff93cc58..c4f8b9e9e0cc8 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -5538,6 +5538,13 @@ "description": "A short description.", "type": "string" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeRestore": { "default": false, "description": "Whether to restore time upon viewing this dashboard", @@ -6184,6 +6191,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" @@ -6838,6 +6852,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" @@ -7376,6 +7397,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" @@ -8002,6 +8030,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" @@ -8534,6 +8569,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" diff --git a/oas_docs/bundle.serverless.json b/oas_docs/bundle.serverless.json index 4f38eb68de2ed..8a5c3a6f75f5b 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -5538,6 +5538,13 @@ "description": "A short description.", "type": "string" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeRestore": { "default": false, "description": "Whether to restore time upon viewing this dashboard", @@ -6184,6 +6191,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" @@ -6838,6 +6852,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" @@ -7376,6 +7397,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" @@ -8002,6 +8030,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" @@ -8534,6 +8569,13 @@ ], "type": "object" }, + "tags": { + "items": { + "description": "An array of tags applied to this dashboard", + "type": "string" + }, + "type": "array" + }, "timeFrom": { "description": "An ISO string indicating when to restore time from", "type": "string" From bac5e569774398986a36ae63324e0ada13f406bb Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 19 Mar 2025 12:28:29 +0000 Subject: [PATCH 05/14] [CI] Auto-commit changed files from 'make api-docs' --- oas_docs/output/kibana.serverless.yaml | 30 ++++++++++++++++++++++++++ oas_docs/output/kibana.yaml | 30 ++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/oas_docs/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index ae88859e7978e..7a79c51ae7a89 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -5283,6 +5283,11 @@ paths: default: '' description: A short description. type: string + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeRestore: default: false description: Whether to restore time upon viewing this dashboard @@ -5745,6 +5750,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string @@ -6211,6 +6221,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string @@ -6599,6 +6614,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string @@ -7045,6 +7065,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string @@ -7429,6 +7454,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string diff --git a/oas_docs/output/kibana.yaml b/oas_docs/output/kibana.yaml index 4e34b7ac8e34b..d57eec1b36a44 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -6763,6 +6763,11 @@ paths: default: '' description: A short description. type: string + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeRestore: default: false description: Whether to restore time upon viewing this dashboard @@ -7223,6 +7228,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string @@ -7688,6 +7698,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string @@ -8076,6 +8091,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string @@ -8521,6 +8541,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string @@ -8905,6 +8930,11 @@ paths: required: - pause - value + tags: + items: + description: An array of tags applied to this dashboard + type: string + type: array timeFrom: description: An ISO string indicating when to restore time from type: string From dac9d7578e9034421bffccec8e78051bf25a5c94 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Wed, 19 Mar 2025 15:53:32 -0400 Subject: [PATCH 06/14] Add api integration tests with tags --- .../content_management/dashboard_storage.ts | 23 ++- .../apis/dashboards/create_dashboard/index.ts | 6 + .../apis/dashboards/create_dashboard/main.ts | 148 ++++++++++++++++++ .../apis/dashboards/get_dashboard/index.ts | 6 + .../apis/dashboards/get_dashboard/main.ts | 27 ++++ .../apis/dashboards/update_dashboard/index.ts | 6 + .../apis/dashboards/update_dashboard/main.ts | 125 ++++++++++++--- .../kbn_archiver/saved_objects/tags.json | 91 +++++++++++ 8 files changed, 403 insertions(+), 29 deletions(-) create mode 100644 src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json diff --git a/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts b/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts index 9525abd8c7dfd..08bf6883ec432 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts @@ -40,7 +40,7 @@ const searchArgsToSOFindOptions = ( return { type: DASHBOARD_SAVED_OBJECT_TYPE, searchFields: options?.onlyTitle ? ['title'] : ['title^3', 'description'], - // fields: options?.fields, + fields: options?.fields, search: query.text, perPage: query.limit, page: query.cursor ? +query.cursor : undefined, @@ -91,11 +91,26 @@ export class DashboardStorage { newTagNames: string[], allTags: Tag[] ) => { - const newTagsIds = newTagNames.flatMap( + const combinedTagNames = this.getUniqueTagNames(references, newTagNames, allTags); + const newTagIds = this.convertTagNamesToIds(combinedTagNames, allTags); + return this.savedObjectsTagging?.replaceTagReferences(references, newTagIds) ?? references; + }; + + private getUniqueTagNames( + references: SavedObjectReference[], + newTagNames: string[], + allTags: Tag[] + ): string[] { + const referenceTagNames = this.getTagNamesFromReferences(references, allTags); + return Array.from(new Set([...referenceTagNames, ...newTagNames])); + } + + private convertTagNamesToIds(tagNames: string[], allTags: Tag[]): string[] { + // convertTagNameToId could return undefined, so flatMap is used to filter out undefined values + return tagNames.flatMap( (tagName) => this.savedObjectsTagging?.convertTagNameToId(tagName, allTags) ?? [] ); - return this.savedObjectsTagging?.replaceTagReferences(references, newTagsIds) ?? references; - }; + } async get(ctx: StorageContext, id: string): Promise { const transforms = ctx.utils.getTransforms(cmServicesDefinition); diff --git a/src/platform/test/api_integration/apis/dashboards/create_dashboard/index.ts b/src/platform/test/api_integration/apis/dashboards/create_dashboard/index.ts index 5a32c8ca968a4..8b78f9659fe13 100644 --- a/src/platform/test/api_integration/apis/dashboards/create_dashboard/index.ts +++ b/src/platform/test/api_integration/apis/dashboards/create_dashboard/index.ts @@ -16,6 +16,9 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await kibanaServer.importExport.load( 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.load( + 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json' + ); }); after(async () => { @@ -23,6 +26,9 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await kibanaServer.importExport.unload( 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.unload( + 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json' + ); }); loadTestFile(require.resolve('./main')); loadTestFile(require.resolve('./validation')); diff --git a/src/platform/test/api_integration/apis/dashboards/create_dashboard/main.ts b/src/platform/test/api_integration/apis/dashboards/create_dashboard/main.ts index 3b8b71f827deb..1820b38ec0f8d 100644 --- a/src/platform/test/api_integration/apis/dashboards/create_dashboard/main.ts +++ b/src/platform/test/api_integration/apis/dashboards/create_dashboard/main.ts @@ -8,6 +8,7 @@ */ import expect from '@kbn/expect'; +import { type SavedObjectReference } from '@kbn/core/server'; import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; import { DEFAULT_IGNORE_PARENT_SETTINGS } from '@kbn/controls-plugin/common'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -171,6 +172,153 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.item.attributes.panels).to.be.an('array'); }); + describe('create a dashboard with tags', () => { + it('with tags specified as an array of names', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + tags: ['foo'], + }, + references: [ + { + name: 'bizz:panel_bizz', + type: 'visualization', + id: 'my-saved-object', + }, + ], + }); + + expect(response.status).to.be(200); + expect(response.body.item.attributes.tags).to.contain('foo'); + expect(response.body.item.attributes.tags).to.have.length(1); + // adds tag reference to existing references + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('tag-1'); + expect(referenceIds).to.contain('my-saved-object'); + expect(response.body.item.references).to.have.length(2); + }); + + it('ignores tags if a saved object matching the a tag is not found', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + tags: ['foo', 'not-found-tag'], + }, + }); + expect(response.status).to.be(200); + expect(response.body.item.attributes.tags).to.contain('foo'); + expect(response.body.item.attributes.tags).to.have.length(1); + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('tag-1'); + expect(response.body.item.references).to.have.length(1); + }); + + it('with tags specified as references', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + }, + references: [ + { + type: 'tag', + id: 'tag-3', + name: 'tag-ref-tag-3', + }, + ], + }); + expect(response.status).to.be(200); + expect(response.body.item.attributes.tags).to.contain('buzz'); + expect(response.body.item.attributes.tags).to.have.length(1); + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('tag-3'); + expect(response.body.item.references).to.have.length(1); + }); + + it('with tags specified using both tags array and references', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + tags: ['foo'], + }, + references: [ + { + type: 'tag', + id: 'tag-2', + name: 'tag-ref-tag-2', + }, + ], + }); + expect(response.status).to.be(200); + expect(response.body.item.attributes.tags).to.contain('foo'); + expect(response.body.item.attributes.tags).to.contain('bar'); + expect(response.body.item.attributes.tags).to.have.length(2); + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('tag-1'); + expect(referenceIds).to.contain('tag-2'); + expect(response.body.item.references).to.have.length(2); + }); + + it('with the same tag specified as a reference and a tag name', async () => { + const title = `foo-${Date.now()}-${Math.random()}`; + const response = await supertest + .post(PUBLIC_API_PATH) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + attributes: { + title, + tags: ['foo', 'bizz'], + }, + references: [ + { + type: 'tag', + id: 'tag-1', + name: 'tag-ref-tag-1', + }, + ], + }); + expect(response.status).to.be(200); + expect(response.body.item.attributes.tags).to.contain('foo'); + expect(response.body.item.attributes.tags).to.contain('bizz'); + expect(response.body.item.attributes.tags).to.have.length(2); + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('tag-1'); + expect(referenceIds).to.contain('tag-3'); + expect(response.body.item.references).to.have.length(2); + }); + }); + // TODO Maybe move this test to x-pack/test/api_integration/dashboards it('can create a dashboard in a defined space', async () => { const title = `foo-${Date.now()}-${Math.random()}`; diff --git a/src/platform/test/api_integration/apis/dashboards/get_dashboard/index.ts b/src/platform/test/api_integration/apis/dashboards/get_dashboard/index.ts index c0dc629af1f8e..b66375a3cbd37 100644 --- a/src/platform/test/api_integration/apis/dashboards/get_dashboard/index.ts +++ b/src/platform/test/api_integration/apis/dashboards/get_dashboard/index.ts @@ -16,6 +16,9 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await kibanaServer.importExport.load( 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.load( + 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json' + ); }); after(async () => { @@ -23,6 +26,9 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await kibanaServer.importExport.unload( 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.unload( + 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json' + ); }); loadTestFile(require.resolve('./main')); }); diff --git a/src/platform/test/api_integration/apis/dashboards/get_dashboard/main.ts b/src/platform/test/api_integration/apis/dashboards/get_dashboard/main.ts index b6585c0c4f48a..0470b3db46f96 100644 --- a/src/platform/test/api_integration/apis/dashboards/get_dashboard/main.ts +++ b/src/platform/test/api_integration/apis/dashboards/get_dashboard/main.ts @@ -8,6 +8,7 @@ */ import expect from '@kbn/expect'; +import { type SavedObjectReference } from '@kbn/core/server'; import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; import { FtrProviderContext } from '../../../ftr_provider_context'; @@ -30,5 +31,31 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.item.attributes.options).to.not.have.keys(['darkTheme']); expect(response.body.item.attributes.refreshInterval).to.not.have.keys(['display']); }); + + it('should return 404 with a non-existing dashboard', async () => { + const response = await supertest + .get(`${PUBLIC_API_PATH}/does-not-exist`) + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send(); + + expect(response.status).to.be(404); + }); + + it('should inject tag names into attributes', async () => { + const response = await supertest + .get(`${PUBLIC_API_PATH}/8d66658a-f5b7-4482-84dc-f41d317473b8`) + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send(); + + expect(response.status).to.be(200); + + expect(response.body.item.attributes.tags).to.contain('bar'); + expect(response.body.item.attributes.tags).to.contain('buzz'); + expect(response.body.item.attributes.tags).to.have.length(2); + const referenceIds = response.body.item.references.map((ref: SavedObjectReference) => ref.id); + expect(referenceIds).to.contain('tag-2'); + expect(referenceIds).to.contain('tag-3'); + expect(response.body.item.references).to.have.length(2); + }); }); } diff --git a/src/platform/test/api_integration/apis/dashboards/update_dashboard/index.ts b/src/platform/test/api_integration/apis/dashboards/update_dashboard/index.ts index 4fe50df85b090..698c3294b13f0 100644 --- a/src/platform/test/api_integration/apis/dashboards/update_dashboard/index.ts +++ b/src/platform/test/api_integration/apis/dashboards/update_dashboard/index.ts @@ -16,12 +16,18 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { await kibanaServer.importExport.load( 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.load( + 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json' + ); }); after(async () => { await kibanaServer.importExport.unload( 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/basic.json' ); + await kibanaServer.importExport.unload( + 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json' + ); }); loadTestFile(require.resolve('./main')); loadTestFile(require.resolve('./validation')); diff --git a/src/platform/test/api_integration/apis/dashboards/update_dashboard/main.ts b/src/platform/test/api_integration/apis/dashboards/update_dashboard/main.ts index 18a7d5ca2d3fe..d52e4039def3f 100644 --- a/src/platform/test/api_integration/apis/dashboards/update_dashboard/main.ts +++ b/src/platform/test/api_integration/apis/dashboards/update_dashboard/main.ts @@ -8,9 +8,36 @@ */ import expect from '@kbn/expect'; +import { type SavedObjectReference } from '@kbn/core/server'; import { PUBLIC_API_PATH } from '@kbn/dashboard-plugin/server'; import { FtrProviderContext } from '../../../ftr_provider_context'; +const updatedDashboard = { + attributes: { + title: 'Refresh Requests (Updated)', + options: { useMargins: false }, + panels: [ + { + type: 'visualization', + gridData: { x: 0, y: 0, w: 48, h: 60, i: '1' }, + panelIndex: '1', + panelRefName: 'panel_1', + version: '7.3.0', + }, + ], + timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', + timeRestore: true, + timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', + }, + references: [ + { + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + name: '1:panel_1', + type: 'visualization', + }, + ], +}; + export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('main', () => { @@ -19,31 +46,7 @@ export default function ({ getService }: FtrProviderContext) { .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) .set('kbn-xsrf', 'true') .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') - .send({ - attributes: { - title: 'Refresh Requests (Updated)', - options: { useMargins: false }, - panels: [ - { - type: 'visualization', - gridData: { x: 0, y: 0, w: 48, h: 60, i: '1' }, - panelIndex: '1', - panelRefName: 'panel_1', - version: '7.3.0', - }, - ], - timeFrom: 'Wed Sep 16 2015 22:52:17 GMT-0700', - timeRestore: true, - timeTo: 'Fri Sep 18 2015 12:24:38 GMT-0700', - }, - references: [ - { - id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - name: '1:panel_1', - type: 'visualization', - }, - ], - }); + .send(updatedDashboard); expect(response.status).to.be(201); @@ -70,5 +73,77 @@ export default function ({ getService }: FtrProviderContext) { message: 'A dashboard with saved object ID not-an-id was not found.', }); }); + + describe('update a dashboard with tags', () => { + it('adds a tag to the dashboard', async () => { + const response = await supertest + .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + ...updatedDashboard, + attributes: { + ...updatedDashboard.attributes, + tags: ['bar'], + }, + }); + + expect(response.status).to.be(201); + expect(response.body.item.attributes.tags).to.contain('bar'); + expect(response.body.item.attributes.tags).to.have.length(1); + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('tag-2'); + expect(referenceIds).to.contain('dd7caf20-9efd-11e7-acb3-3dab96693fab'); + expect(response.body.item.references).to.have.length(2); + }); + + it('replaces the tags on the dashboard', async () => { + const response = await supertest + .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + ...updatedDashboard, + attributes: { + ...updatedDashboard.attributes, + tags: ['foo'], + }, + }); + + expect(response.status).to.be(201); + expect(response.body.item.attributes.tags).to.contain('foo'); + expect(response.body.item.attributes.tags).to.have.length(1); + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('tag-1'); + expect(referenceIds).to.contain('dd7caf20-9efd-11e7-acb3-3dab96693fab'); + expect(response.body.item.references).to.have.length(2); + }); + + it('empty tags array removes all tags', async () => { + const response = await supertest + .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + ...updatedDashboard, + attributes: { + ...updatedDashboard.attributes, + tags: [], + }, + }); + + expect(response.status).to.be(201); + expect(response.body.item.attributes).not.to.have.property('tags'); + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('dd7caf20-9efd-11e7-acb3-3dab96693fab'); + expect(response.body.item.references).to.have.length(1); + }); + }); }); } diff --git a/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json b/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json new file mode 100644 index 0000000000000..3742bc59ced44 --- /dev/null +++ b/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json @@ -0,0 +1,91 @@ +{ + "attributes": { + "color": "#123456", + "description": "Another awesome tag", + "name": "bar" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-19T18:17:49.747Z", + "id": "tag-2", + "managed": false, + "references": [], + "type": "tag", + "typeMigrationVersion": "8.0.0", + "updated_at": "2025-03-19T18:17:49.747Z", + "version": "WzYxMywxXQ==" +} + +{ + "attributes": { + "color": "#000000", + "description": "Last but not least", + "name": "buzz" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-19T18:17:49.747Z", + "id": "tag-3", + "managed": false, + "references": [], + "type": "tag", + "typeMigrationVersion": "8.0.0", + "updated_at": "2025-03-19T18:17:49.747Z", + "version": "WzYxNCwxXQ==" +} + +{ + "attributes": { + "controlGroupInput": { + "chainingSystem": "HIERARCHICAL", + "controlStyle": "oneLine", + "ignoreParentSettingsJSON": "{\"ignoreFilters\":false,\"ignoreQuery\":false,\"ignoreTimerange\":false,\"ignoreValidations\":false}", + "panelsJSON": "{}", + "showApplySelections": false + }, + "description": "I have some tags!", + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"kuery\"}}" + }, + "optionsJSON": "{\"useMargins\":true,\"syncColors\":false,\"syncCursor\":true,\"syncTooltips\":false,\"hidePanelTitles\":false}", + "panelsJSON": "[{\"type\":\"visualization\",\"embeddableConfig\":{\"enhancements\":{\"dynamicActions\":{\"events\":[]}},\"savedVis\":{\"id\":\"\",\"title\":\"\",\"description\":\"\",\"type\":\"markdown\",\"params\":{\"fontSize\":12,\"openLinksInNewTab\":false,\"markdown\":\"# Hello world\"},\"uiState\":{},\"data\":{\"aggs\":[],\"searchSource\":{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}}}},\"panelIndex\":\"b9e32168-d39e-4289-ab07-30cfda9e00d4\",\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":15,\"i\":\"b9e32168-d39e-4289-ab07-30cfda9e00d4\"}}]", + "timeRestore": false, + "title": "tagged dashboard", + "version": 3 + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-19T18:18:29.671Z", + "id": "8d66658a-f5b7-4482-84dc-f41d317473b8", + "managed": false, + "references": [ + { + "id": "tag-2", + "name": "tag-ref-tag-2", + "type": "tag" + }, + { + "id": "tag-3", + "name": "tag-ref-tag-3", + "type": "tag" + } + ], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-19T18:18:58.406Z", + "version": "WzQ1MTQxLDFd" +} + +{ + "attributes": { + "color": "#FF00FF", + "description": "My first tag!", + "name": "foo" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-19T18:17:49.747Z", + "id": "tag-1", + "managed": false, + "references": [], + "type": "tag", + "typeMigrationVersion": "8.0.0", + "updated_at": "2025-03-19T18:17:49.747Z", + "version": "WzYxMiwxXQ==" +} From 3095adb5afdb27594ec9d2219067f3ec495f7aa0 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Thu, 20 Mar 2025 14:30:46 -0400 Subject: [PATCH 07/14] Create tag if saved object matching tag name not found --- .../content_management/dashboard_storage.ts | 113 +- .../content_management/v3/transform_utils.ts | 14 +- .../server/content_management/v3/types.ts | 7 +- .../apis/dashboards/create_dashboard/main.ts | 12 +- .../apis/dashboards/list_dashboards/index.ts | 30 +- .../apis/dashboards/update_dashboard/main.ts | 30 + .../saved_objects/many-dashboards.json | 1999 +++++++++++++++++ 7 files changed, 2138 insertions(+), 67 deletions(-) create mode 100644 src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json diff --git a/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts b/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts index 08bf6883ec432..7c225cd2eae27 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts @@ -16,7 +16,7 @@ import { CreateResult, DeleteResult, SearchQuery } from '@kbn/content-management import { StorageContext } from '@kbn/content-management-plugin/server'; import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server'; import type { SavedObjectReference } from '@kbn/core/server'; -import type { Tag } from '@kbn/saved-objects-tagging-oss-plugin/common'; +import type { ITagsClient, Tag } from '@kbn/saved-objects-tagging-oss-plugin/common'; import { DASHBOARD_SAVED_OBJECT_TYPE } from '../dashboard_saved_object'; import { cmServicesDefinition } from './cm_services'; import { DashboardSavedObjectAttributes } from '../dashboard_saved_object'; @@ -33,6 +33,10 @@ import type { DashboardSearchOptions, } from './latest'; +const getRandomColor = (): string => { + return '#' + String(Math.floor(Math.random() * 16777215).toString(16)).padStart(6, '0'); +}; + const searchArgsToSOFindOptions = ( query: SearchQuery, options: DashboardSearchOptions @@ -78,40 +82,97 @@ export class DashboardStorage { private savedObjectsTagging?: SavedObjectTaggingStart; private throwOnResultValidationError: boolean; - private getTagNamesFromReferences = (references: SavedObjectReference[], allTags: Tag[]) => { - return this.savedObjectsTagging - ? this.savedObjectsTagging - .getTagsFromReferences(references, allTags) - .tags.map((tag) => tag.name) - : []; - }; + private getTagNamesFromReferences(references: SavedObjectReference[], allTags: Tag[]) { + return Array.from( + new Set( + this.savedObjectsTagging + ? this.savedObjectsTagging + .getTagsFromReferences(references, allTags) + .tags.map((tag) => tag.name) + : [] + ) + ); + } - private replaceTagReferencesByName = ( + private getUniqueTagNames( references: SavedObjectReference[], newTagNames: string[], allTags: Tag[] - ) => { - const combinedTagNames = this.getUniqueTagNames(references, newTagNames, allTags); - const newTagIds = this.convertTagNamesToIds(combinedTagNames, allTags); - return this.savedObjectsTagging?.replaceTagReferences(references, newTagIds) ?? references; - }; + ) { + const referenceTagNames = this.getTagNamesFromReferences(references, allTags); + return new Set([...referenceTagNames, ...newTagNames]); + } - private getUniqueTagNames( + private async replaceTagReferencesByName( references: SavedObjectReference[], newTagNames: string[], - allTags: Tag[] - ): string[] { - const referenceTagNames = this.getTagNamesFromReferences(references, allTags); - return Array.from(new Set([...referenceTagNames, ...newTagNames])); + allTags: Tag[], + tagsClient?: ITagsClient + ) { + const combinedTagNames = this.getUniqueTagNames(references, newTagNames, allTags); + const newTagIds = await this.convertTagNamesToIds(combinedTagNames, allTags, tagsClient); + return this.savedObjectsTagging?.replaceTagReferences(references, newTagIds) ?? references; } - private convertTagNamesToIds(tagNames: string[], allTags: Tag[]): string[] { - // convertTagNameToId could return undefined, so flatMap is used to filter out undefined values - return tagNames.flatMap( + private async convertTagNamesToIds( + tagNames: Set, + allTags: Tag[], + tagsClient?: ITagsClient + ): Promise { + const combinedTagNames = await this.createTagsIfNeeded(tagNames, allTags, tagsClient); + + return Array.from(combinedTagNames).flatMap( (tagName) => this.savedObjectsTagging?.convertTagNameToId(tagName, allTags) ?? [] ); } + private async createTagsIfNeeded( + tagNames: Set, + allTags: Tag[], + tagsClient?: ITagsClient + ) { + const tagsToCreate = Array.from(tagNames).filter( + (tagName) => !allTags.some((tag) => tag.name === tagName) + ); + const tagCreationResults = await Promise.allSettled( + tagsToCreate.flatMap( + (tagName) => + tagsClient?.create({ + name: tagName, + description: '', + color: getRandomColor(), + }) ?? [] + ) + ); + + for (const result of tagCreationResults) { + if (result.status === 'rejected') { + this.logger.error(`Error creating tag: ${result.reason}`); + } else { + this.logger.info(`Tag created: ${result.value.name}`); + } + } + + const createdTags = tagCreationResults + .filter((result): result is PromiseFulfilledResult => result.status === 'fulfilled') + .map((result) => result.value); + + // Remove tags that were not created + const invalidTagNames = tagsToCreate.filter( + (tagName) => !createdTags.some((tag) => tag.name === tagName) + ); + invalidTagNames.forEach((tagName) => tagNames.delete(tagName)); + + // Add newly created tags to allTags + allTags.push(...createdTags); + + const combinedTagNames = new Set([ + ...tagNames, + ...createdTags.map((createdTag) => createdTag.name), + ]); + return combinedTagNames; + } + async get(ctx: StorageContext, id: string): Promise { const transforms = ctx.utils.getTransforms(cmServicesDefinition); const soClient = await savedObjectClientFromRequest(ctx); @@ -197,10 +258,10 @@ export class DashboardStorage { attributes: soAttributes, references: soReferences, error: attributesError, - } = itemAttrsToSavedObject({ + } = await itemAttrsToSavedObject({ attributes: dataToLatest, replaceTagReferencesByName: (references: SavedObjectReference[], newTagNames: string[]) => - this.replaceTagReferencesByName(references, newTagNames, allTags), + this.replaceTagReferencesByName(references, newTagNames, allTags, tagsClient), incomingReferences: options.references, }); if (attributesError) { @@ -279,10 +340,10 @@ export class DashboardStorage { attributes: soAttributes, references: soReferences, error: attributesError, - } = itemAttrsToSavedObject({ + } = await itemAttrsToSavedObject({ attributes: dataToLatest, replaceTagReferencesByName: (references: SavedObjectReference[], newTagNames: string[]) => - this.replaceTagReferencesByName(references, newTagNames, allTags), + this.replaceTagReferencesByName(references, newTagNames, allTags, tagsClient), incomingReferences: options.references, }); if (attributesError) { diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts index 37e920550fb2a..46c2eaf333c39 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts @@ -168,11 +168,9 @@ export function dashboardAttributesOut( } = attributes; // Inject any tag names from references into the attributes - const tags = new Set(); - const tagRefs = references?.filter(({ type }) => type === 'tag'); - if (getTagNamesFromReferences && tagRefs && tagRefs.length) { - const tagNames = getTagNamesFromReferences(tagRefs); - tagNames.forEach((tagName) => tags.add(tagName)); + let tags: string[] | undefined; + if (getTagNamesFromReferences && references && references.length) { + tags = getTagNamesFromReferences(references); } // try to maintain a consistent (alphabetical) order of keys @@ -187,7 +185,7 @@ export function dashboardAttributesOut( ...(refreshInterval && { refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value }, }), - ...(tags.size && { tags: Array.from(tags) }), + ...(tags && { tags }), ...(timeFrom && { timeFrom }), timeRestore: timeRestore ?? false, ...(timeTo && { timeTo }), @@ -287,7 +285,7 @@ export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2[' }; }; -export const itemAttrsToSavedObject = ({ +export const itemAttrsToSavedObject = async ({ attributes, replaceTagReferencesByName, incomingReferences = [], @@ -313,7 +311,7 @@ export const itemAttrsToSavedObject = ({ // Tags can be specified as an attribute or in the incomingReferences. const soReferences = replaceTagReferencesByName && tags && tags.length - ? replaceTagReferencesByName(incomingReferences, tags) + ? await replaceTagReferencesByName(incomingReferences, tags) : incomingReferences; return { attributes: soAttributes, references: soReferences, error: null }; } catch (e) { diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts index 82934ef04ca14..5248498872fab 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts @@ -86,11 +86,11 @@ export interface ItemAttrsToSavedObjectParams { replaceTagReferencesByName?: ( references: SavedObjectReference[], tagNames: string[] - ) => SavedObjectReference[]; + ) => Promise; incomingReferences?: SavedObjectReference[]; } -export type ItemAttrsToSavedObjectReturn = +export type ItemAttrsToSavedObjectReturn = Promise< | { attributes: DashboardSavedObjectAttributes; references: SavedObjectReference[]; @@ -100,4 +100,5 @@ export type ItemAttrsToSavedObjectReturn = attributes: null; references: null; error: Error; - }; + } +>; diff --git a/src/platform/test/api_integration/apis/dashboards/create_dashboard/main.ts b/src/platform/test/api_integration/apis/dashboards/create_dashboard/main.ts index 1820b38ec0f8d..54c3fbafad401 100644 --- a/src/platform/test/api_integration/apis/dashboards/create_dashboard/main.ts +++ b/src/platform/test/api_integration/apis/dashboards/create_dashboard/main.ts @@ -206,7 +206,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response.body.item.references).to.have.length(2); }); - it('ignores tags if a saved object matching the a tag is not found', async () => { + it('creates tags if a saved object matching a tag name is not found', async () => { const title = `foo-${Date.now()}-${Math.random()}`; const response = await supertest .post(PUBLIC_API_PATH) @@ -219,13 +219,13 @@ export default function ({ getService }: FtrProviderContext) { }, }); expect(response.status).to.be(200); - expect(response.body.item.attributes.tags).to.contain('foo'); - expect(response.body.item.attributes.tags).to.have.length(1); + expect(response.body.item.attributes.tags).to.contain('foo', 'not-found-tag'); + expect(response.body.item.attributes.tags).to.have.length(2); const referenceIds = response.body.item.references.map( (ref: SavedObjectReference) => ref.id ); expect(referenceIds).to.contain('tag-1'); - expect(response.body.item.references).to.have.length(1); + expect(response.body.item.references).to.have.length(2); }); it('with tags specified as references', async () => { @@ -296,7 +296,7 @@ export default function ({ getService }: FtrProviderContext) { .send({ attributes: { title, - tags: ['foo', 'bizz'], + tags: ['foo', 'buzz'], }, references: [ { @@ -308,7 +308,7 @@ export default function ({ getService }: FtrProviderContext) { }); expect(response.status).to.be(200); expect(response.body.item.attributes.tags).to.contain('foo'); - expect(response.body.item.attributes.tags).to.contain('bizz'); + expect(response.body.item.attributes.tags).to.contain('buzz'); expect(response.body.item.attributes.tags).to.have.length(2); const referenceIds = response.body.item.references.map( (ref: SavedObjectReference) => ref.id diff --git a/src/platform/test/api_integration/apis/dashboards/list_dashboards/index.ts b/src/platform/test/api_integration/apis/dashboards/list_dashboards/index.ts index 10f77ad3fee5a..c2b250c6179c2 100644 --- a/src/platform/test/api_integration/apis/dashboards/list_dashboards/index.ts +++ b/src/platform/test/api_integration/apis/dashboards/list_dashboards/index.ts @@ -11,36 +11,18 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); - const supertest = getService('supertest'); describe('dashboards - list', () => { - const createManyDashboards = async (count: number) => { - const fileChunks: string[] = []; - for (let i = 0; i < count; i++) { - const id = `test-dashboard-${i}`; - fileChunks.push( - JSON.stringify({ - type: 'dashboard', - id, - attributes: { - title: `My dashboard (${i})`, - kibanaSavedObjectMeta: { searchSourceJSON: '{}' }, - }, - references: [], - }) - ); - } - - await supertest - .post(`/api/saved_objects/_import`) - .attach('file', Buffer.from(fileChunks.join('\n'), 'utf8'), 'export.ndjson') - .expect(200); - }; before(async () => { - await createManyDashboards(100); + await kibanaServer.importExport.load( + 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json' + ); }); after(async () => { await kibanaServer.savedObjects.cleanStandardList(); + await kibanaServer.importExport.unload( + 'src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json' + ); }); loadTestFile(require.resolve('./main')); }); diff --git a/src/platform/test/api_integration/apis/dashboards/update_dashboard/main.ts b/src/platform/test/api_integration/apis/dashboards/update_dashboard/main.ts index d52e4039def3f..1d8fc895e131d 100644 --- a/src/platform/test/api_integration/apis/dashboards/update_dashboard/main.ts +++ b/src/platform/test/api_integration/apis/dashboards/update_dashboard/main.ts @@ -144,6 +144,36 @@ export default function ({ getService }: FtrProviderContext) { expect(referenceIds).to.contain('dd7caf20-9efd-11e7-acb3-3dab96693fab'); expect(response.body.item.references).to.have.length(1); }); + + it('creates tag if a saved object matching a tag name is not found', async () => { + const randomTagName = `tag-${Math.random() * 1000}`; + const response = await supertest + .put(`${PUBLIC_API_PATH}/be3733a0-9efe-11e7-acb3-3dab96693fab`) + .set('kbn-xsrf', 'true') + .set('ELASTIC_HTTP_VERSION_HEADER', '2023-10-31') + .send({ + ...updatedDashboard, + attributes: { + ...updatedDashboard.attributes, + tags: ['foo', 'bar', 'buzz', randomTagName], + }, + }); + + expect(response.status).to.be(201); + expect(response.body.item.attributes.tags).to.contain('foo'); + expect(response.body.item.attributes.tags).to.contain('bar'); + expect(response.body.item.attributes.tags).to.contain('buzz'); + expect(response.body.item.attributes.tags).to.contain(randomTagName); + expect(response.body.item.attributes.tags).to.have.length(4); + const referenceIds = response.body.item.references.map( + (ref: SavedObjectReference) => ref.id + ); + expect(referenceIds).to.contain('tag-1'); + expect(referenceIds).to.contain('tag-2'); + expect(referenceIds).to.contain('tag-3'); + expect(referenceIds).to.contain('dd7caf20-9efd-11e7-acb3-3dab96693fab'); + expect(response.body.item.references).to.have.length(5); + }); }); }); } diff --git a/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json b/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json new file mode 100644 index 0000000000000..b71c8317778d1 --- /dev/null +++ b/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json @@ -0,0 +1,1999 @@ +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (0)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-0", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIwNSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (1)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-1", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIwNiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (10)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-10", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxNSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (11)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-11", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxNiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (12)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-12", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxNywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (13)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-13", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxOCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (14)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-14", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxOSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (15)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-15", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyMCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (16)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-16", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyMSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (17)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-17", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyMiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (18)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-18", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyMywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (19)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-19", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyNCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (2)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-2", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIwNywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (20)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-20", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyNSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (21)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-21", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyNiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (22)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-22", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyNywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (23)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-23", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyOCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (24)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-24", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIyOSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (25)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-25", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzMCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (26)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-26", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzMSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (27)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-27", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzMiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (28)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-28", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzMywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (29)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-29", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzNCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (3)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-3", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIwOCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (30)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-30", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzNSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (31)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-31", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzNiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (32)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-32", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzNywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (33)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-33", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzOCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (34)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-34", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIzOSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (35)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-35", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0MCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (36)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-36", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0MSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (37)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-37", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0MiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (38)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-38", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0MywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (39)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-39", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0NCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (4)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-4", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIwOSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (40)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-40", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0NSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (41)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-41", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0NiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (42)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-42", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0NywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (43)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-43", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0OCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (44)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-44", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI0OSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (45)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-45", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1MCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (46)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-46", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1MSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (47)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-47", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1MiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (48)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-48", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1MywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (49)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-49", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1NCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (5)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-5", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxMCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (50)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-50", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1NSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (51)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-51", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1NiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (52)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-52", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1NywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (53)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-53", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1OCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (54)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-54", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI1OSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (55)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-55", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2MCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (56)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-56", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2MSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (57)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-57", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2MiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (58)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-58", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2MywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (59)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-59", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2NCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (6)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-6", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxMSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (60)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-60", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2NSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (61)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-61", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2NiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (62)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-62", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2NywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (63)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-63", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2OCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (64)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-64", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI2OSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (65)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-65", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3MCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (66)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-66", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3MSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (67)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-67", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3MiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (68)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-68", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3MywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (69)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-69", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3NCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (7)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-7", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxMiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (70)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-70", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3NSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (71)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-71", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3NiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (72)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-72", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3NywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (73)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-73", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3OCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (74)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-74", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI3OSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (75)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-75", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4MCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (76)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-76", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4MSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (77)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-77", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4MiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (78)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-78", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4MywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (79)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-79", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4NCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (8)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-8", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxMywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (80)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-80", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4NSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (81)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-81", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4NiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (82)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-82", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4NywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (83)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-83", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4OCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (84)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-84", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI4OSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (85)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-85", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5MCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (86)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-86", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5MSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (87)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-87", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5MiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (88)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-88", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5MywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (89)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-89", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5NCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (9)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-9", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxNCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (90)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-90", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5NSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (91)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-91", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5NiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (92)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-92", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5NywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (93)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-93", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5OCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (94)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-94", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzI5OSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (95)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-95", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzMwMCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (96)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-96", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzMwMSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (97)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-97", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzMwMiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (98)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-98", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzMwMywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (99)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-99", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzMwNCwxXQ==" +} \ No newline at end of file From 64071bb9db3553d3862386687938b5948477e675 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Fri, 28 Mar 2025 12:13:45 -0400 Subject: [PATCH 08/14] Fix unit test --- .../v3/transform_utils.test.ts | 36 +++++++------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts index 21c647afda595..f20935cbbe107 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.test.ts @@ -408,20 +408,8 @@ describe('savedObjectToItem', () => { }); }); - it('should pass tag references to getTagNamesFromReferences', () => { + it('should pass references to getTagNamesFromReferences', () => { getTagNamesFromReferences.mockReturnValue(['tag1', 'tag2']); - const tagReferences = [ - { - type: 'tag', - id: 'tag1', - name: 'tag-ref-tag1', - }, - { - type: 'tag', - id: 'tag2', - name: 'tag-ref-tag2', - }, - ]; const input = { ...getSavedObjectForAttributes({ title: 'dashboard with tags', @@ -431,7 +419,16 @@ describe('savedObjectToItem', () => { panelsJSON: JSON.stringify([]), }), references: [ - ...tagReferences, + { + type: 'tag', + id: 'tag1', + name: 'tag-ref-tag1', + }, + { + type: 'tag', + id: 'tag2', + name: 'tag-ref-tag2', + }, { type: 'index-pattern', id: 'index-pattern1', @@ -440,18 +437,11 @@ describe('savedObjectToItem', () => { ], }; const { item, error } = savedObjectToItem(input, false, { getTagNamesFromReferences }); - expect(getTagNamesFromReferences).toHaveBeenCalledWith(tagReferences); + expect(getTagNamesFromReferences).toHaveBeenCalledWith(input.references); expect(error).toBeNull(); expect(item).toEqual({ ...commonSavedObject, - references: [ - ...tagReferences, - { - type: 'index-pattern', - id: 'index-pattern1', - name: 'index-pattern-ref-index-pattern1', - }, - ], + references: [...input.references], attributes: { title: 'dashboard with tags', description: 'I have some tags!', From e1b2121951b7d85fe79623a369057cff795e11a8 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Fri, 28 Mar 2025 12:17:40 -0400 Subject: [PATCH 09/14] Move tag creation to wrapper function This allows saved object migrations to continue to use the synchronous itemAttrsToSavedObject function since they don't have a need for tag creation. --- .../content_management/dashboard_storage.ts | 11 +++---- .../server/content_management/v3/index.ts | 2 ++ .../content_management/v3/transform_utils.ts | 29 +++++++++++++------ .../server/content_management/v3/types.ts | 20 ++++++++----- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts b/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts index 7c225cd2eae27..9ba230ffd5bc7 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts @@ -20,7 +20,7 @@ import type { ITagsClient, Tag } from '@kbn/saved-objects-tagging-oss-plugin/com import { DASHBOARD_SAVED_OBJECT_TYPE } from '../dashboard_saved_object'; import { cmServicesDefinition } from './cm_services'; import { DashboardSavedObjectAttributes } from '../dashboard_saved_object'; -import { itemAttrsToSavedObject, savedObjectToItem } from './latest'; +import { itemAttrsToSavedObjectWithTags, savedObjectToItem } from './latest'; import type { DashboardAttributes, DashboardItem, @@ -31,6 +31,7 @@ import type { DashboardUpdateOptions, DashboardUpdateOut, DashboardSearchOptions, + ReplaceTagReferencesByNameParams, } from './latest'; const getRandomColor = (): string => { @@ -258,9 +259,9 @@ export class DashboardStorage { attributes: soAttributes, references: soReferences, error: attributesError, - } = await itemAttrsToSavedObject({ + } = await itemAttrsToSavedObjectWithTags({ attributes: dataToLatest, - replaceTagReferencesByName: (references: SavedObjectReference[], newTagNames: string[]) => + replaceTagReferencesByName: ({ references, newTagNames }: ReplaceTagReferencesByNameParams) => this.replaceTagReferencesByName(references, newTagNames, allTags, tagsClient), incomingReferences: options.references, }); @@ -340,9 +341,9 @@ export class DashboardStorage { attributes: soAttributes, references: soReferences, error: attributesError, - } = await itemAttrsToSavedObject({ + } = await itemAttrsToSavedObjectWithTags({ attributes: dataToLatest, - replaceTagReferencesByName: (references: SavedObjectReference[], newTagNames: string[]) => + replaceTagReferencesByName: ({ references, newTagNames }: ReplaceTagReferencesByNameParams) => this.replaceTagReferencesByName(references, newTagNames, allTags, tagsClient), incomingReferences: options.references, }); diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/index.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/index.ts index 32a5469fcf72c..5c26a0d7fed99 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/index.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/index.ts @@ -25,6 +25,7 @@ export type { DashboardUpdateOut, DashboardUpdateOptions, DashboardOptions, + ReplaceTagReferencesByNameParams, } from './types'; export { serviceDefinition, @@ -38,5 +39,6 @@ export { export { dashboardAttributesOut, itemAttrsToSavedObject, + itemAttrsToSavedObjectWithTags, savedObjectToItem, } from './transform_utils'; diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts index 1b50b43314010..8acd2538c9097 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts @@ -16,6 +16,7 @@ import type { DashboardItem, ItemAttrsToSavedObjectParams, ItemAttrsToSavedObjectReturn, + ItemAttrsToSavedObjectWithTagsParams, PartialDashboardItem, SavedObjectToItemReturn, } from './types'; @@ -123,9 +124,8 @@ export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2[' }; }; -export const itemAttrsToSavedObject = async ({ +export const itemAttrsToSavedObject = ({ attributes, - replaceTagReferencesByName, incomingReferences = [], }: ItemAttrsToSavedObjectParams): ItemAttrsToSavedObjectReturn => { try { @@ -145,18 +145,29 @@ export const itemAttrsToSavedObject = async ({ kibanaSavedObjectMeta: transformSearchSourceIn(kibanaSavedObjectMeta), }), }; - - // Tags can be specified as an attribute or in the incomingReferences. - const soReferences = - replaceTagReferencesByName && tags && tags.length - ? await replaceTagReferencesByName(incomingReferences, tags) - : incomingReferences; - return { attributes: soAttributes, references: soReferences, error: null }; + return { attributes: soAttributes, references: incomingReferences, error: null }; } catch (e) { return { attributes: null, references: null, error: e }; } }; +export const itemAttrsToSavedObjectWithTags = async ({ + attributes, + replaceTagReferencesByName, + incomingReferences = [], +}: ItemAttrsToSavedObjectWithTagsParams): Promise => { + const { tags, ...restAttributes } = attributes; + // Tags can be specified as an attribute or in the incomingReferences. + const soReferences = + replaceTagReferencesByName && tags && tags.length + ? await replaceTagReferencesByName({ references: incomingReferences, newTagNames: tags }) + : incomingReferences; + return itemAttrsToSavedObject({ + attributes: restAttributes, + incomingReferences: soReferences, + }); +}; + type PartialSavedObject = Omit>, 'references'> & { references: SavedObjectReference[] | undefined; }; diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts index 5248498872fab..0c55985162e3d 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/types.ts @@ -83,14 +83,10 @@ export type SavedObjectToItemReturn = export interface ItemAttrsToSavedObjectParams { attributes: DashboardAttributes; - replaceTagReferencesByName?: ( - references: SavedObjectReference[], - tagNames: string[] - ) => Promise; incomingReferences?: SavedObjectReference[]; } -export type ItemAttrsToSavedObjectReturn = Promise< +export type ItemAttrsToSavedObjectReturn = | { attributes: DashboardSavedObjectAttributes; references: SavedObjectReference[]; @@ -100,5 +96,15 @@ export type ItemAttrsToSavedObjectReturn = Promise< attributes: null; references: null; error: Error; - } ->; + }; + +export interface ItemAttrsToSavedObjectWithTagsParams extends ItemAttrsToSavedObjectParams { + replaceTagReferencesByName?: ( + params: ReplaceTagReferencesByNameParams + ) => Promise; +} + +export interface ReplaceTagReferencesByNameParams { + references: SavedObjectReference[]; + newTagNames: string[]; +} From ff8e17a77ec737fbbcc160b1636904dc9f49660f Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Fri, 28 Mar 2025 12:21:21 -0400 Subject: [PATCH 10/14] Remove optional params from up transform --- .../dashboard/server/content_management/v2/cm_services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v2/cm_services.ts b/src/platform/plugins/shared/dashboard/server/content_management/v2/cm_services.ts index e1fce210b0e19..28da0236e5567 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v2/cm_services.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v2/cm_services.ts @@ -37,7 +37,7 @@ export const serviceDefinition: ServicesDefinition = { ...serviceDefinitionV1?.create?.in, data: { schema: dashboardAttributesSchema, - up: (data: DashboardCrudTypes['CreateIn']['data']) => attributesTov3(data, [], () => []), + up: (data: DashboardCrudTypes['CreateIn']['data']) => attributesTov3(data), }, }, out: { From b1645c7d803af12da379490f41a75035b08902f3 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Fri, 28 Mar 2025 13:33:13 -0400 Subject: [PATCH 11/14] Fix lint error --- src/platform/plugins/shared/dashboard/server/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/dashboard/server/plugin.ts b/src/platform/plugins/shared/dashboard/server/plugin.ts index ec98305aa42f2..d8704dc740840 100644 --- a/src/platform/plugins/shared/dashboard/server/plugin.ts +++ b/src/platform/plugins/shared/dashboard/server/plugin.ts @@ -67,7 +67,7 @@ export class DashboardPlugin }) ); - core.getStartServices().then(([_, { savedObjectsTagging }]) => { + void core.getStartServices().then(([_, { savedObjectsTagging }]) => { const { contentClient } = plugins.contentManagement.register({ id: CONTENT_ID, storage: new DashboardStorage({ From aee41999e6fffd23147dd14ff2008edfe0f3b256 Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Fri, 28 Mar 2025 15:27:40 -0400 Subject: [PATCH 12/14] Fix functional test failure --- .../dashboard/server/content_management/v3/transform_utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts index 8acd2538c9097..5a44857657275 100644 --- a/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts +++ b/src/platform/plugins/shared/dashboard/server/content_management/v3/transform_utils.ts @@ -72,7 +72,7 @@ export function dashboardAttributesOut( ...(refreshInterval && { refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value }, }), - ...(tags && { tags }), + ...(tags && tags.length && { tags }), ...(timeFrom && { timeFrom }), timeRestore: timeRestore ?? false, ...(timeTo && { timeTo }), From fc9e0e7e5cc807deeb683cf16cbbf1d91b4f2b4a Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Sat, 29 Mar 2025 11:14:25 -0400 Subject: [PATCH 13/14] Update functional test fixture --- .../saved_objects/many-dashboards.json | 326 +++++++++--------- 1 file changed, 163 insertions(+), 163 deletions(-) diff --git a/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json b/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json index b71c8317778d1..f518aa7ec2fcd 100644 --- a/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json +++ b/src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json @@ -9,7 +9,7 @@ }, "coreMigrationVersion": "8.8.0", "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-0", + "id": "test-dashboard-00", "managed": false, "references": [], "type": "dashboard", @@ -29,7 +29,7 @@ }, "coreMigrationVersion": "8.8.0", "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-1", + "id": "test-dashboard-01", "managed": false, "references": [], "type": "dashboard", @@ -38,6 +38,166 @@ "version": "WzIwNiwxXQ==" } +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (2)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-02", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIwNywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (3)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-03", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIwOCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (4)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-04", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIwOSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (5)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-05", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxMCwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (6)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-06", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxMSwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (7)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-07", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxMiwxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (8)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-08", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxMywxXQ==" +} + +{ + "attributes": { + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{}" + }, + "panelsJSON": "[]", + "timeRestore": false, + "title": "My dashboard (9)" + }, + "coreMigrationVersion": "8.8.0", + "created_at": "2025-03-20T18:14:41.618Z", + "id": "test-dashboard-09", + "managed": false, + "references": [], + "type": "dashboard", + "typeMigrationVersion": "10.2.0", + "updated_at": "2025-03-20T18:14:41.618Z", + "version": "WzIxNCwxXQ==" +} + { "attributes": { "kibanaSavedObjectMeta": { @@ -238,26 +398,6 @@ "version": "WzIyNCwxXQ==" } -{ - "attributes": { - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{}" - }, - "panelsJSON": "[]", - "timeRestore": false, - "title": "My dashboard (2)" - }, - "coreMigrationVersion": "8.8.0", - "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-2", - "managed": false, - "references": [], - "type": "dashboard", - "typeMigrationVersion": "10.2.0", - "updated_at": "2025-03-20T18:14:41.618Z", - "version": "WzIwNywxXQ==" -} - { "attributes": { "kibanaSavedObjectMeta": { @@ -458,26 +598,6 @@ "version": "WzIzNCwxXQ==" } -{ - "attributes": { - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{}" - }, - "panelsJSON": "[]", - "timeRestore": false, - "title": "My dashboard (3)" - }, - "coreMigrationVersion": "8.8.0", - "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-3", - "managed": false, - "references": [], - "type": "dashboard", - "typeMigrationVersion": "10.2.0", - "updated_at": "2025-03-20T18:14:41.618Z", - "version": "WzIwOCwxXQ==" -} - { "attributes": { "kibanaSavedObjectMeta": { @@ -678,26 +798,6 @@ "version": "WzI0NCwxXQ==" } -{ - "attributes": { - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{}" - }, - "panelsJSON": "[]", - "timeRestore": false, - "title": "My dashboard (4)" - }, - "coreMigrationVersion": "8.8.0", - "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-4", - "managed": false, - "references": [], - "type": "dashboard", - "typeMigrationVersion": "10.2.0", - "updated_at": "2025-03-20T18:14:41.618Z", - "version": "WzIwOSwxXQ==" -} - { "attributes": { "kibanaSavedObjectMeta": { @@ -898,26 +998,6 @@ "version": "WzI1NCwxXQ==" } -{ - "attributes": { - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{}" - }, - "panelsJSON": "[]", - "timeRestore": false, - "title": "My dashboard (5)" - }, - "coreMigrationVersion": "8.8.0", - "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-5", - "managed": false, - "references": [], - "type": "dashboard", - "typeMigrationVersion": "10.2.0", - "updated_at": "2025-03-20T18:14:41.618Z", - "version": "WzIxMCwxXQ==" -} - { "attributes": { "kibanaSavedObjectMeta": { @@ -1118,26 +1198,6 @@ "version": "WzI2NCwxXQ==" } -{ - "attributes": { - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{}" - }, - "panelsJSON": "[]", - "timeRestore": false, - "title": "My dashboard (6)" - }, - "coreMigrationVersion": "8.8.0", - "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-6", - "managed": false, - "references": [], - "type": "dashboard", - "typeMigrationVersion": "10.2.0", - "updated_at": "2025-03-20T18:14:41.618Z", - "version": "WzIxMSwxXQ==" -} - { "attributes": { "kibanaSavedObjectMeta": { @@ -1338,26 +1398,6 @@ "version": "WzI3NCwxXQ==" } -{ - "attributes": { - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{}" - }, - "panelsJSON": "[]", - "timeRestore": false, - "title": "My dashboard (7)" - }, - "coreMigrationVersion": "8.8.0", - "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-7", - "managed": false, - "references": [], - "type": "dashboard", - "typeMigrationVersion": "10.2.0", - "updated_at": "2025-03-20T18:14:41.618Z", - "version": "WzIxMiwxXQ==" -} - { "attributes": { "kibanaSavedObjectMeta": { @@ -1558,26 +1598,6 @@ "version": "WzI4NCwxXQ==" } -{ - "attributes": { - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{}" - }, - "panelsJSON": "[]", - "timeRestore": false, - "title": "My dashboard (8)" - }, - "coreMigrationVersion": "8.8.0", - "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-8", - "managed": false, - "references": [], - "type": "dashboard", - "typeMigrationVersion": "10.2.0", - "updated_at": "2025-03-20T18:14:41.618Z", - "version": "WzIxMywxXQ==" -} - { "attributes": { "kibanaSavedObjectMeta": { @@ -1778,26 +1798,6 @@ "version": "WzI5NCwxXQ==" } -{ - "attributes": { - "kibanaSavedObjectMeta": { - "searchSourceJSON": "{}" - }, - "panelsJSON": "[]", - "timeRestore": false, - "title": "My dashboard (9)" - }, - "coreMigrationVersion": "8.8.0", - "created_at": "2025-03-20T18:14:41.618Z", - "id": "test-dashboard-9", - "managed": false, - "references": [], - "type": "dashboard", - "typeMigrationVersion": "10.2.0", - "updated_at": "2025-03-20T18:14:41.618Z", - "version": "WzIxNCwxXQ==" -} - { "attributes": { "kibanaSavedObjectMeta": { @@ -1996,4 +1996,4 @@ "typeMigrationVersion": "10.2.0", "updated_at": "2025-03-20T18:14:41.618Z", "version": "WzMwNCwxXQ==" -} \ No newline at end of file +} From e57d432062467edae5d6c77bf02463bc29ae9eea Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Mon, 31 Mar 2025 08:48:37 -0400 Subject: [PATCH 14/14] Fix test --- .../api_integration/apis/dashboards/list_dashboards/main.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/test/api_integration/apis/dashboards/list_dashboards/main.ts b/src/platform/test/api_integration/apis/dashboards/list_dashboards/main.ts index c0ef1059169ef..19d61544e2e08 100644 --- a/src/platform/test/api_integration/apis/dashboards/list_dashboards/main.ts +++ b/src/platform/test/api_integration/apis/dashboards/list_dashboards/main.ts @@ -22,7 +22,7 @@ export default function ({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body.total).to.be(100); - expect(response.body.items[0].id).to.be('test-dashboard-0'); + expect(response.body.items[0].id).to.be('test-dashboard-00'); expect(response.body.items.length).to.be(20); });