From 9d7ba90dc914fd5c550037c0ab58b7b4bfcd6a2e Mon Sep 17 00:00:00 2001 From: Nick Peihl Date: Mon, 31 Mar 2025 10:52:24 -0400 Subject: [PATCH] [Dashboard] Inject / extract tag references (#214788) Fixes #210619 ## Summary Provides a tags array on the request and response bodies of dashboards. This allows consumers of the Dashboards HTTP API and internal RPC API to add an array of tag names to the attributes in the body of create and update endpoints. The dashboard server will be responsible for converting the tag names into references in the saved object. If, during creation or update, a tag name does not have a matching tag saved object, a new tag saved object will be created. If the user lacks permissions to manage tags, then an error will be logged in the server and the tag will not be added to the dashboard. The server also injects the tag references as an array of tag names in the attributes of the response body of get and search endpoints of the HTTP and RPC APIs. For backwards compatibility in create and update endpoints, tags can alternatively be specified in the `references` array in the options instead of (or in addition to) the `attributes.tags` in the request body. Similarly, for backwards compatibility, tag references are returned in the `references` of the response body of get and search endpoints. Client-side tag handling is out of scope for this PR. Dashboards listing page and dashboard settings continue to use the tag references and do not use the `tags` attribute in the response. For example: Here's how we currently create a dashboard with tag references. ``` ## Creating a dashboard with tag references POST kbn:/api/dashboards/dashboard { "attributes": { "title": "my tagged dashboard" }, "references": [ { "type": "tag", "id": "37aab5de-a34d-47cb-9aa5-9375d5db595f", "name": "tag-ref-37aab5de-a34d-47cb-9aa5-9375d5db595f" }, { "type": "tag", "id": "5ed29bba-c14d-4302-9a8c-9602e40dbc2a", "name": "tag-ref-5ed29bba-c14d-4302-9a8c-9602e40dbc2a" }, { "type": "tag", "id": "fc7890e8-c00f-44a1-88a2-250e4d27e61d", "name": "tag-ref-fc7890e8-c00f-44a1-88a2-250e4d27e61d" } ] } ``` With this PR, creating a dashboard with tags is much simpler. ``` ## Creating a dashboard with tag names POST kbn:/api/dashboards/dashboard { "attributes": { "title": "my tagged dashboard", "tags": [ "boo", "foo", "bingo", "bongo" ] } } ``` ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] If there are more than one tag saved objects with the same name, only one of the tag references will be added to the saved object when creating a dashboard. Creating tags with duplicate names are not permitted via the UI. But there is no such restrictions when creating tags from imported saved objects. Having multiple tags with the same name is an edge case that Kibana guards against with reasonable restrictions, so I think we should not be too concerned about it. - [ ] ... --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> (cherry picked from commit c4f7c649b12b8189f1b7a00d4857c1372acc8755) # Conflicts: # src/platform/plugins/shared/dashboard/server/content_management/dashboard_storage.ts # src/platform/plugins/shared/dashboard/server/plugin.ts --- oas_docs/bundle.json | 42 + oas_docs/bundle.serverless.json | 42 + oas_docs/output/kibana.serverless.yaml | 30 + oas_docs/output/kibana.yaml | 30 + .../dashboard_api/get_serialized_state.ts | 2 + .../content_management/dashboard_storage.ts | 185 +- .../content_management/v1/cm_services.ts | 7 - .../content_management/v2/cm_services.ts | 9 +- .../content_management/v3/cm_services.ts | 12 +- .../server/content_management/v3/index.ts | 4 +- .../v3/transform_utils.test.ts | 87 +- .../content_management/v3/transform_utils.ts | 58 +- .../server/content_management/v3/types.ts | 20 +- .../migrate_extract_panel_references.ts | 6 +- .../plugins/shared/dashboard/server/plugin.ts | 24 +- .../plugins/shared/dashboard/tsconfig.json | 3 +- .../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/list_dashboards/index.ts | 30 +- .../apis/dashboards/list_dashboards/main.ts | 2 +- .../apis/dashboards/update_dashboard/index.ts | 6 + .../apis/dashboards/update_dashboard/main.ts | 155 +- .../saved_objects/many-dashboards.json | 1999 +++++++++++++++++ .../kbn_archiver/saved_objects/tags.json | 91 + 26 files changed, 2892 insertions(+), 139 deletions(-) create mode 100644 src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/many-dashboards.json create mode 100644 src/platform/test/api_integration/fixtures/kbn_archiver/saved_objects/tags.json diff --git a/oas_docs/bundle.json b/oas_docs/bundle.json index b3fbab8817175..5a7f54a08ca14 100644 --- a/oas_docs/bundle.json +++ b/oas_docs/bundle.json @@ -6293,6 +6293,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", @@ -6939,6 +6946,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" @@ -7593,6 +7607,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" @@ -8131,6 +8152,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" @@ -8757,6 +8785,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" @@ -9289,6 +9324,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 7fb45cbdf7f29..4a9e2744df782 100644 --- a/oas_docs/bundle.serverless.json +++ b/oas_docs/bundle.serverless.json @@ -5830,6 +5830,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", @@ -6476,6 +6483,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" @@ -7130,6 +7144,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" @@ -7668,6 +7689,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" @@ -8294,6 +8322,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" @@ -8826,6 +8861,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/output/kibana.serverless.yaml b/oas_docs/output/kibana.serverless.yaml index 432cef82ff8da..b43c661200082 100644 --- a/oas_docs/output/kibana.serverless.yaml +++ b/oas_docs/output/kibana.serverless.yaml @@ -5540,6 +5540,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 @@ -6000,6 +6005,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 @@ -6465,6 +6475,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 @@ -6853,6 +6868,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 @@ -7298,6 +7318,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 @@ -7682,6 +7707,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 957c7c93fa104..1cb1420adc9ff 100644 --- a/oas_docs/output/kibana.yaml +++ b/oas_docs/output/kibana.yaml @@ -8056,6 +8056,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 @@ -8516,6 +8521,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 @@ -8981,6 +8991,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 @@ -9369,6 +9384,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 @@ -9814,6 +9834,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 @@ -10198,6 +10223,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/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 d113d509f5e89..2ccbefc6311f1 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 @@ -17,10 +17,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 { 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'; -import { itemAttrsToSavedObjectAttrs, savedObjectToItem } from './latest'; +import { itemAttrsToSavedObjectWithTags, savedObjectToItem } from './latest'; import type { DashboardAttributes, DashboardItem, @@ -31,8 +34,13 @@ import type { DashboardUpdateOptions, DashboardUpdateOut, DashboardSearchOptions, + ReplaceTagReferencesByNameParams, } from './latest'; +const getRandomColor = (): string => { + return '#' + String(Math.floor(Math.random() * 16777215).toString(16)).padStart(6, '0'); +}; + const searchArgsToSOFindOptions = ( query: SearchQuery, options: DashboardSearchOptions @@ -62,10 +70,13 @@ export class DashboardStorage { constructor({ logger, throwOnResultValidationError, + savedObjectsTagging, }: { logger: Logger; throwOnResultValidationError: boolean; + savedObjectsTagging?: SavedObjectTaggingStart; }) { + this.savedObjectsTagging = savedObjectsTagging; this.logger = logger; this.throwOnResultValidationError = throwOnResultValidationError ?? false; this.mSearch = { @@ -111,18 +122,105 @@ export class DashboardStorage { } private logger: Logger; + private savedObjectsTagging?: SavedObjectTaggingStart; private throwOnResultValidationError: boolean; - mSearch: { - savedObjectType: string; - toItemResult: (ctx: StorageContext, savedObject: SavedObjectsFindResult) => DashboardItem; - additionalSearchFields?: string[]; - }; + private getTagNamesFromReferences(references: SavedObjectReference[], allTags: Tag[]) { + return Array.from( + new Set( + this.savedObjectsTagging + ? this.savedObjectsTagging + .getTagsFromReferences(references, allTags) + .tags.map((tag) => tag.name) + : [] + ) + ); + } + + private getUniqueTagNames( + references: SavedObjectReference[], + newTagNames: string[], + allTags: Tag[] + ) { + const referenceTagNames = this.getTagNamesFromReferences(references, allTags); + return new Set([...referenceTagNames, ...newTagNames]); + } + + private async replaceTagReferencesByName( + references: SavedObjectReference[], + newTagNames: string[], + 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 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); - + const tagsClient = this.savedObjectsTagging?.createTagClient({ client: soClient }); + const allTags = (await tagsClient?.getAll()) ?? []; // Save data in DB const { saved_object: savedObject, @@ -131,7 +229,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}`); } @@ -176,6 +277,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< @@ -194,8 +297,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, + } = await itemAttrsToSavedObjectWithTags({ + attributes: dataToLatest, + replaceTagReferencesByName: ({ references, newTagNames }: ReplaceTagReferencesByNameParams) => + this.replaceTagReferencesByName(references, newTagNames, allTags, tagsClient), + incomingReferences: options.references, + }); if (attributesError) { throw Boom.badRequest(`Invalid data. ${attributesError.message}`); } @@ -204,10 +315,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}`); } @@ -245,6 +359,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< @@ -263,8 +379,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, + } = await itemAttrsToSavedObjectWithTags({ + attributes: dataToLatest, + replaceTagReferencesByName: ({ references, newTagNames }: ReplaceTagReferencesByNameParams) => + this.replaceTagReferencesByName(references, newTagNames, allTags, tagsClient), + incomingReferences: options.references, + }); if (attributesError) { throw Boom.badRequest(`Invalid data. ${attributesError.message}`); } @@ -274,10 +398,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}`); } @@ -326,6 +453,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< @@ -339,16 +468,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..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 @@ -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 78b13b43322e1..043d63d9baede 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({ @@ -545,11 +550,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..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, @@ -37,6 +38,7 @@ export { } from './cm_services'; export { dashboardAttributesOut, - itemAttrsToSavedObjectAttrs, + itemAttrsToSavedObject, + itemAttrsToSavedObjectWithTags, 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..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 @@ -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, - itemAttrsToSavedObjectAttrs, - 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,51 @@ describe('savedObjectToItem', () => { }); }); + it('should pass references to getTagNamesFromReferences', () => { + getTagNamesFromReferences.mockReturnValue(['tag1', 'tag2']); + const input = { + ...getSavedObjectForAttributes({ + title: 'dashboard with tags', + description: 'I have some tags!', + timeRestore: true, + kibanaSavedObjectMeta: {}, + panelsJSON: JSON.stringify([]), + }), + references: [ + { + type: 'tag', + id: 'tag1', + name: 'tag-ref-tag1', + }, + { + type: 'tag', + id: 'tag2', + name: 'tag-ref-tag2', + }, + { + type: 'index-pattern', + id: 'index-pattern1', + name: 'index-pattern-ref-index-pattern1', + }, + ], + }; + const { item, error } = savedObjectToItem(input, false, { getTagNamesFromReferences }); + expect(getTagNamesFromReferences).toHaveBeenCalledWith(input.references); + expect(error).toBeNull(); + expect(item).toEqual({ + ...commonSavedObject, + references: [...input.references], + 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 0ea8b883305c9..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 @@ -14,7 +14,9 @@ import type { DashboardAttributes, DashboardGetOut, DashboardItem, - ItemAttrsToSavedObjectAttrsReturn, + ItemAttrsToSavedObjectParams, + ItemAttrsToSavedObjectReturn, + ItemAttrsToSavedObjectWithTagsParams, PartialDashboardItem, SavedObjectToItemReturn, } from './types'; @@ -34,7 +36,9 @@ import { } from './transforms'; export function dashboardAttributesOut( - attributes: DashboardSavedObjectAttributes | Partial + attributes: DashboardSavedObjectAttributes | Partial, + references?: SavedObjectReference[], + getTagNamesFromReferences?: (references: SavedObjectReference[]) => string[] ): DashboardAttributes | Partial { const { controlGroupInput, @@ -49,6 +53,13 @@ export function dashboardAttributesOut( title, version, } = attributes; + + // Inject any tag names from references into the attributes + let tags: string[] | undefined; + if (getTagNamesFromReferences && references && references.length) { + tags = getTagNamesFromReferences(references); + } + // try to maintain a consistent (alphabetical) order of keys return { ...(controlGroupInput && { controlGroupInput: transformControlGroupOut(controlGroupInput) }), @@ -61,6 +72,7 @@ export function dashboardAttributesOut( ...(refreshInterval && { refreshInterval: { pause: refreshInterval.pause, value: refreshInterval.value }, }), + ...(tags && tags.length && { tags }), ...(timeFrom && { timeFrom }), timeRestore: timeRestore ?? false, ...(timeTo && { timeTo }), @@ -112,11 +124,12 @@ export const getResultV3ToV2 = (result: DashboardGetOut): DashboardCrudTypesV2[' }; }; -export const itemAttrsToSavedObjectAttrs = ( - attributes: DashboardAttributes -): ItemAttrsToSavedObjectAttrsReturn => { +export const itemAttrsToSavedObject = ({ + attributes, + incomingReferences = [], +}: ItemAttrsToSavedObjectParams): ItemAttrsToSavedObjectReturn => { try { - const { controlGroupInput, kibanaSavedObjectMeta, options, panels, ...rest } = attributes; + const { controlGroupInput, kibanaSavedObjectMeta, options, panels, tags, ...rest } = attributes; const soAttributes = { ...rest, ...(controlGroupInput && { @@ -132,17 +145,34 @@ export const itemAttrsToSavedObjectAttrs = ( kibanaSavedObjectMeta: transformSearchSourceIn(kibanaSavedObjectMeta), }), }; - return { attributes: soAttributes, error: null }; + return { attributes: soAttributes, references: incomingReferences, error: null }; } catch (e) { - return { attributes: null, error: 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; }; -export interface SavedObjectToItemOptions { +interface SavedObjectToItemOptions { /** * attributes to include in the output item */ @@ -151,6 +181,7 @@ export interface SavedObjectToItemOptions { * references to include in the output item */ allowedReferences?: string[]; + getTagNamesFromReferences?: (references: SavedObjectReference[]) => string[]; } export function savedObjectToItem( @@ -170,7 +201,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, @@ -189,8 +220,11 @@ export function savedObjectToItem( 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..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 @@ -81,12 +81,30 @@ export type SavedObjectToItemReturn = error: Error; }; -export type ItemAttrsToSavedObjectAttrsReturn = +export interface ItemAttrsToSavedObjectParams { + attributes: DashboardAttributes; + incomingReferences?: SavedObjectReference[]; +} + +export type ItemAttrsToSavedObjectReturn = | { attributes: DashboardSavedObjectAttributes; + references: SavedObjectReference[]; error: null; } | { attributes: null; + references: null; error: Error; }; + +export interface ItemAttrsToSavedObjectWithTagsParams extends ItemAttrsToSavedObjectParams { + replaceTagReferencesByName?: ( + params: ReplaceTagReferencesByNameParams + ) => Promise; +} + +export interface ReplaceTagReferencesByNameParams { + references: SavedObjectReference[]; + newTagNames: string[]; +} 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..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 @@ -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,9 @@ export function createExtractPanelReferencesMigration( { embeddablePersistableStateService: deps.embeddable } ); - const { attributes, error: attributesError } = itemAttrsToSavedObjectAttrs(extractedAttributes); + const { attributes, error: attributesError } = itemAttrsToSavedObject({ + attributes: 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 a102fb39eaf34..b9e3c8cecbdeb 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 @@ -64,15 +66,19 @@ export class DashboardPlugin }) ); - plugins.contentManagement.register({ - id: CONTENT_ID, - storage: new DashboardStorage({ - throwOnResultValidationError: this.initializerContext.env.mode.dev, - logger: this.logger.get('storage'), - }), - version: { - latest: LATEST_VERSION, - }, + void 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; }); plugins.contentManagement.favorites.registerFavoriteType('dashboard'); 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/**/*"] } 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..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 @@ -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('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) + .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', '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(2); + }); + + 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', 'buzz'], + }, + 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('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-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/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/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); }); 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..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 @@ -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,107 @@ 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); + }); + + 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..f518aa7ec2fcd --- /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-00", + "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-01", + "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 (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": { + "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 (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 (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 (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 (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 (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 (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 (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 (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==" +} 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==" +}