From d79236cfd4680559825b0c9cf3c55489a5bcf56e Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 17 Apr 2020 12:53:43 +0300 Subject: [PATCH] Move common used types and fucntions to dashboard/common --- src/core/server/index.ts | 1 + src/legacy/core_plugins/kibana/index.js | 20 - .../kibana/migrations/migrations.js | 106 -- .../kibana/migrations/migrations.test.js | 447 ----- .../core_plugins/kibana/migrations/types.ts | 40 - .../public/dashboard/migrations/index.ts | 21 - .../dashboard/{public => common}/bwc/types.ts | 17 +- .../dashboard/common/embeddable/types.ts} | 9 +- .../dashboard/common/index.ts} | 27 +- .../common}/migrate_to_730_panels.test.ts | 15 +- .../common}/migrate_to_730_panels.ts | 12 +- src/plugins/dashboard/common/types.ts | 76 + .../embeddable/grid/dashboard_grid.tsx | 3 +- .../public/application/embeddable/types.ts | 9 +- .../application/lib/migrate_app_state.ts | 8 +- src/plugins/dashboard/public/bwc/index.ts | 20 - src/plugins/dashboard/public/index.ts | 24 - src/plugins/dashboard/public/types.ts | 58 +- .../server/saved_objects/dashboard.ts | 6 +- .../dashboard_migrations.test.ts | 1664 ++++------------- .../saved_objects/dashboard_migrations.ts | 603 +----- .../server/saved_objects}/is_dashboard_doc.ts | 17 +- .../migrate_match_all_query.test.ts | 0 .../saved_objects}/migrate_match_all_query.ts | 2 +- .../saved_objects}/migrations_730.test.ts | 36 +- .../server/saved_objects}/migrations_730.ts | 27 +- .../move_filters_to_query.test.ts | 2 +- .../saved_objects}/move_filters_to_query.ts | 0 28 files changed, 531 insertions(+), 2739 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/migrations/migrations.js delete mode 100644 src/legacy/core_plugins/kibana/migrations/migrations.test.js delete mode 100644 src/legacy/core_plugins/kibana/migrations/types.ts delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts rename src/plugins/dashboard/{public => common}/bwc/types.ts (93%) rename src/{legacy/core_plugins/kibana/migrations/index.ts => plugins/dashboard/common/embeddable/types.ts} (89%) rename src/{legacy/core_plugins/kibana/migrations/is_doc.ts => plugins/dashboard/common/index.ts} (64%) rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/common}/migrate_to_730_panels.test.ts (97%) rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/common}/migrate_to_730_panels.ts (99%) create mode 100644 src/plugins/dashboard/common/types.ts delete mode 100644 src/plugins/dashboard/public/bwc/index.ts rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/server/saved_objects}/is_dashboard_doc.ts (70%) rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/server/saved_objects}/migrate_match_all_query.test.ts (100%) rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/server/saved_objects}/migrate_match_all_query.ts (95%) rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/server/saved_objects}/migrations_730.test.ts (91%) rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/server/saved_objects}/migrations_730.ts (79%) rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/server/saved_objects}/move_filters_to_query.test.ts (96%) rename src/{legacy/core_plugins/kibana/public/dashboard/migrations => plugins/dashboard/server/saved_objects}/move_filters_to_query.ts (100%) diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 039988fa0896..fe1c9925abc4 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -220,6 +220,7 @@ export { SavedObjectsMigrationLogger, SavedObjectsRawDoc, SavedObjectSanitizedDoc, + SavedObjectUnsanitizedDoc, SavedObjectsRepositoryFactory, SavedObjectsResolveImportErrorsOptions, SavedObjectsSchema, diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 1d643418997f..7a11845e29b5 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -21,7 +21,6 @@ import Fs from 'fs'; import { resolve } from 'path'; import { promisify } from 'util'; -import { migrations } from './migrations'; import { importApi } from './server/routes/api/import'; import { exportApi } from './server/routes/api/export'; import mappings from './mappings.json'; @@ -124,23 +123,6 @@ export default function(kibana) { ], savedObjectsManagement: { - dashboard: { - icon: 'dashboardApp', - defaultSearchField: 'title', - isImportableAndExportable: true, - getTitle(obj) { - return obj.attributes.title; - }, - getEditUrl(obj) { - return `/management/kibana/objects/savedDashboards/${encodeURIComponent(obj.id)}`; - }, - getInAppUrl(obj) { - return { - path: `/app/kibana#/dashboard/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'dashboard.show', - }; - }, - }, url: { defaultSearchField: 'url', isImportableAndExportable: true, @@ -181,8 +163,6 @@ export default function(kibana) { mappings, uiSettingDefaults: getUiSettingDefaults(), - - migrations, }, uiCapabilities: async function() { diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.js b/src/legacy/core_plugins/kibana/migrations/migrations.js deleted file mode 100644 index 029dbde555a4..000000000000 --- a/src/legacy/core_plugins/kibana/migrations/migrations.js +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { get } from 'lodash'; -import { - migrateMatchAllQuery, - migrations730 as dashboardMigrations730, -} from '../public/dashboard/migrations'; - -function migrateIndexPattern(doc) { - const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); - if (typeof searchSourceJSON !== 'string') { - return; - } - let searchSource; - try { - searchSource = JSON.parse(searchSourceJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - return; - } - if (searchSource.index) { - searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; - doc.references.push({ - name: searchSource.indexRefName, - type: 'index-pattern', - id: searchSource.index, - }); - delete searchSource.index; - } - if (searchSource.filter) { - searchSource.filter.forEach((filterRow, i) => { - if (!filterRow.meta || !filterRow.meta.index) { - return; - } - filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; - doc.references.push({ - name: filterRow.meta.indexRefName, - type: 'index-pattern', - id: filterRow.meta.index, - }); - delete filterRow.meta.index; - }); - } - doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); -} - -export const migrations = { - dashboard: { - '6.7.2': migrateMatchAllQuery, - '7.0.0': doc => { - // Set new "references" attribute - doc.references = doc.references || []; - - // Migrate index pattern - migrateIndexPattern(doc); - // Migrate panels - const panelsJSON = get(doc, 'attributes.panelsJSON'); - if (typeof panelsJSON !== 'string') { - return doc; - } - let panels; - try { - panels = JSON.parse(panelsJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - return doc; - } - if (!Array.isArray(panels)) { - return doc; - } - panels.forEach((panel, i) => { - if (!panel.type || !panel.id) { - return; - } - panel.panelRefName = `panel_${i}`; - doc.references.push({ - name: `panel_${i}`, - type: panel.type, - id: panel.id, - }); - delete panel.type; - delete panel.id; - }); - doc.attributes.panelsJSON = JSON.stringify(panels); - return doc; - }, - '7.3.0': dashboardMigrations730, - }, -}; diff --git a/src/legacy/core_plugins/kibana/migrations/migrations.test.js b/src/legacy/core_plugins/kibana/migrations/migrations.test.js deleted file mode 100644 index b02081128c85..000000000000 --- a/src/legacy/core_plugins/kibana/migrations/migrations.test.js +++ /dev/null @@ -1,447 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { migrations } from './migrations'; - -describe('dashboard', () => { - describe('7.0.0', () => { - const migration = migrations.dashboard['7.0.0']; - - test('skips error on empty object', () => { - expect(migration({})).toMatchInlineSnapshot(` -Object { - "references": Array [], -} -`); - }); - - test('skips errors when searchSourceJSON is null', () => { - const doc = { - id: '1', - type: 'dashboard', - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: null, - }, - panelsJSON: - '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": null, - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); - }); - - test('skips errors when searchSourceJSON is undefined', () => { - const doc = { - id: '1', - type: 'dashboard', - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: undefined, - }, - panelsJSON: - '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": undefined, - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); - }); - - test('skips error when searchSourceJSON is not a string', () => { - const doc = { - id: '1', - type: 'dashboard', - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: 123, - }, - panelsJSON: - '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": 123, - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); - }); - - test('skips error when searchSourceJSON is invalid json', () => { - const doc = { - id: '1', - type: 'dashboard', - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: '{abc123}', - }, - panelsJSON: - '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{abc123}", - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); - }); - - test('skips error when "index" and "filter" is missing from searchSourceJSON', () => { - const doc = { - id: '1', - type: 'dashboard', - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ bar: true }), - }, - panelsJSON: - '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true}", - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); - }); - - test('extracts "index" attribute from doc', () => { - const doc = { - id: '1', - type: 'dashboard', - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), - }, - panelsJSON: - '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern", - }, - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); - }); - - test('extracts index patterns from filter', () => { - const doc = { - id: '1', - type: 'dashboard', - attributes: { - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - bar: true, - filter: [ - { - meta: { - foo: true, - index: 'my-index', - }, - }, - ], - }), - }, - panelsJSON: - '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', - }, - }; - const migratedDoc = migration(doc); - - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", - }, - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "my-index", - "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "type": "index-pattern", - }, - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], - "type": "dashboard", -} -`); - }); - - test('skips error when panelsJSON is not a string', () => { - const doc = { - id: '1', - attributes: { - panelsJSON: 123, - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": 123, - }, - "id": "1", - "references": Array [], -} -`); - }); - - test('skips error when panelsJSON is not valid JSON', () => { - const doc = { - id: '1', - attributes: { - panelsJSON: '{123abc}', - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "{123abc}", - }, - "id": "1", - "references": Array [], -} -`); - }); - - test('skips panelsJSON when its not an array', () => { - const doc = { - id: '1', - attributes: { - panelsJSON: '{}', - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "{}", - }, - "id": "1", - "references": Array [], -} -`); - }); - - test('skips error when a panel is missing "type" attribute', () => { - const doc = { - id: '1', - attributes: { - panelsJSON: '[{"id":"123"}]', - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "[{\\"id\\":\\"123\\"}]", - }, - "id": "1", - "references": Array [], -} -`); - }); - - test('skips error when a panel is missing "id" attribute', () => { - const doc = { - id: '1', - attributes: { - panelsJSON: '[{"type":"visualization"}]', - }, - }; - expect(migration(doc)).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "[{\\"type\\":\\"visualization\\"}]", - }, - "id": "1", - "references": Array [], -} -`); - }); - - test('extract panel references from doc', () => { - const doc = { - id: '1', - attributes: { - panelsJSON: - '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', - }, - }; - const migratedDoc = migration(doc); - expect(migratedDoc).toMatchInlineSnapshot(` -Object { - "attributes": Object { - "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", - }, - "id": "1", - "references": Array [ - Object { - "id": "1", - "name": "panel_0", - "type": "visualization", - }, - Object { - "id": "2", - "name": "panel_1", - "type": "visualization", - }, - ], -} -`); - }); - }); -}); diff --git a/src/legacy/core_plugins/kibana/migrations/types.ts b/src/legacy/core_plugins/kibana/migrations/types.ts deleted file mode 100644 index 839f753670b2..000000000000 --- a/src/legacy/core_plugins/kibana/migrations/types.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectReference } from '../../../../core/server'; - -export interface SavedObjectAttributes { - kibanaSavedObjectMeta: { - searchSourceJSON: string; - }; -} - -export interface Doc { - references: SavedObjectReference[]; - attributes: Attributes; - id: string; - type: string; -} - -export interface DocPre700 { - attributes: Attributes; - id: string; - type: string; -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts deleted file mode 100644 index f333ce97d120..000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { migrations730 } from './migrations_730'; -export { migrateMatchAllQuery } from './migrate_match_all_query'; diff --git a/src/plugins/dashboard/public/bwc/types.ts b/src/plugins/dashboard/common/bwc/types.ts similarity index 93% rename from src/plugins/dashboard/public/bwc/types.ts rename to src/plugins/dashboard/common/bwc/types.ts index d5655e525e9b..242779934546 100644 --- a/src/plugins/dashboard/public/bwc/types.ts +++ b/src/plugins/dashboard/common/bwc/types.ts @@ -18,33 +18,28 @@ */ import { SavedObjectReference } from 'kibana/public'; -import { GridData } from '../application'; -export interface SavedObjectAttributes { +import { GridData } from '../'; + +interface SavedObjectAttributes { kibanaSavedObjectMeta: { searchSourceJSON: string; }; } -export interface Doc { +interface Doc { references: SavedObjectReference[]; attributes: Attributes; id: string; type: string; } -export interface DocPre700 { +interface DocPre700 { attributes: Attributes; id: string; type: string; } -export interface SavedObjectAttributes { - kibanaSavedObjectMeta: { - searchSourceJSON: string; - }; -} - interface DashboardAttributes extends SavedObjectAttributes { panelsJSON: string; description: string; @@ -55,8 +50,6 @@ interface DashboardAttributes extends SavedObjectAttributes { optionsJSON?: string; } -export type DashboardAttributes730ToLatest = DashboardAttributes; - interface DashboardAttributesTo720 extends SavedObjectAttributes { panelsJSON: string; description: string; diff --git a/src/legacy/core_plugins/kibana/migrations/index.ts b/src/plugins/dashboard/common/embeddable/types.ts similarity index 89% rename from src/legacy/core_plugins/kibana/migrations/index.ts rename to src/plugins/dashboard/common/embeddable/types.ts index 68c843d2343c..eb76d73af7a5 100644 --- a/src/legacy/core_plugins/kibana/migrations/index.ts +++ b/src/plugins/dashboard/common/embeddable/types.ts @@ -17,5 +17,10 @@ * under the License. */ -// @ts-ignore -export { migrations } from './migrations'; +export interface GridData { + w: number; + h: number; + x: number; + y: number; + i: string; +} diff --git a/src/legacy/core_plugins/kibana/migrations/is_doc.ts b/src/plugins/dashboard/common/index.ts similarity index 64% rename from src/legacy/core_plugins/kibana/migrations/is_doc.ts rename to src/plugins/dashboard/common/index.ts index cc50dfa3b2d2..e3f3f629ae5d 100644 --- a/src/legacy/core_plugins/kibana/migrations/is_doc.ts +++ b/src/plugins/dashboard/common/index.ts @@ -17,15 +17,20 @@ * under the License. */ -import { Doc } from './types'; +export { GridData } from './embeddable/types'; +export { + RawSavedDashboardPanel730ToLatest, + DashboardDoc730ToLatest, + DashboardDoc700To720, + DashboardDocPre700, +} from './bwc/types'; +export { + SavedDashboardPanelTo60, + SavedDashboardPanel610, + SavedDashboardPanel620, + SavedDashboardPanel630, + SavedDashboardPanel640To720, + SavedDashboardPanel730ToLatest, +} from './types'; -export function isDoc(doc: { [key: string]: unknown } | Doc): doc is Doc { - return ( - typeof doc.id === 'string' && - typeof doc.type === 'string' && - doc.attributes !== null && - typeof doc.attributes === 'object' && - doc.references !== null && - typeof doc.references === 'object' - ); -} +export { migratePanelsTo730 } from './migrate_to_730_panels'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts b/src/plugins/dashboard/common/migrate_to_730_panels.test.ts similarity index 97% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts rename to src/plugins/dashboard/common/migrate_to_730_panels.test.ts index 4dd71fd8ee5f..0867909225dd 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.test.ts +++ b/src/plugins/dashboard/common/migrate_to_730_panels.test.ts @@ -19,15 +19,12 @@ import { migratePanelsTo730 } from './migrate_to_730_panels'; import { RawSavedDashboardPanelTo60, - RawSavedDashboardPanel610, - RawSavedDashboardPanel620, RawSavedDashboardPanel630, RawSavedDashboardPanel640To720, - DEFAULT_PANEL_WIDTH, - DEFAULT_PANEL_HEIGHT, - SavedDashboardPanelTo60, - SavedDashboardPanel730ToLatest, -} from '../../../../../../plugins/dashboard/public'; + RawSavedDashboardPanel610, + RawSavedDashboardPanel620, +} from './bwc/types'; +import { SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest } from './types'; test('6.0 migrates uiState, sort, scales, and gridData', async () => { const uiState = { @@ -96,8 +93,8 @@ test('6.0 migration gives default width and height when missing', () => { }, ]; const newPanels = migratePanelsTo730(panels, '8.0.0', true); - expect(newPanels[0].gridData.w).toBe(DEFAULT_PANEL_WIDTH); - expect(newPanels[0].gridData.h).toBe(DEFAULT_PANEL_HEIGHT); + expect(newPanels[0].gridData.w).toBe(24); + expect(newPanels[0].gridData.h).toBe(15); expect(newPanels[0].version).toBe('8.0.0'); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts b/src/plugins/dashboard/common/migrate_to_730_panels.ts similarity index 99% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts rename to src/plugins/dashboard/common/migrate_to_730_panels.ts index a19c861f092d..8d3d51594586 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels.ts +++ b/src/plugins/dashboard/common/migrate_to_730_panels.ts @@ -21,17 +21,19 @@ import semver from 'semver'; import uuid from 'uuid'; import { GridData, + SavedDashboardPanelTo60, + SavedDashboardPanel620, + SavedDashboardPanel630, + SavedDashboardPanel610, +} from './'; +import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel630, RawSavedDashboardPanel640To720, RawSavedDashboardPanel730ToLatest, RawSavedDashboardPanel610, RawSavedDashboardPanel620, - SavedDashboardPanelTo60, - SavedDashboardPanel620, - SavedDashboardPanel630, - SavedDashboardPanel610, -} from '../../../../../../plugins/dashboard/public'; +} from './bwc/types'; const PANEL_HEIGHT_SCALE_FACTOR = 5; const PANEL_HEIGHT_SCALE_FACTOR_WITH_MARGINS = 4; diff --git a/src/plugins/dashboard/common/types.ts b/src/plugins/dashboard/common/types.ts new file mode 100644 index 000000000000..7cc82a917397 --- /dev/null +++ b/src/plugins/dashboard/common/types.ts @@ -0,0 +1,76 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + RawSavedDashboardPanelTo60, + RawSavedDashboardPanel610, + RawSavedDashboardPanel620, + RawSavedDashboardPanel630, + RawSavedDashboardPanel640To720, + RawSavedDashboardPanel730ToLatest, +} from './bwc/types'; + +export type SavedDashboardPanel640To720 = Pick< + RawSavedDashboardPanel640To720, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel630 = Pick< + RawSavedDashboardPanel630, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel620 = Pick< + RawSavedDashboardPanel620, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanel610 = Pick< + RawSavedDashboardPanel610, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +export type SavedDashboardPanelTo60 = Pick< + RawSavedDashboardPanelTo60, + Exclude +> & { + readonly id: string; + readonly type: string; +}; + +// id becomes optional starting in 7.3.0 +export type SavedDashboardPanel730ToLatest = Pick< + RawSavedDashboardPanel730ToLatest, + Exclude +> & { + readonly id?: string; + readonly type: string; +}; diff --git a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx index b15a813aff90..fb33649093c8 100644 --- a/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx +++ b/src/plugins/dashboard/public/application/embeddable/grid/dashboard_grid.tsx @@ -29,9 +29,10 @@ import _ from 'lodash'; import React from 'react'; import { Subscription } from 'rxjs'; import ReactGridLayout, { Layout } from 'react-grid-layout'; +import { GridData } from '../../../../common'; import { ViewMode, EmbeddableChildPanel } from '../../../embeddable_plugin'; import { DASHBOARD_GRID_COLUMN_COUNT, DASHBOARD_GRID_HEIGHT } from '../dashboard_constants'; -import { DashboardPanelState, GridData } from '../types'; +import { DashboardPanelState } from '../types'; import { withKibana } from '../../../../../kibana_react/public'; import { DashboardContainerInput } from '../dashboard_container'; import { DashboardContainer, DashboardReactContextValue } from '../dashboard_container'; diff --git a/src/plugins/dashboard/public/application/embeddable/types.ts b/src/plugins/dashboard/public/application/embeddable/types.ts index 6d0221cb10e8..66cdd22ed6bd 100644 --- a/src/plugins/dashboard/public/application/embeddable/types.ts +++ b/src/plugins/dashboard/public/application/embeddable/types.ts @@ -17,18 +17,11 @@ * under the License. */ import { SavedObjectEmbeddableInput } from 'src/plugins/embeddable/public'; +import { GridData } from '../../../common'; import { PanelState, EmbeddableInput } from '../../embeddable_plugin'; export type PanelId = string; export type SavedObjectId = string; -export interface GridData { - w: number; - h: number; - x: number; - y: number; - i: string; -} - export interface DashboardPanelState< TEmbeddableInput extends EmbeddableInput | SavedObjectEmbeddableInput = SavedObjectEmbeddableInput > extends PanelState { diff --git a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts index 8f8de3663518..f4d97578adeb 100644 --- a/src/plugins/dashboard/public/application/lib/migrate_app_state.ts +++ b/src/plugins/dashboard/public/application/lib/migrate_app_state.ts @@ -22,18 +22,16 @@ import { i18n } from '@kbn/i18n'; import { METRIC_TYPE } from '@kbn/analytics'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; +import { DashboardAppState, SavedDashboardPanel } from '../../types'; import { - DashboardAppState, + migratePanelsTo730, SavedDashboardPanelTo60, SavedDashboardPanel730ToLatest, SavedDashboardPanel610, SavedDashboardPanel630, SavedDashboardPanel640To720, SavedDashboardPanel620, - SavedDashboardPanel, -} from '../../types'; -// should be moved in src/plugins/dashboard/common right after https://github.com/elastic/kibana/pull/61895 is merged -import { migratePanelsTo730 } from '../../../../../legacy/core_plugins/kibana/public/dashboard/migrations/migrate_to_730_panels'; +} from '../../../common'; /** * Attempts to migrate the state stored in the URL into the latest version of it. diff --git a/src/plugins/dashboard/public/bwc/index.ts b/src/plugins/dashboard/public/bwc/index.ts deleted file mode 100644 index d8f7b5091eb8..000000000000 --- a/src/plugins/dashboard/public/bwc/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export * from './types'; diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index ca0ea0293b07..44733499cdcb 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -20,29 +20,6 @@ import { PluginInitializerContext } from '../../../core/public'; import { DashboardPlugin } from './plugin'; -/** - * These types can probably be internal once all of dashboard app is migrated into this plugin. Right - * now, migrations are still in legacy land. - */ -export { - DashboardDoc730ToLatest, - DashboardDoc700To720, - RawSavedDashboardPanelTo60, - RawSavedDashboardPanel610, - RawSavedDashboardPanel620, - RawSavedDashboardPanel630, - RawSavedDashboardPanel640To720, - RawSavedDashboardPanel730ToLatest, - DashboardDocPre700, -} from './bwc'; -export { - SavedDashboardPanelTo60, - SavedDashboardPanel610, - SavedDashboardPanel620, - SavedDashboardPanel630, - SavedDashboardPanel730ToLatest, -} from './types'; - export { DashboardContainer, DashboardContainerInput, @@ -51,7 +28,6 @@ export { // Types below here can likely be made private when dashboard app moved into this NP plugin. DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT, - GridData, } from './application'; export { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index 7bccd3de6eca..c3008913dc31 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -19,14 +19,7 @@ import { Query, Filter } from 'src/plugins/data/public'; import { SavedObject as SavedObjectType, SavedObjectAttributes } from 'src/core/public'; -import { - RawSavedDashboardPanelTo60, - RawSavedDashboardPanel610, - RawSavedDashboardPanel620, - RawSavedDashboardPanel630, - RawSavedDashboardPanel640To720, - RawSavedDashboardPanel730ToLatest, -} from './bwc'; +import { SavedDashboardPanel730ToLatest } from '../common'; import { ViewMode } from './embeddable_plugin'; export interface DashboardCapabilities { @@ -83,55 +76,6 @@ export type NavAction = (anchorElement?: any) => void; */ export type SavedDashboardPanel = SavedDashboardPanel730ToLatest; -// id becomes optional starting in 7.3.0 -export type SavedDashboardPanel730ToLatest = Pick< - RawSavedDashboardPanel730ToLatest, - Exclude -> & { - readonly id?: string; - readonly type: string; -}; - -export type SavedDashboardPanel640To720 = Pick< - RawSavedDashboardPanel640To720, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel630 = Pick< - RawSavedDashboardPanel630, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel620 = Pick< - RawSavedDashboardPanel620, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanel610 = Pick< - RawSavedDashboardPanel610, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - -export type SavedDashboardPanelTo60 = Pick< - RawSavedDashboardPanelTo60, - Exclude -> & { - readonly id: string; - readonly type: string; -}; - export interface DashboardAppState { panels: SavedDashboardPanel[]; fullScreenMode: boolean; diff --git a/src/plugins/dashboard/server/saved_objects/dashboard.ts b/src/plugins/dashboard/server/saved_objects/dashboard.ts index 2f5523e2d24c..0d2accd66e20 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard.ts @@ -32,12 +32,12 @@ export const dashboardSavedObjectType: SavedObjectsType = { return obj.attributes.title; }, getEditUrl(obj) { - return `/management/kibana/objects/savedVisualizations/${encodeURIComponent(obj.id)}`; + return `/management/kibana/objects/savedDashboards/${encodeURIComponent(obj.id)}`; }, getInAppUrl(obj) { return { - path: `/app/kibana#/visualize/edit/${encodeURIComponent(obj.id)}`, - uiCapabilitiesPath: 'visualize.show', + path: `/app/kibana#/dashboard/${encodeURIComponent(obj.id)}`, + uiCapabilitiesPath: 'dashboard.show', }; }, }, diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts index 26f8278cd3d4..e7f84ad1fe51 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.test.ts @@ -17,1447 +17,431 @@ * under the License. */ -import { visualizationSavedObjectTypeMigrations } from './visualization_migrations'; -import { SavedObjectMigrationContext, SavedObjectMigrationFn } from 'kibana/server'; - -const savedObjectMigrationContext = (null as unknown) as SavedObjectMigrationContext; - -describe('migration visualization', () => { - describe('6.7.2', () => { - const migrate = (doc: any) => - visualizationSavedObjectTypeMigrations['6.7.2']( - doc as Parameters[0], - savedObjectMigrationContext - ); - let doc: any; - - describe('date histogram time zone removal', () => { - beforeEach(() => { - doc = { - attributes: { - visState: JSON.stringify({ - aggs: [ - { - enabled: true, - id: '1', - params: { - // Doesn't make much sense but we want to test it's not removing it from anything else - time_zone: 'Europe/Berlin', - }, - schema: 'metric', - type: 'count', - }, - { - enabled: true, - id: '2', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - time_zone: 'Europe/Berlin', - interval: 'auto', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - schema: 'segment', - type: 'date_histogram', - }, - { - enabled: true, - id: '4', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'auto', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - schema: 'segment', - type: 'date_histogram', - }, - { - enabled: true, - id: '3', - params: { - customBucket: { - enabled: true, - id: '1-bucket', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'auto', - min_doc_count: 1, - time_zone: 'Europe/Berlin', - useNormalizedEsInterval: true, - }, - type: 'date_histogram', - }, - customMetric: { - enabled: true, - id: '1-metric', - params: {}, - type: 'count', - }, - }, - schema: 'metric', - type: 'max_bucket', - }, - ], - }), - }, - } as Parameters[0]; - }); - - it('should remove time_zone from date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - - expect(aggs[1]).not.toHaveProperty('params.time_zone'); - }); - - it('should not remove time_zone from non date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - - expect(aggs[0]).toHaveProperty('params.time_zone'); - }); - - it('should remove time_zone from nested aggregations', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - - expect(aggs[3]).not.toHaveProperty('params.customBucket.params.time_zone'); - }); - - it('should not fail on date histograms without a time_zone', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - - expect(aggs[2]).not.toHaveProperty('params.time_zone'); - }); - - it('should be able to apply the migration twice, since we need it for 6.7.2 and 7.0.1', () => { - const migratedDoc = migrate(doc); - const aggs = JSON.parse(migratedDoc.attributes.visState).aggs; - - expect(aggs[1]).not.toHaveProperty('params.time_zone'); - expect(aggs[0]).toHaveProperty('params.time_zone'); - expect(aggs[3]).not.toHaveProperty('params.customBucket.params.time_zone'); - expect(aggs[2]).not.toHaveProperty('params.time_zone'); - }); - - it('should migrate obsolete match_all query', () => { - const migratedDoc = migrate({ - ...doc, - attributes: { - ...doc.attributes, - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - query: { - match_all: {}, - }, - }), - }, - }, - }); - const migratedSearchSource = JSON.parse( - migratedDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON - ); - - expect(migratedSearchSource).toEqual({ - query: { - query: '', - language: 'kuery', - }, - }); - }); - }); - }); +import { dashboardSavedObjectTypeMigrations as migrations } from './dashboard_migrations'; +describe('dashboard', () => { describe('7.0.0', () => { - const migrate = (doc: any) => - visualizationSavedObjectTypeMigrations['7.0.0']( - doc as Parameters[0], - savedObjectMigrationContext - ); - - const generateDoc = (type: any, aggs: any) => ({ - attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: JSON.stringify({ type, aggs }), - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - references: [], - }); + const migration = migrations['7.0.0']; - it('does not throw error on empty object', () => { - const migratedDoc = migrate({ - attributes: { - visState: '{}', - }, - }); - expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "visState": "{}", - }, - "references": Array [], - } - `); + test('skips error on empty object', () => { + expect(migration({})).toMatchInlineSnapshot(` +Object { + "references": Array [], +} +`); }); - it('skips errors when searchSourceJSON is null', () => { + test('skips errors when searchSourceJSON is null', () => { const doc = { id: '1', - type: 'visualization', + type: 'dashboard', attributes: { - visState: '{}', kibanaSavedObjectMeta: { searchSourceJSON: null, }, - savedSearchId: '123', + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - const migratedDoc = migrate(doc); - + const migratedDoc = migration(doc); expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": null, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", - } - `); - }); - - it('skips errors when searchSourceJSON is undefined', () => { +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": null, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips errors when searchSourceJSON is undefined', () => { const doc = { id: '1', - type: 'visualization', + type: 'dashboard', attributes: { - visState: '{}', kibanaSavedObjectMeta: { searchSourceJSON: undefined, }, - savedSearchId: '123', + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - const migratedDoc = migrate(doc); - + const migratedDoc = migration(doc); expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": undefined, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", - } - `); - }); - - it('skips error when searchSourceJSON is not a string', () => { +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": undefined, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips error when searchSourceJSON is not a string', () => { const doc = { id: '1', - type: 'visualization', + type: 'dashboard', attributes: { - visState: '{}', kibanaSavedObjectMeta: { searchSourceJSON: 123, }, - savedSearchId: '123', + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - - expect(migrate(doc)).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": 123, - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", - } - `); - }); - - it('skips error when searchSourceJSON is invalid json', () => { + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": 123, + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips error when searchSourceJSON is invalid json', () => { const doc = { id: '1', - type: 'visualization', + type: 'dashboard', attributes: { - visState: '{}', kibanaSavedObjectMeta: { searchSourceJSON: '{abc123}', }, - savedSearchId: '123', + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - - expect(migrate(doc)).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{abc123}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", - } - `); - }); - - it('skips error when "index" and "filter" is missing from searchSourceJSON', () => { + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{abc123}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips error when "index" and "filter" is missing from searchSourceJSON', () => { const doc = { id: '1', - type: 'visualization', + type: 'dashboard', attributes: { - visState: '{}', kibanaSavedObjectMeta: { searchSourceJSON: JSON.stringify({ bar: true }), }, - savedSearchId: '123', + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - const migratedDoc = migrate(doc); - + const migratedDoc = migration(doc); expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", - } - `); - }); - - it('extracts "index" attribute from doc', () => { +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('extracts "index" attribute from doc', () => { const doc = { id: '1', - type: 'visualization', + type: 'dashboard', attributes: { - visState: '{}', kibanaSavedObjectMeta: { searchSourceJSON: JSON.stringify({ bar: true, index: 'pattern*' }), }, - savedSearchId: '123', + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - const migratedDoc = migrate(doc); - + const migratedDoc = migration(doc); expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "kibanaSavedObjectMeta.searchSourceJSON.index", - "type": "index-pattern", - }, - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", - } - `); - }); - - it('extracts index patterns from the filter', () => { +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.index\\"}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "pattern*", + "name": "kibanaSavedObjectMeta.searchSourceJSON.index", + "type": "index-pattern", + }, + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('extracts index patterns from filter', () => { const doc = { id: '1', - type: 'visualization', + type: 'dashboard', attributes: { - visState: '{}', kibanaSavedObjectMeta: { searchSourceJSON: JSON.stringify({ bar: true, filter: [ { - meta: { index: 'my-index', foo: true }, + meta: { + foo: true, + index: 'my-index', + }, }, ], }), }, - savedSearchId: '123', + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - const migratedDoc = migrate(doc); + const migratedDoc = migration(doc); expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "my-index", - "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", - "type": "index-pattern", - }, - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - "type": "visualization", - } - `); - }); - - it('extracts index patterns from controls', () => { +Object { + "attributes": Object { + "kibanaSavedObjectMeta": Object { + "searchSourceJSON": "{\\"bar\\":true,\\"filter\\":[{\\"meta\\":{\\"foo\\":true,\\"indexRefName\\":\\"kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index\\"}}]}", + }, + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "my-index", + "name": "kibanaSavedObjectMeta.searchSourceJSON.filter[0].meta.index", + "type": "index-pattern", + }, + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], + "type": "dashboard", +} +`); + }); + + test('skips error when panelsJSON is not a string', () => { const doc = { id: '1', - type: 'visualization', attributes: { - foo: true, - visState: JSON.stringify({ - bar: false, - params: { - controls: [ - { - bar: true, - indexPattern: 'pattern*', - }, - { - foo: true, - }, - ], - }, - }), + panelsJSON: 123, }, }; - const migratedDoc = migrate(doc); - - expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "foo": true, - "visState": "{\\"bar\\":false,\\"params\\":{\\"controls\\":[{\\"bar\\":true,\\"indexPatternRefName\\":\\"control_0_index_pattern\\"},{\\"foo\\":true}]}}", - }, - "id": "1", - "references": Array [ - Object { - "id": "pattern*", - "name": "control_0_index_pattern", - "type": "index-pattern", - }, - ], - "type": "visualization", - } - `); - }); - - it('skips extracting savedSearchId when missing', () => { + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": 123, + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('skips error when panelsJSON is not valid JSON', () => { const doc = { id: '1', attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, + panelsJSON: '{123abc}', }, }; - const migratedDoc = migrate(doc); - - expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "visState": "{}", - }, - "id": "1", - "references": Array [], - } - `); - }); - - it('extract savedSearchId from doc', () => { + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "{123abc}", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('skips panelsJSON when its not an array', () => { const doc = { id: '1', attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - savedSearchId: '123', + panelsJSON: '{}', }, }; - const migratedDoc = migrate(doc); - - expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "savedSearchRefName": "search_0", - "visState": "{}", - }, - "id": "1", - "references": Array [ - Object { - "id": "123", - "name": "search_0", - "type": "search", - }, - ], - } - `); - }); - - it('delete savedSearchId when empty string in doc', () => { + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "{}", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('skips error when a panel is missing "type" attribute', () => { const doc = { id: '1', attributes: { - visState: '{}', - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - savedSearchId: '', + panelsJSON: '[{"id":"123"}]', }, }; - const migratedDoc = migrate(doc); - - expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "kibanaSavedObjectMeta": Object { - "searchSourceJSON": "{}", - }, - "visState": "{}", - }, - "id": "1", - "references": Array [], - } - `); - }); - - it('should return a new object if vis is table and has multiple split aggs', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - { - id: '3', - schema: 'split', - params: { hey: 'ya', row: false }, - }, - ]; - const tableDoc = generateDoc('table', aggs); - const expected = tableDoc; - const actual = migrate(tableDoc); - - expect(actual).not.toEqual(expected); - }); - - it('should not touch any vis that is not table', () => { - const pieDoc = generateDoc('pie', []); - const expected = pieDoc; - const actual = migrate(pieDoc); - - expect(actual).toEqual(expected); - }); - - it('should not change values in any vis that is not table', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - { - id: '3', - schema: 'segment', - params: { hey: 'ya' }, - }, - ]; - const pieDoc = generateDoc('pie', aggs); - const expected = pieDoc; - const actual = migrate(pieDoc); - - expect(actual).toEqual(expected); - }); - - it('should not touch table vis if there are not multiple split aggs', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - ]; - const tableDoc = generateDoc('table', aggs); - const expected = tableDoc; - const actual = migrate(tableDoc); - - expect(actual).toEqual(expected); - }); - - it('should change all split aggs to `bucket` except the first', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - { - id: '3', - schema: 'split', - params: { hey: 'ya', row: false }, - }, - { - id: '4', - schema: 'bucket', - params: { heyyy: 'yaaa' }, - }, - ]; - const expected = ['metric', 'split', 'bucket', 'bucket']; - const migrated = migrate(generateDoc('table', aggs)); - const actual = JSON.parse(migrated.attributes.visState); - - expect(actual.aggs.map((agg: any) => agg.schema)).toEqual(expected); - }); - - it('should remove `rows` param from any aggs that are not `split`', () => { - const aggs = [ - { - id: '1', - schema: 'metric', - params: {}, - }, - { - id: '2', - schema: 'split', - params: { foo: 'bar', row: true }, - }, - { - id: '3', - schema: 'split', - params: { hey: 'ya', row: false }, - }, - ]; - const expected = [{}, { foo: 'bar', row: true }, { hey: 'ya' }]; - const migrated = migrate(generateDoc('table', aggs)); - const actual = JSON.parse(migrated.attributes.visState); - - expect(actual.aggs.map((agg: any) => agg.params)).toEqual(expected); - }); - - it('should throw with a reference to the doc name if something goes wrong', () => { + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "[{\\"id\\":\\"123\\"}]", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('skips error when a panel is missing "id" attribute', () => { const doc = { + id: '1', attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: '!/// Intentionally malformed JSON ///!', - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, + panelsJSON: '[{"type":"visualization"}]', }, }; - expect(() => migrate(doc)).toThrowError(/My Vis/); - }); - }); - - describe('7.2.0', () => { - describe('date histogram custom interval removal', () => { - const migrate = (doc: any) => - visualizationSavedObjectTypeMigrations['7.2.0']( - doc as Parameters[0], - savedObjectMigrationContext - ); - let doc: any; - - beforeEach(() => { - doc = { - attributes: { - visState: JSON.stringify({ - aggs: [ - { - enabled: true, - id: '1', - params: { - customInterval: '1h', - }, - schema: 'metric', - type: 'count', - }, - { - enabled: true, - id: '2', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'auto', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - schema: 'segment', - type: 'date_histogram', - }, - { - enabled: true, - id: '4', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'custom', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - schema: 'segment', - type: 'date_histogram', - }, - { - enabled: true, - id: '3', - params: { - customBucket: { - enabled: true, - id: '1-bucket', - params: { - customInterval: '2h', - drop_partials: false, - extended_bounds: {}, - field: 'timestamp', - interval: 'custom', - min_doc_count: 1, - useNormalizedEsInterval: true, - }, - type: 'date_histogram', - }, - customMetric: { - enabled: true, - id: '1-metric', - params: {}, - type: 'count', - }, - }, - schema: 'metric', - type: 'max_bucket', - }, - ], - }), - }, - }; - }); - - it('should remove customInterval from date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const { aggs } = JSON.parse(migratedDoc.attributes.visState); - - expect(aggs[1]).not.toHaveProperty('params.customInterval'); - }); - - it('should not change interval from date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const { aggs } = JSON.parse(migratedDoc.attributes.visState); - - expect(aggs[1].params.interval).toBe( - JSON.parse(doc.attributes.visState).aggs[1].params.interval - ); - }); - - it('should not remove customInterval from non date_histogram aggregations', () => { - const migratedDoc = migrate(doc); - const { aggs } = JSON.parse(migratedDoc.attributes.visState); - - expect(aggs[0]).toHaveProperty('params.customInterval'); - }); - - it('should set interval with customInterval value and remove customInterval when interval equals "custom"', () => { - const migratedDoc = migrate(doc); - const { aggs } = JSON.parse(migratedDoc.attributes.visState); - - expect(aggs[2].params.interval).toBe( - JSON.parse(doc.attributes.visState).aggs[2].params.customInterval - ); - expect(aggs[2]).not.toHaveProperty('params.customInterval'); - }); - - it('should remove customInterval from nested aggregations', () => { - const migratedDoc = migrate(doc); - const { aggs } = JSON.parse(migratedDoc.attributes.visState); - - expect(aggs[3]).not.toHaveProperty('params.customBucket.params.customInterval'); - }); - - it('should remove customInterval from nested aggregations and set interval with customInterval value', () => { - const migratedDoc = migrate(doc); - const { aggs } = JSON.parse(migratedDoc.attributes.visState); - - expect(aggs[3].params.customBucket.params.interval).toBe( - JSON.parse(doc.attributes.visState).aggs[3].params.customBucket.params.customInterval - ); - expect(aggs[3]).not.toHaveProperty('params.customBucket.params.customInterval'); - }); - - it('should not fail on date histograms without a customInterval', () => { - const migratedDoc = migrate(doc); - const { aggs } = JSON.parse(migratedDoc.attributes.visState); - - expect(aggs[3]).not.toHaveProperty('params.customInterval'); - }); - }); - }); - - describe('7.3.0', () => { - const logMsgArr: string[] = []; - const logger = ({ - log: { - warn: (msg: string) => logMsgArr.push(msg), - }, - } as unknown) as SavedObjectMigrationContext; - - const migrate = (doc: any) => - visualizationSavedObjectTypeMigrations['7.3.0']( - doc as Parameters[0], - logger - ); - - it('migrates type = gauge verticalSplit: false to alignment: vertical', () => { - const migratedDoc = migrate({ - attributes: { - visState: JSON.stringify({ type: 'gauge', params: { gauge: { verticalSplit: false } } }), - }, - }); - expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"horizontal\\"}}}", - }, - } - `); - }); - - it('migrates type = gauge verticalSplit: false to alignment: horizontal', () => { - const migratedDoc = migrate({ - attributes: { - visState: JSON.stringify({ type: 'gauge', params: { gauge: { verticalSplit: true } } }), - }, - }); - - expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\",\\"params\\":{\\"gauge\\":{\\"alignment\\":\\"vertical\\"}}}", - }, - } - `); - }); - - it('doesnt migrate type = gauge containing invalid visState object, adds message to log', () => { - const migratedDoc = migrate({ - attributes: { - visState: JSON.stringify({ type: 'gauge' }), - }, - }); - - expect(migratedDoc).toMatchInlineSnapshot(` - Object { - "attributes": Object { - "visState": "{\\"type\\":\\"gauge\\"}", - }, - } - `); - expect(logMsgArr).toMatchInlineSnapshot(` - Array [ - "Exception @ migrateGaugeVerticalSplitToAlignment! TypeError: Cannot read property 'gauge' of undefined", - "Exception @ migrateGaugeVerticalSplitToAlignment! Payload: {\\"type\\":\\"gauge\\"}", - ] - `); - }); - - describe('filters agg query migration', () => { + expect(migration(doc)).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "[{\\"type\\":\\"visualization\\"}]", + }, + "id": "1", + "references": Array [], +} +`); + }); + + test('extract panel references from doc', () => { const doc = { + id: '1', attributes: { - visState: JSON.stringify({ - aggs: [ - { - type: 'filters', - params: { - filters: [ - { - input: { - query: 'response:200', - }, - label: '', - }, - { - input: { - query: 'response:404', - }, - label: 'bad response', - }, - { - input: { - query: { - exists: { - field: 'phpmemory', - }, - }, - }, - label: '', - }, - ], - }, - }, - ], - }), - }, - }; - - it('should add language property to filters without one, assuming lucene', () => { - const migrationResult = migrate(doc); - - expect(migrationResult).toEqual({ - attributes: { - visState: JSON.stringify({ - aggs: [ - { - type: 'filters', - params: { - filters: [ - { - input: { - query: 'response:200', - language: 'lucene', - }, - label: '', - }, - { - input: { - query: 'response:404', - language: 'lucene', - }, - label: 'bad response', - }, - { - input: { - query: { - exists: { - field: 'phpmemory', - }, - }, - language: 'lucene', - }, - label: '', - }, - ], - }, - }, - ], - }), - }, - }); - }); - }); - - describe('replaceMovAvgToMovFn()', () => { - let doc: any; - - beforeEach(() => { - doc = { - attributes: { - title: 'VIS', - visState: `{"title":"VIS","type":"metrics","params":{"id":"61ca57f0-469d-11e7-af02-69e470af7417", - "type":"timeseries","series":[{"id":"61ca57f1-469d-11e7-af02-69e470af7417","color":"rgba(0,156,224,1)", - "split_mode":"terms","metrics":[{"id":"61ca57f2-469d-11e7-af02-69e470af7417","type":"count", - "numerator":"FlightDelay:true"},{"settings":"","minimize":0,"window":5,"model": - "holt_winters","id":"23054fe0-8915-11e9-9b86-d3f94982620f","type":"moving_average","field": - "61ca57f2-469d-11e7-af02-69e470af7417","predict":1}],"separate_axis":0,"axis_position":"right", - "formatter":"number","chart_type":"line","line_width":"2","point_size":"0","fill":0.5,"stacked":"none", - "label":"Percent Delays","terms_size":"2","terms_field":"OriginCityName"}],"time_field":"timestamp", - "index_pattern":"kibana_sample_data_flights","interval":">=12h","axis_position":"left","axis_formatter": - "number","show_legend":1,"show_grid":1,"annotations":[{"fields":"FlightDelay,Cancelled,Carrier", - "template":"{{Carrier}}: Flight Delayed and Cancelled!","index_pattern":"kibana_sample_data_flights", - "query_string":"FlightDelay:true AND Cancelled:true","id":"53b7dff0-4c89-11e8-a66a-6989ad5a0a39", - "color":"rgba(0,98,177,1)","time_field":"timestamp","icon":"fa-exclamation-triangle", - "ignore_global_filters":1,"ignore_panel_filters":1,"hidden":true}],"legend_position":"bottom", - "axis_scale":"normal","default_index_pattern":"kibana_sample_data_flights","default_timefield":"timestamp"}, - "aggs":[]}`, - }, - migrationVersion: { - visualization: '7.2.0', - }, - type: 'visualization', - }; - }); - - test('should add some necessary moving_fn fields', () => { - const migratedDoc = migrate(doc); - const visState = JSON.parse(migratedDoc.attributes.visState); - const metric = visState.params.series[0].metrics[1]; - - expect(metric).toHaveProperty('model_type'); - expect(metric).toHaveProperty('alpha'); - expect(metric).toHaveProperty('beta'); - expect(metric).toHaveProperty('gamma'); - expect(metric).toHaveProperty('period'); - expect(metric).toHaveProperty('multiplicative'); - }); - }); - }); - - describe('7.3.0 tsvb', () => { - const migrate = (doc: any) => - visualizationSavedObjectTypeMigrations['7.3.0']( - doc as Parameters[0], - savedObjectMigrationContext - ); - - const generateDoc = (params: any) => ({ - attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: JSON.stringify({ params }), - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - }); - it('should change series item filters from a string into an object', () => { - const params = { type: 'metric', series: [{ filter: 'Filter Bytes Test:>1000' }] }; - const testDoc1 = generateDoc(params); - const migratedTestDoc1 = migrate(testDoc1); - const series = JSON.parse(migratedTestDoc1.attributes.visState).params.series; - - expect(series[0].filter).toHaveProperty('query'); - expect(series[0].filter).toHaveProperty('language'); - }); - it('should not change a series item filter string in the object after migration', () => { - const markdownParams = { - type: 'markdown', - series: [ - { - filter: 'Filter Bytes Test:>1000', - split_filters: [{ filter: 'bytes:>1000' }], - }, - ], - }; - const markdownDoc = generateDoc(markdownParams); - const migratedMarkdownDoc = migrate(markdownDoc); - const markdownSeries = JSON.parse(migratedMarkdownDoc.attributes.visState).params.series; - - expect(markdownSeries[0].filter.query).toBe( - JSON.parse(markdownDoc.attributes.visState).params.series[0].filter - ); - expect(markdownSeries[0].split_filters[0].filter.query).toBe( - JSON.parse(markdownDoc.attributes.visState).params.series[0].split_filters[0].filter - ); - }); - - it('should change series item filters from a string into an object for all filters', () => { - const params = { - type: 'timeseries', - filter: 'bytes:>1000', - series: [ - { - filter: 'Filter Bytes Test:>1000', - split_filters: [{ filter: 'bytes:>1000' }], - }, - ], - annotations: [{ query_string: 'bytes:>1000' }], - }; - const timeSeriesDoc = generateDoc(params); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - - expect(Object.keys(timeSeriesParams.series[0].filter)).toEqual( - expect.arrayContaining(['query', 'language']) - ); - expect(Object.keys(timeSeriesParams.series[0].split_filters[0].filter)).toEqual( - expect.arrayContaining(['query', 'language']) - ); - expect(Object.keys(timeSeriesParams.annotations[0].query_string)).toEqual( - expect.arrayContaining(['query', 'language']) - ); - }); - - it('should not fail on a metric visualization without a filter in a series item', () => { - const params = { type: 'metric', series: [{}, {}, {}] }; - const testDoc1 = generateDoc(params); - const migratedTestDoc1 = migrate(testDoc1); - const series = JSON.parse(migratedTestDoc1.attributes.visState).params.series; - - expect(series[2]).not.toHaveProperty('filter.query'); - }); - - it('should not migrate a visualization of unknown type', () => { - const params = { type: 'unknown', series: [{ filter: 'foo:bar' }] }; - const doc = generateDoc(params); - const migratedDoc = migrate(doc); - const series = JSON.parse(migratedDoc.attributes.visState).params.series; - - expect(series[0].filter).toEqual(params.series[0].filter); - }); - }); - - describe('7.3.1', () => { - const migrate = (doc: any) => - visualizationSavedObjectTypeMigrations['7.3.1']( - doc as Parameters[0], - savedObjectMigrationContext - ); - - it('should migrate filters agg query string queries', () => { - const state = { - aggs: [ - { type: 'count', params: {} }, - { - type: 'filters', - params: { - filters: [ - { - input: { - query: { - query_string: { query: 'machine.os.keyword:"win 8"' }, - }, - }, - }, - ], - }, - }, - ], - }; - const expected = { - aggs: [ - { type: 'count', params: {} }, - { - type: 'filters', - params: { - filters: [{ input: { query: 'machine.os.keyword:"win 8"' } }], - }, - }, - ], - }; - const migratedDoc = migrate({ attributes: { visState: JSON.stringify(state) } }); - - expect(migratedDoc).toEqual({ attributes: { visState: JSON.stringify(expected) } }); - }); - }); - - describe('7.4.2 tsvb split_filters migration', () => { - const migrate = (doc: any) => - visualizationSavedObjectTypeMigrations['7.4.2']( - doc as Parameters[0], - savedObjectMigrationContext - ); - const generateDoc = (params: any) => ({ - attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: JSON.stringify({ params }), - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - }); - - it('should change series item filters from a string into an object for all filters', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [ - { - split_filters: [{ filter: 'bytes:>1000' }], - }, - ], - }; - const timeSeriesDoc = generateDoc(params); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - - expect(Object.keys(timeSeriesParams.filter)).toEqual( - expect.arrayContaining(['query', 'language']) - ); - expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual({ - query: 'bytes:>1000', - language: 'lucene', - }); - }); - - it('should change series item split filters when there is no filter item', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [ - { - split_filters: [{ filter: 'bytes:>1000' }], - }, - ], - annotations: [ - { - query_string: { - query: 'bytes:>1000', - language: 'lucene', - }, - }, - ], - }; - const timeSeriesDoc = generateDoc(params); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - - expect(timeSeriesParams.series[0].split_filters[0].filter).toEqual({ - query: 'bytes:>1000', - language: 'lucene', - }); - }); - - it('should not convert split_filters to objects if there are no split filter filters', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', + panelsJSON: + '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, - series: [ - { - split_filters: [], - }, - ], }; - const timeSeriesDoc = generateDoc(params); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - - expect(timeSeriesParams.series[0].split_filters).not.toHaveProperty('query'); - }); - - it('should do nothing if a split_filter is already a query:language object', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [ - { - split_filters: [ - { - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - }, - ], - }, - ], - annotations: [ - { - query_string: { - query: 'bytes:>1000', - language: 'lucene', - }, - }, - ], - }; - const timeSeriesDoc = generateDoc(params); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const timeSeriesParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - - expect(timeSeriesParams.series[0].split_filters[0].filter.query).toEqual('bytes:>1000'); - expect(timeSeriesParams.series[0].split_filters[0].filter.language).toEqual('lucene'); - }); - }); - - describe('7.7.0 tsvb opperator typo migration', () => { - const migrate = (doc: any) => - visualizationSavedObjectTypeMigrations['7.7.0']( - doc as Parameters[0], - savedObjectMigrationContext - ); - const generateDoc = (params: any) => ({ - attributes: { - title: 'My Vis', - description: 'This is my super cool vis.', - visState: JSON.stringify({ params }), - uiStateJSON: '{}', - version: 1, - kibanaSavedObjectMeta: { - searchSourceJSON: '{}', - }, - }, - }); - - it('should remove the misspelled opperator key if it exists', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [], - gauge_color_rules: [ - { - value: 0, - id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', - gauge: 'rgba(69,39,217,1)', - opperator: 'lt', - }, - ], - }; - const timeSeriesDoc = generateDoc(params); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - - expect(migratedParams.gauge_color_rules[0]).toMatchInlineSnapshot(` - Object { - "gauge": "rgba(69,39,217,1)", - "id": "020e3d50-75a6-11ea-8f61-71579ff7f64d", - "opperator": "lt", - "value": 0, - } - `); - }); - - it('should not change color rules with the correct spelling', () => { - const params = { - type: 'timeseries', - filter: { - query: 'bytes:>1000', - language: 'lucene', - }, - series: [], - gauge_color_rules: [ - { - value: 0, - id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', - gauge: 'rgba(69,39,217,1)', - opperator: 'lt', - }, - { - value: 0, - id: '020e3d50-75a6-11ea-8f61-71579ff7f64d', - gauge: 'rgba(69,39,217,1)', - operator: 'lt', - }, - ], - }; - const timeSeriesDoc = generateDoc(params); - const migratedtimeSeriesDoc = migrate(timeSeriesDoc); - const migratedParams = JSON.parse(migratedtimeSeriesDoc.attributes.visState).params; - - expect(migratedParams.gauge_color_rules[1]).toEqual(params.gauge_color_rules[1]); + const migratedDoc = migration(doc); + expect(migratedDoc).toMatchInlineSnapshot(` +Object { + "attributes": Object { + "panelsJSON": "[{\\"foo\\":true,\\"panelRefName\\":\\"panel_0\\"},{\\"bar\\":true,\\"panelRefName\\":\\"panel_1\\"}]", + }, + "id": "1", + "references": Array [ + Object { + "id": "1", + "name": "panel_0", + "type": "visualization", + }, + Object { + "id": "2", + "name": "panel_1", + "type": "visualization", + }, + ], +} +`); }); }); }); diff --git a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts index f9feae485655..e5288afd81dc 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard_migrations.ts @@ -17,11 +17,17 @@ * under the License. */ +import { get, flow } from 'lodash'; + import { SavedObjectMigrationFn } from 'kibana/server'; -import { cloneDeep, get, omit, has, flow } from 'lodash'; -import { DEFAULT_QUERY_LANGUAGE } from '../../../data/common'; +import { migrations730 } from './migrations_730'; +import { migrateMatchAllQuery } from './migrate_match_all_query'; + +const migrations700: SavedObjectMigrationFn = doc => { + // Set new "references" attribute + doc.references = doc.references || []; -const migrateIndexPattern: SavedObjectMigrationFn = doc => { + // Migrate index pattern const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); if (typeof searchSourceJSON !== 'string') { return doc; @@ -33,8 +39,7 @@ const migrateIndexPattern: SavedObjectMigrationFn = doc => { // Let it go, the data is invalid and we'll leave it as is return doc; } - - if (searchSource.index && Array.isArray(doc.references)) { + if (searchSource.index) { searchSource.indexRefName = 'kibanaSavedObjectMeta.searchSourceJSON.index'; doc.references.push({ name: searchSource.indexRefName, @@ -45,7 +50,7 @@ const migrateIndexPattern: SavedObjectMigrationFn = doc => { } if (searchSource.filter) { searchSource.filter.forEach((filterRow: any, i: number) => { - if (!filterRow.meta || !filterRow.meta.index || !Array.isArray(doc.references)) { + if (!filterRow.meta || !filterRow.meta.index) { return; } filterRow.meta.indexRefName = `kibanaSavedObjectMeta.searchSourceJSON.filter[${i}].meta.index`; @@ -57,552 +62,37 @@ const migrateIndexPattern: SavedObjectMigrationFn = doc => { delete filterRow.meta.index; }); } - doc.attributes.kibanaSavedObjectMeta.searchSourceJSON = JSON.stringify(searchSource); - return doc; -}; - -// [TSVB] Migrate percentile-rank aggregation (value -> values) -const migratePercentileRankAggregation: SavedObjectMigrationFn = doc => { - const visStateJSON = get(doc, 'attributes.visState'); - let visState; - - if (visStateJSON) { - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - if (visState && visState.type === 'metrics') { - const series: any[] = get(visState, 'params.series') || []; - - series.forEach(part => { - (part.metrics || []).forEach((metric: any) => { - if (metric.type === 'percentile_rank' && has(metric, 'value')) { - metric.values = [metric.value]; - - delete metric.value; - } - }); - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } - return doc; -}; - -// [TSVB] Remove stale opperator key -const migrateOperatorKeyTypo: SavedObjectMigrationFn = doc => { - const visStateJSON = get(doc, 'attributes.visState'); - let visState; - - if (visStateJSON) { - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - if (visState && visState.type === 'metrics') { - const gaugeColorRules: any[] = get(visState, 'params.gauge_color_rules') || []; - - gaugeColorRules.forEach(colorRule => { - if (colorRule.opperator) { - delete colorRule.opperator; - } - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } - return doc; -}; - -// Migrate date histogram aggregation (remove customInterval) -const migrateDateHistogramAggregation: SavedObjectMigrationFn = doc => { - const visStateJSON = get(doc, 'attributes.visState'); - let visState; - - if (visStateJSON) { - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - - if (visState && visState.aggs) { - visState.aggs.forEach((agg: any) => { - if (agg.type === 'date_histogram' && agg.params) { - if (agg.params.interval === 'custom') { - agg.params.interval = agg.params.customInterval; - } - delete agg.params.customInterval; - } - - if ( - get(agg, 'params.customBucket.type', null) === 'date_histogram' && - agg.params.customBucket.params - ) { - if (agg.params.customBucket.params.interval === 'custom') { - agg.params.customBucket.params.interval = agg.params.customBucket.params.customInterval; - } - delete agg.params.customBucket.params.customInterval; - } - }); - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } - return doc; -}; - -const removeDateHistogramTimeZones: SavedObjectMigrationFn = doc => { - const visStateJSON = get(doc, 'attributes.visState'); - if (visStateJSON) { - let visState; - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - if (visState && visState.aggs) { - visState.aggs.forEach((agg: any) => { - // We're checking always for the existance of agg.params here. This should always exist, but better - // be safe then sorry during migrations. - if (agg.type === 'date_histogram' && agg.params) { - delete agg.params.time_zone; - } - - if ( - get(agg, 'params.customBucket.type', null) === 'date_histogram' && - agg.params.customBucket.params - ) { - delete agg.params.customBucket.params.time_zone; - } - }); - doc.attributes.visState = JSON.stringify(visState); - } - } - return doc; -}; - -// migrate gauge verticalSplit to alignment -// https://github.com/elastic/kibana/issues/34636 -const migrateGaugeVerticalSplitToAlignment: SavedObjectMigrationFn = (doc, logger) => { - const visStateJSON = get(doc, 'attributes.visState'); - - if (visStateJSON) { - try { - const visState = JSON.parse(visStateJSON); - if (visState && visState.type === 'gauge' && !visState.params.gauge.alignment) { - visState.params.gauge.alignment = visState.params.gauge.verticalSplit - ? 'vertical' - : 'horizontal'; - delete visState.params.gauge.verticalSplit; - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } catch (e) { - logger.log.warn(`Exception @ migrateGaugeVerticalSplitToAlignment! ${e}`); - logger.log.warn(`Exception @ migrateGaugeVerticalSplitToAlignment! Payload: ${visStateJSON}`); - } - } - return doc; -}; -// Migrate filters (string -> { query: string, language: lucene }) -/* - Enabling KQL in TSVB causes problems with savedObject visualizations when these are saved with filters. - In a visualisation type of saved object, if the visState param is of type metric, the filter is saved as a string that is not interpretted correctly as a lucene query in the visualization itself. - We need to transform the filter string into an object containing the original string as a query and specify the query language as lucene. - For Metrics visualizations (param.type === "metric"), filters can be applied to each series object in the series array within the SavedObject.visState.params object. - Path to the series array is thus: - attributes.visState. -*/ -const transformFilterStringToQueryObject: SavedObjectMigrationFn = (doc, logger) => { - // Migrate filters - // If any filters exist and they are a string, we assume it to be lucene and transform the filter into an object accordingly - const newDoc = cloneDeep(doc); - const visStateJSON = get(doc, 'attributes.visState'); - if (visStateJSON) { - let visState; - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // let it go, the data is invalid and we'll leave it as is - } - if (visState) { - const visType = get(visState, 'params.type'); - const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; - if (tsvbTypes.indexOf(visType) === -1) { - // skip - return doc; - } - // migrate the params fitler - const params: any = get(visState, 'params'); - if (params.filter && typeof params.filter === 'string') { - const paramsFilterObject = { - query: params.filter, - language: 'lucene', - }; - params.filter = paramsFilterObject; - } - - // migrate the annotations query string: - const annotations: any[] = get(visState, 'params.annotations') || []; - annotations.forEach(item => { - if (!item.query_string) { - // we don't need to transform anything if there isn't a filter at all - return; - } - if (typeof item.query_string === 'string') { - const itemQueryStringObject = { - query: item.query_string, - language: 'lucene', - }; - item.query_string = itemQueryStringObject; - } - }); - // migrate the series filters - const series: any[] = get(visState, 'params.series') || []; - - series.forEach(item => { - if (!item.filter) { - // we don't need to transform anything if there isn't a filter at all - return; - } - // series item filter - if (typeof item.filter === 'string') { - const itemfilterObject = { - query: item.filter, - language: 'lucene', - }; - item.filter = itemfilterObject; - } - // series item split filters filter - if (item.split_filters) { - const splitFilters: any[] = get(item, 'split_filters') || []; - splitFilters.forEach(filter => { - if (!filter.filter) { - // we don't need to transform anything if there isn't a filter at all - return; - } - if (typeof filter.filter === 'string') { - const filterfilterObject = { - query: filter.filter, - language: 'lucene', - }; - filter.filter = filterfilterObject; - } - }); - } - }); - newDoc.attributes.visState = JSON.stringify(visState); - } - } - return newDoc; -}; - -const transformSplitFiltersStringToQueryObject: SavedObjectMigrationFn = doc => { - // Migrate split_filters in TSVB objects that weren't migrated in 7.3 - // If any filters exist and they are a string, we assume them to be lucene syntax and transform the filter into an object accordingly - const newDoc = cloneDeep(doc); - const visStateJSON = get(doc, 'attributes.visState'); - if (visStateJSON) { - let visState; - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // let it go, the data is invalid and we'll leave it as is - } - if (visState) { - const visType = get(visState, 'params.type'); - const tsvbTypes = ['metric', 'markdown', 'top_n', 'gauge', 'table', 'timeseries']; - if (tsvbTypes.indexOf(visType) === -1) { - // skip - return doc; - } - // migrate the series split_filter filters - const series: any[] = get(visState, 'params.series') || []; - series.forEach(item => { - // series item split filters filter - if (item.split_filters) { - const splitFilters: any[] = get(item, 'split_filters') || []; - if (splitFilters.length > 0) { - // only transform split_filter filters if we have filters - splitFilters.forEach(filter => { - if (typeof filter.filter === 'string') { - const filterfilterObject = { - query: filter.filter, - language: 'lucene', - }; - filter.filter = filterfilterObject; - } - }); - } - } - }); - newDoc.attributes.visState = JSON.stringify(visState); - } + // Migrate panels + const panelsJSON = get(doc, 'attributes.panelsJSON'); + if (typeof panelsJSON !== 'string') { + return doc; } - return newDoc; -}; - -const migrateFiltersAggQuery: SavedObjectMigrationFn = doc => { - const visStateJSON = get(doc, 'attributes.visState'); - - if (visStateJSON) { - try { - const visState = JSON.parse(visStateJSON); - - if (visState && visState.aggs) { - visState.aggs.forEach((agg: any) => { - if (agg.type !== 'filters') return; - - agg.params.filters.forEach((filter: any) => { - if (filter.input.language) return filter; - filter.input.language = 'lucene'; - }); - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } + let panels; + try { + panels = JSON.parse(panelsJSON); + } catch (e) { + // Let it go, the data is invalid and we'll leave it as is + return doc; } - return doc; -}; - -const replaceMovAvgToMovFn: SavedObjectMigrationFn = (doc, logger) => { - const visStateJSON = get(doc, 'attributes.visState'); - let visState; - - if (visStateJSON) { - try { - visState = JSON.parse(visStateJSON); - - if (visState && visState.type === 'metrics') { - const series: any[] = get(visState, 'params.series', []); - - series.forEach(part => { - if (part.metrics && Array.isArray(part.metrics)) { - part.metrics.forEach((metric: any) => { - if (metric.type === 'moving_average') { - metric.model_type = metric.model; - metric.alpha = get(metric, 'settings.alpha', 0.3); - metric.beta = get(metric, 'settings.beta', 0.1); - metric.gamma = get(metric, 'settings.gamma', 0.3); - metric.period = get(metric, 'settings.period', 1); - metric.multiplicative = get(metric, 'settings.type') === 'mult'; - - delete metric.minimize; - delete metric.model; - delete metric.settings; - delete metric.predict; - } - }); - } - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } catch (e) { - logger.log.warn(`Exception @ replaceMovAvgToMovFn! ${e}`); - logger.log.warn(`Exception @ replaceMovAvgToMovFn! Payload: ${visStateJSON}`); - } + if (!Array.isArray(panels)) { + return doc; } - - return doc; -}; - -const migrateFiltersAggQueryStringQueries: SavedObjectMigrationFn = (doc, logger) => { - const visStateJSON = get(doc, 'attributes.visState'); - - if (visStateJSON) { - try { - const visState = JSON.parse(visStateJSON); - - if (visState && visState.aggs) { - visState.aggs.forEach((agg: any) => { - if (agg.type !== 'filters') return doc; - - agg.params.filters.forEach((filter: any) => { - if (filter.input.query.query_string) { - filter.input.query = filter.input.query.query_string.query; - } - }); - }); - - return { - ...doc, - attributes: { - ...doc.attributes, - visState: JSON.stringify(visState), - }, - }; - } - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is + panels.forEach((panel, i) => { + if (!panel.type || !panel.id) { + return; } - } - return doc; -}; - -const addDocReferences: SavedObjectMigrationFn = doc => ({ - ...doc, - references: doc.references || [], -}); - -const migrateSavedSearch: SavedObjectMigrationFn = doc => { - const savedSearchId = get(doc, 'attributes.savedSearchId'); - - if (savedSearchId && doc.references) { + panel.panelRefName = `panel_${i}`; doc.references.push({ - type: 'search', - name: 'search_0', - id: savedSearchId, - }); - doc.attributes.savedSearchRefName = 'search_0'; - } - - delete doc.attributes.savedSearchId; - - return doc; -}; - -const migrateControls: SavedObjectMigrationFn = doc => { - const visStateJSON = get(doc, 'attributes.visState'); - - if (visStateJSON) { - let visState; - try { - visState = JSON.parse(visStateJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - if (visState) { - const controls: any[] = get(visState, 'params.controls') || []; - controls.forEach((control, i) => { - if (!control.indexPattern || !doc.references) { - return; - } - control.indexPatternRefName = `control_${i}_index_pattern`; - doc.references.push({ - name: control.indexPatternRefName, - type: 'index-pattern', - id: control.indexPattern, - }); - delete control.indexPattern; - }); - doc.attributes.visState = JSON.stringify(visState); - } - } - - return doc; -}; - -const migrateTableSplits: SavedObjectMigrationFn = doc => { - try { - const visState = JSON.parse(doc.attributes.visState); - if (get(visState, 'type') !== 'table') { - return doc; // do nothing; we only want to touch tables - } - - let splitCount = 0; - visState.aggs = visState.aggs.map((agg: any) => { - if (agg.schema !== 'split') { - return agg; - } - - splitCount++; - if (splitCount === 1) { - return agg; // leave the first split agg unchanged - } - agg.schema = 'bucket'; - // the `row` param is exclusively used by split aggs, so we remove it - agg.params = omit(agg.params, ['row']); - return agg; + name: `panel_${i}`, + type: panel.type, + id: panel.id, }); - - if (splitCount <= 1) { - return doc; // do nothing; we only want to touch tables with multiple split aggs - } - - const newDoc = cloneDeep(doc); - newDoc.attributes.visState = JSON.stringify(visState); - - return newDoc; - } catch (e) { - throw new Error(`Failure attempting to migrate saved object '${doc.attributes.title}' - ${e}`); - } -}; - -const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { - const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); - - if (searchSourceJSON) { - let searchSource: any; - - try { - searchSource = JSON.parse(searchSourceJSON); - } catch (e) { - // Let it go, the data is invalid and we'll leave it as is - } - - if (searchSource.query?.match_all) { - return { - ...doc, - attributes: { - ...doc.attributes, - kibanaSavedObjectMeta: { - searchSourceJSON: JSON.stringify({ - ...searchSource, - query: { - query: '', - language: DEFAULT_QUERY_LANGUAGE, - }, - }), - }, - }, - }; - } - } - + delete panel.type; + delete panel.id; + }); + doc.attributes.panelsJSON = JSON.stringify(panels); return doc; }; @@ -617,26 +107,7 @@ export const dashboardSavedObjectTypeMigrations = { * in that version. So we apply this twice, once with 6.7.2 and once with 7.0.1 while the backport to 6.7 * only contained the 6.7.2 migration and not the 7.0.1 migration. */ - '6.7.2': flow(migrateMatchAllQuery, removeDateHistogramTimeZones), - '7.0.0': flow( - addDocReferences, - migrateIndexPattern, - migrateSavedSearch, - migrateControls, - migrateTableSplits - ), - '7.0.1': flow(removeDateHistogramTimeZones), - '7.2.0': flow( - migratePercentileRankAggregation, - migrateDateHistogramAggregation - ), - '7.3.0': flow( - migrateGaugeVerticalSplitToAlignment, - transformFilterStringToQueryObject, - migrateFiltersAggQuery, - replaceMovAvgToMovFn - ), - '7.3.1': flow(migrateFiltersAggQueryStringQueries), - '7.4.2': flow(transformSplitFiltersStringToQueryObject), - '7.7.0': flow(migrateOperatorKeyTypo), + '6.7.2': flow(migrateMatchAllQuery), + '7.0.0': flow(migrations700), + '7.3.0': flow(migrations730), }; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts b/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts similarity index 70% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts rename to src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts index d8f8882a218d..c9b35263a549 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/is_dashboard_doc.ts +++ b/src/plugins/dashboard/server/saved_objects/is_dashboard_doc.ts @@ -17,8 +17,21 @@ * under the License. */ -import { DashboardDoc730ToLatest } from '../../../../../../plugins/dashboard/public'; -import { isDoc } from '../../../migrations/is_doc'; +import { SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { DashboardDoc730ToLatest } from '../../common'; + +function isDoc( + doc: { [key: string]: unknown } | SavedObjectUnsanitizedDoc +): doc is SavedObjectUnsanitizedDoc { + return ( + typeof doc.id === 'string' && + typeof doc.type === 'string' && + doc.attributes !== null && + typeof doc.attributes === 'object' && + doc.references !== null && + typeof doc.references === 'object' + ); +} export function isDashboardDoc( doc: { [key: string]: unknown } | DashboardDoc730ToLatest diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.test.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.test.ts rename to src/plugins/dashboard/server/saved_objects/migrate_match_all_query.test.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts similarity index 95% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts rename to src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts index 707aae9e5d4a..5b8582bf821e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrate_match_all_query.ts +++ b/src/plugins/dashboard/server/saved_objects/migrate_match_all_query.ts @@ -19,7 +19,7 @@ import { SavedObjectMigrationFn } from 'kibana/server'; import { get } from 'lodash'; -import { DEFAULT_QUERY_LANGUAGE } from '../../../../../../plugins/data/common'; +import { DEFAULT_QUERY_LANGUAGE } from '../../../data/common'; export const migrateMatchAllQuery: SavedObjectMigrationFn = doc => { const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON'); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts b/src/plugins/dashboard/server/saved_objects/migrations_730.test.ts similarity index 91% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts rename to src/plugins/dashboard/server/saved_objects/migrations_730.test.ts index 5a4970897098..885c70e5b423 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.test.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations_730.test.ts @@ -17,20 +17,18 @@ * under the License. */ -import { migrations } from '../../../migrations'; +import { dashboardSavedObjectTypeMigrations as migrations } from './dashboard_migrations'; import { migrations730 } from './migrations_730'; -import { - DashboardDoc700To720, - DashboardDoc730ToLatest, - RawSavedDashboardPanel730ToLatest, - DashboardDocPre700, -} from '../../../../../../plugins/dashboard/public'; - -const mockLogger = { - warning: () => {}, - warn: () => {}, - debug: () => {}, - info: () => {}, +import { DashboardDoc700To720, DashboardDoc730ToLatest, DashboardDocPre700 } from '../../common'; +import { RawSavedDashboardPanel730ToLatest } from '../../common'; + +const mockContext = { + log: { + warning: () => {}, + warn: () => {}, + debug: () => {}, + info: () => {}, + }, }; test('dashboard migration 7.3.0 migrates filters to query on search source', () => { @@ -53,7 +51,7 @@ test('dashboard migration 7.3.0 migrates filters to query on search source', () '[{"id":"1","type":"visualization","foo":true},{"id":"2","type":"visualization","bar":true}]', }, }; - const newDoc = migrations730(doc, mockLogger); + const newDoc = migrations730(doc, mockContext); expect(newDoc).toMatchInlineSnapshot(` Object { @@ -97,8 +95,8 @@ test('dashboard migration 7.3.0 migrates filters to query on search source when }, }; - const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockLogger); - const newDoc = migrations.dashboard['7.3.0'](doc700, mockLogger); + const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockContext); + const newDoc = migrations.dashboard['7.3.0'](doc700, mockContext); const parsedSearchSource = JSON.parse(newDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON); expect(parsedSearchSource.filter.length).toBe(0); @@ -129,8 +127,8 @@ test('dashboard migration works when panelsJSON is missing panelIndex', () => { }, }; - const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockLogger); - const newDoc = migrations.dashboard['7.3.0'](doc700, mockLogger); + const doc700: DashboardDoc700To720 = migrations.dashboard['7.0.0'](doc, mockContext); + const newDoc = migrations.dashboard['7.3.0'](doc700, mockContext); const parsedSearchSource = JSON.parse(newDoc.attributes.kibanaSavedObjectMeta.searchSourceJSON); expect(parsedSearchSource.filter.length).toBe(0); @@ -159,7 +157,7 @@ test('dashboard migration 7.3.0 migrates panels', () => { }, }; - const newDoc = migrations730(doc, mockLogger) as DashboardDoc730ToLatest; + const newDoc = migrations730(doc, mockContext) as DashboardDoc730ToLatest; const newPanels = JSON.parse(newDoc.attributes.panelsJSON) as RawSavedDashboardPanel730ToLatest[]; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts b/src/plugins/dashboard/server/saved_objects/migrations_730.ts similarity index 79% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts rename to src/plugins/dashboard/server/saved_objects/migrations_730.ts index 56856f7b2130..37de816b8329 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/migrations_730.ts +++ b/src/plugins/dashboard/server/saved_objects/migrations_730.ts @@ -16,26 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -// This file should be moved to dashboard/server/ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsMigrationLogger } from 'src/core/server'; + import { inspect } from 'util'; -import { - DashboardDoc730ToLatest, - DashboardDoc700To720, -} from '../../../../../../plugins/dashboard/public'; +import { SavedObjectMigrationFn } from 'kibana/server'; +import { DashboardDoc730ToLatest } from '../../common'; import { isDashboardDoc } from './is_dashboard_doc'; import { moveFiltersToQuery } from './move_filters_to_query'; -import { migratePanelsTo730 } from './migrate_to_730_panels'; +import { migratePanelsTo730 } from '../../common/migrate_to_730_panels'; -export function migrations730( - doc: - | { - [key: string]: unknown; - } - | DashboardDoc700To720, - logger: SavedObjectsMigrationLogger -): DashboardDoc730ToLatest | { [key: string]: unknown } { +export const migrations730: SavedObjectMigrationFn = (doc, { log }) => { if (!isDashboardDoc(doc)) { // NOTE: we should probably throw an error here... but for now following suit and in the // case of errors, just returning the same document. @@ -48,7 +37,7 @@ export function migrations730( moveFiltersToQuery(searchSource) ); } catch (e) { - logger.warning( + log.warning( `Exception @ migrations730 while trying to migrate dashboard query filters!\n` + `${e.stack}\n` + `dashboard: ${inspect(doc, false, null)}` @@ -75,7 +64,7 @@ export function migrations730( delete doc.attributes.uiStateJSON; } catch (e) { - logger.warning( + log.warning( `Exception @ migrations730 while trying to migrate dashboard panels!\n` + `Error: ${e.stack}\n` + `dashboard: ${inspect(doc, false, null)}` @@ -84,4 +73,4 @@ export function migrations730( } return doc as DashboardDoc730ToLatest; -} +}; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts similarity index 96% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts rename to src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts index 621983b1ca8a..a06f64e0f0c4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.test.ts +++ b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.test.ts @@ -17,8 +17,8 @@ * under the License. */ +import { esFilters, Filter } from 'src/plugins/data/public'; import { moveFiltersToQuery, Pre600FilterQuery } from './move_filters_to_query'; -import { esFilters, Filter } from '../../../../../../plugins/data/public'; const filter: Filter = { meta: { disabled: false, negate: false, alias: '' }, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts b/src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/dashboard/migrations/move_filters_to_query.ts rename to src/plugins/dashboard/server/saved_objects/move_filters_to_query.ts