From df9846598264d658971b70513474a9bc2ea56ef6 Mon Sep 17 00:00:00 2001 From: Anton Dosov Date: Mon, 15 Mar 2021 13:57:44 +0100 Subject: [PATCH] Fix importing dashboards created before ~6.1.0 (#94332) --- .../common/saved_dashboard_references.test.ts | 81 ++++++++++++++++++- .../common/saved_dashboard_references.ts | 39 ++++++--- test/functional/apps/dashboard/bwc_import.ts | 43 ++++++++++ .../dashboard/exports/dashboard_6_0_1.json | 46 +++++++++++ test/functional/apps/dashboard/index.ts | 1 + 5 files changed, 198 insertions(+), 12 deletions(-) create mode 100644 test/functional/apps/dashboard/bwc_import.ts create mode 100644 test/functional/apps/dashboard/exports/dashboard_6_0_1.json diff --git a/src/plugins/dashboard/common/saved_dashboard_references.test.ts b/src/plugins/dashboard/common/saved_dashboard_references.test.ts index cee9aef46f6aa..584d7e5e63a92 100644 --- a/src/plugins/dashboard/common/saved_dashboard_references.test.ts +++ b/src/plugins/dashboard/common/saved_dashboard_references.test.ts @@ -30,11 +30,13 @@ describe('extractReferences', () => { type: 'visualization', id: '1', title: 'Title 1', + version: '7.9.1', }, { type: 'visualization', id: '2', title: 'Title 2', + version: '7.9.1', }, ]), }, @@ -46,7 +48,7 @@ describe('extractReferences', () => { Object { "attributes": Object { "foo": true, - "panelsJSON": "[{\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_0\\"},{\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_1\\"}]", + "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\",\\"panelRefName\\":\\"panel_0\\"},{\\"version\\":\\"7.9.1\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 2\\",\\"panelRefName\\":\\"panel_1\\"}]", }, "references": Array [ Object { @@ -73,6 +75,7 @@ describe('extractReferences', () => { { id: '1', title: 'Title 1', + version: '7.9.1', }, ]), }, @@ -92,6 +95,7 @@ describe('extractReferences', () => { { type: 'visualization', title: 'Title 1', + version: '7.9.1', }, ]), }, @@ -101,12 +105,85 @@ describe('extractReferences', () => { Object { "attributes": Object { "foo": true, - "panelsJSON": "[{\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]", + "panelsJSON": "[{\\"version\\":\\"7.9.1\\",\\"type\\":\\"visualization\\",\\"embeddableConfig\\":{},\\"title\\":\\"Title 1\\"}]", }, "references": Array [], } `); }); + + // https://github.com/elastic/kibana/issues/93772 + test('passes when received older RAW SO with older panels', () => { + const doc = { + id: '1', + attributes: { + hits: 0, + timeFrom: 'now-16h/h', + timeTo: 'now', + refreshInterval: { + display: '1 minute', + section: 2, + value: 60000, + pause: false, + }, + description: '', + uiStateJSON: '{"P-1":{"vis":{"legendOpen":false}}}', + title: 'Errors/Fatals/Warnings dashboard', + timeRestore: true, + version: 1, + panelsJSON: + '[{"col":1,"id":"544891f0-2cf2-11e8-9735-93e95b055f48","panelIndex":1,"row":1,"size_x":12,"size_y":8,"type":"visualization"}]', + optionsJSON: '{"darkTheme":true}', + kibanaSavedObjectMeta: { + searchSourceJSON: + '{"highlightAll":true,"filter":[{"query":{"query_string":{"analyze_wildcard":true,"query":"*"}}}]}', + }, + }, + references: [], + }; + const updatedDoc = extractReferences(doc, deps); + + expect(updatedDoc).toMatchInlineSnapshot(` + Object { + "attributes": Object { + "description": "", + "hits": 0, + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"highlightAll\\":true,\\"filter\\":[{\\"query\\":{\\"query_string\\":{\\"analyze_wildcard\\":true,\\"query\\":\\"*\\"}}}]}", + }, + "optionsJSON": "{\\"darkTheme\\":true}", + "panelsJSON": "[{\\"col\\":1,\\"panelIndex\\":1,\\"row\\":1,\\"size_x\\":12,\\"size_y\\":8,\\"panelRefName\\":\\"panel_0\\"}]", + "refreshInterval": Object { + "display": "1 minute", + "pause": false, + "section": 2, + "value": 60000, + }, + "timeFrom": "now-16h/h", + "timeRestore": true, + "timeTo": "now", + "title": "Errors/Fatals/Warnings dashboard", + "uiStateJSON": "{\\"P-1\\":{\\"vis\\":{\\"legendOpen\\":false}}}", + "version": 1, + }, + "references": Array [ + Object { + "id": "544891f0-2cf2-11e8-9735-93e95b055f48", + "name": "panel_0", + "type": "visualization", + }, + ], + } + `); + + const panel = JSON.parse(updatedDoc.attributes.panelsJSON as string)[0]; + + // unknown older panel keys are left untouched + expect(panel).toHaveProperty('col'); + expect(panel).toHaveProperty('row'); + expect(panel).toHaveProperty('size_x'); + expect(panel).toHaveProperty('size_y'); + }); }); describe('injectReferences', () => { diff --git a/src/plugins/dashboard/common/saved_dashboard_references.ts b/src/plugins/dashboard/common/saved_dashboard_references.ts index 9da71a5cc729f..f1fea99057f83 100644 --- a/src/plugins/dashboard/common/saved_dashboard_references.ts +++ b/src/plugins/dashboard/common/saved_dashboard_references.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import semverSatisfies from 'semver/functions/satisfies'; import { SavedObjectAttributes, SavedObjectReference } from '../../../core/types'; import { extractPanelsReferences, @@ -33,17 +34,35 @@ export function extractReferences( const panelReferences: SavedObjectReference[] = []; let panels: Array> = JSON.parse(String(attributes.panelsJSON)); - const extractedReferencesResult = extractPanelsReferences( - (panels as unknown) as SavedDashboardPanel730ToLatest[], - deps - ); + const isPre730Panel = (panel: Record): boolean => { + return 'version' in panel ? semverSatisfies(panel.version, '<7.3') : true; + }; - panels = (extractedReferencesResult.map((res) => res.panel) as unknown) as Array< - Record - >; - extractedReferencesResult.forEach((res) => { - panelReferences.push(...res.references); - }); + const hasPre730Panel = panels.some(isPre730Panel); + + /** + * `extractPanelsReferences` only knows how to reliably handle "latest" panels + * It is possible that `extractReferences` is run on older dashboard SO with older panels, + * for example, when importing a saved object using saved object UI `extractReferences` is called BEFORE any server side migrations are run. + * + * In this case we skip running `extractPanelsReferences` on such object. + * We also know that there is nothing to extract + * (First possible entity to be extracted by this mechanism is a dashboard drilldown since 7.11) + */ + if (!hasPre730Panel) { + const extractedReferencesResult = extractPanelsReferences( + // it is ~safe~ to cast to `SavedDashboardPanel730ToLatest` because above we've checked that there are only >=7.3 panels + (panels as unknown) as SavedDashboardPanel730ToLatest[], + deps + ); + + panels = (extractedReferencesResult.map((res) => res.panel) as unknown) as Array< + Record + >; + extractedReferencesResult.forEach((res) => { + panelReferences.push(...res.references); + }); + } // TODO: This extraction should be done by EmbeddablePersistableStateService // https://github.com/elastic/kibana/issues/82830 diff --git a/test/functional/apps/dashboard/bwc_import.ts b/test/functional/apps/dashboard/bwc_import.ts new file mode 100644 index 0000000000000..03f1f126338fa --- /dev/null +++ b/test/functional/apps/dashboard/bwc_import.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import path from 'path'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['dashboard', 'header', 'settings', 'savedObjects', 'common']); + const dashboardExpect = getService('dashboardExpect'); + + describe('bwc import', function describeIndexTests() { + before(async function () { + await PageObjects.dashboard.initTests(); + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + await PageObjects.savedObjects.importFile( + path.join(__dirname, 'exports', 'dashboard_6_0_1.json') + ); + await PageObjects.settings.associateIndexPattern( + 'dd684000-8255-11eb-a5e7-93c302c8f329', + 'logstash-*' + ); + await PageObjects.savedObjects.clickConfirmChanges(); + await PageObjects.savedObjects.clickImportDone(); + await PageObjects.common.navigateToApp('dashboard'); + }); + + describe('6.0.1 dashboard', () => { + it('loads an imported dashboard', async function () { + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.loadSavedDashboard('My custom bwc dashboard'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + await dashboardExpect.metricValuesExist(['14,004']); + }); + }); + }); +} diff --git a/test/functional/apps/dashboard/exports/dashboard_6_0_1.json b/test/functional/apps/dashboard/exports/dashboard_6_0_1.json new file mode 100644 index 0000000000000..8c5b1eda32b54 --- /dev/null +++ b/test/functional/apps/dashboard/exports/dashboard_6_0_1.json @@ -0,0 +1,46 @@ +[ + { + "_id": "924ed3d0-8256-11eb-a5e7-93c302c8f329", + "_type": "dashboard", + "_source": { + "title": "My custom bwc dashboard", + "hits": 0, + "description": "", + "panelsJSON": "[{\"size_x\":6,\"size_y\":3,\"panelIndex\":1,\"type\":\"visualization\",\"id\":\"1bdf34a0-8266-11eb-a5e7-93c302c8f329\",\"col\":1,\"row\":1}]", + "optionsJSON": "{\"darkTheme\":false}", + "uiStateJSON": "{\"P-1\":{\"vis\":{\"defaultColors\":{\"0 - 100\":\"rgb(0,104,55)\"}}}}", + "version": 1, + "timeRestore": true, + "timeTo": "Wed Apr 20 2016 23:59:59 GMT+0200", + "timeFrom": "Thu Jan 01 2015 00:00:00 GMT+0100", + "refreshInterval": { + "display": "Off", + "pause": false, + "value": 0 + }, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"query\":{\"language\":\"lucene\",\"query\":\"\"},\"filter\":[],\"highlightAll\":true,\"version\":true}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + }, + { + "_id": "1bdf34a0-8266-11eb-a5e7-93c302c8f329", + "_type": "visualization", + "_source": { + "title": "My custom bwc viz", + "visState": "{\"title\":\"My custom bwc viz\",\"type\":\"metric\",\"params\":{\"addTooltip\":true,\"addLegend\":false,\"type\":\"gauge\",\"gauge\":{\"verticalSplit\":false,\"autoExtend\":false,\"percentageMode\":false,\"gaugeType\":\"Metric\",\"gaugeStyle\":\"Full\",\"backStyle\":\"Full\",\"orientation\":\"vertical\",\"colorSchema\":\"Green to Red\",\"gaugeColorMode\":\"None\",\"useRange\":false,\"colorsRange\":[{\"from\":0,\"to\":100}],\"invertColors\":false,\"labels\":{\"show\":true,\"color\":\"black\"},\"scale\":{\"show\":false,\"labels\":false,\"color\":\"#333\",\"width\":2},\"type\":\"simple\",\"style\":{\"fontSize\":60,\"bgColor\":false,\"labelColor\":false,\"subText\":\"\"}}},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}}]}", + "uiStateJSON": "{\"vis\":{\"defaultColors\":{\"0 - 100\":\"rgb(0,104,55)\"}}}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"dd684000-8255-11eb-a5e7-93c302c8f329\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + } +] diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts index d7bd9f6b51522..9f9422ddaec64 100644 --- a/test/functional/apps/dashboard/index.ts +++ b/test/functional/apps/dashboard/index.ts @@ -95,6 +95,7 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./dashboard_time_picker')); loadTestFile(require.resolve('./bwc_shared_urls')); + loadTestFile(require.resolve('./bwc_import')); loadTestFile(require.resolve('./panel_replacing')); loadTestFile(require.resolve('./panel_cloning')); loadTestFile(require.resolve('./copy_panel_to'));