Skip to content

Commit

Permalink
add dashboard migration
Browse files Browse the repository at this point in the history
  • Loading branch information
nreese committed Feb 15, 2022
1 parent 07806cf commit 00171f7
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
MigrateFunctionsObject,
} from '../../../kibana_utils/common';
import { replaceIndexPatternReference } from './replace_index_pattern_reference';
import { migrateMapEmbeddable } from './migrate_map_embeddable';

function migrateIndexPattern(doc: DashboardDoc700To720) {
const searchSourceJSON = get(doc, 'attributes.kibanaSavedObjectMeta.searchSourceJSON');
Expand Down Expand Up @@ -249,6 +250,9 @@ export const createDashboardSavedObjectTypeMigrations = (
// '7.x': flow(yourNewMigrationFunction, embeddableMigrations['7.x'] ?? identity),

'7.14.0': flow(replaceIndexPatternReference),

'7.17.1': flow(migrateMapEmbeddable),
'8.0.1': flow(migrateMapEmbeddable),
};

return mergeMigrationFunctionMaps(dashboardMigrations, embeddableMigrations);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { extractReferences } from './extract_references';

test('Should handle missing layerListJSON attribute', () => {
const attributes = {
title: 'my map',
};
expect(extractReferences({ attributes, embeddableId: 'panel1' })).toEqual({
attributes: {
title: 'my map',
},
references: [],
});
});

test('Should extract index-pattern reference from source with indexPatternId', () => {
const attributes = {
title: 'my map',
layerListJSON:
'[{"sourceDescriptor":{"indexPatternId":"c698b940-e149-11e8-a35a-370a8516603a"}}]',
};
expect(extractReferences({ attributes, embeddableId: 'panel1' })).toEqual({
attributes: {
title: 'my map',
layerListJSON:
'[{"sourceDescriptor":{"indexPatternRefName":"panel1_layer_0_source_index_pattern"}}]',
},
references: [
{
id: 'c698b940-e149-11e8-a35a-370a8516603a',
name: 'panel1_layer_0_source_index_pattern',
type: 'index-pattern',
},
],
});
});

test('Should extract index-pattern reference from join with indexPatternId', () => {
const attributes = {
title: 'my map',
layerListJSON:
'[{"joins":[{"right":{"indexPatternId":"e20b2a30-f735-11e8-8ce0-9723965e01e3"}}]}]',
};
expect(extractReferences({ attributes, embeddableId: 'panel1' })).toEqual({
attributes: {
title: 'my map',
layerListJSON:
'[{"joins":[{"right":{"indexPatternRefName":"panel1_layer_0_join_0_index_pattern"}}]}]',
},
references: [
{
id: 'e20b2a30-f735-11e8-8ce0-9723965e01e3',
name: 'panel1_layer_0_join_0_index_pattern',
type: 'index-pattern',
},
],
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { SavedObjectReference } from '../../../../../core/types';

interface IndexPatternReferenceDescriptor {
indexPatternId?: string;
indexPatternRefName?: string;
}

interface JoinDescriptor {
right: {
indexPatternId?: string;
};
}

interface LayerDescriptor {
sourceDescriptor: {
indexPatternId?: string;
};
}

type VectorLayerDescriptor = LayerDescriptor & {
joins?: JoinDescriptor[];
};

export function extractReferences({
attributes,
references = [],
embeddableId,
}: {
attributes: Record<string, string>;
references?: SavedObjectReference[];
embeddableId: string;
}) {
if (!attributes.layerListJSON) {
return { attributes, references };
}

const extractedReferences: SavedObjectReference[] = [];

let layerList: LayerDescriptor[] = [];
try {
layerList = JSON.parse(attributes.layerListJSON);
} catch (e) {
return { attributes, references };
}

layerList.forEach((layer, layerIndex) => {
// Extract index-pattern references from source descriptor
if (layer.sourceDescriptor && 'indexPatternId' in layer.sourceDescriptor) {
const sourceDescriptor = layer.sourceDescriptor as IndexPatternReferenceDescriptor;
const baseRefName = `layer_${layerIndex}_source_index_pattern`;
const refName = embeddableId ? `${embeddableId}_${baseRefName}` : baseRefName;
extractedReferences.push({
name: refName,
type: 'index-pattern',
id: sourceDescriptor.indexPatternId!,
});
delete sourceDescriptor.indexPatternId;
sourceDescriptor.indexPatternRefName = refName;
}

if ('joins' in layer) {
// Extract index-pattern references from join
const vectorLayer = layer as VectorLayerDescriptor;
const joins = vectorLayer.joins ? vectorLayer.joins : [];
joins.forEach((join, joinIndex) => {
if ('indexPatternId' in join.right) {
const sourceDescriptor = join.right as IndexPatternReferenceDescriptor;
const baseRefName = `layer_${layerIndex}_join_${joinIndex}_index_pattern`;
const refName = embeddableId ? `${embeddableId}_${baseRefName}` : baseRefName;
extractedReferences.push({
name: refName,
type: 'index-pattern',
id: sourceDescriptor.indexPatternId!,
});
delete sourceDescriptor.indexPatternId;
sourceDescriptor.indexPatternRefName = refName;
}
});
}
});

return {
attributes: {
...attributes,
layerListJSON: JSON.stringify(layerList),
},
references: references.concat(extractedReferences),
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

export { migrateMapEmbeddable } from './migrate_map_embeddable';
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { savedObjectsServiceMock } from '../../../../../core/server/mocks';
import { migrateMapEmbeddable } from './migrate_map_embeddable';

test('Should extract references from by-value map panels', () => {
const dashboard = {
id: 'cf56e3f0-8de8-11ec-975f-f7e09cf7ebaf',
type: 'dashboard',
attributes: {
description: '',
hits: 0,
kibanaSavedObjectMeta: {
searchSourceJSON: '{"query":{"query":"","language":"kuery"},"filter":[]}',
},
optionsJSON: '{"useMargins":true,"syncColors":false,"hidePanelTitles":false}',
panelsJSON:
'[{"version":"8.0.0","type":"map","gridData":{"x":0,"y":0,"w":24,"h":15,"i":"6c75ee1d-28d9-4eea-8816-4110f3d8c154"},"panelIndex":"6c75ee1d-28d9-4eea-8816-4110f3d8c154","embeddableConfig":{"attributes":{"title":"","description":"","layerListJSON":"[{\\"sourceDescriptor\\":{\\"indexPatternId\\":\\"90943e30-9a47-11e8-b64d-95841ca0b247\\",\\"geoField\\":\\"geo.coordinates\\",\\"filterByMapBounds\\":true,\\"scalingType\\":\\"LIMIT\\",\\"id\\":\\"76e10a23-70b3-43a6-9476-7480a525aced\\",\\"type\\":\\"ES_SEARCH\\",\\"applyGlobalQuery\\":true,\\"applyGlobalTime\\":true,\\"applyForceRefresh\\":true,\\"tooltipProperties\\":[],\\"sortField\\":\\"\\",\\"sortOrder\\":\\"desc\\",\\"topHitsSplitField\\":\\"\\",\\"topHitsSize\\":1},\\"id\\":\\"14acaa6e-3a3a-419e-9080-28c8d64626ab\\",\\"label\\":null,\\"minZoom\\":0,\\"maxZoom\\":24,\\"alpha\\":0.75,\\"visible\\":true,\\"style\\":{\\"type\\":\\"VECTOR\\",\\"properties\\":{\\"icon\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"value\\":\\"marker\\"}},\\"fillColor\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"color\\":\\"#54B399\\"}},\\"lineColor\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"color\\":\\"#41937c\\"}},\\"lineWidth\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"size\\":1}},\\"iconSize\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"size\\":6}},\\"iconOrientation\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"orientation\\":0}},\\"labelText\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"value\\":\\"\\"}},\\"labelColor\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"color\\":\\"#000000\\"}},\\"labelSize\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"size\\":14}},\\"labelBorderColor\\":{\\"type\\":\\"STATIC\\",\\"options\\":{\\"color\\":\\"#FFFFFF\\"}},\\"symbolizeAs\\":{\\"options\\":{\\"value\\":\\"circle\\"}},\\"labelBorderSize\\":{\\"options\\":{\\"size\\":\\"SMALL\\"}}},\\"isTimeAware\\":true},\\"includeInFitToBounds\\":true,\\"type\\":\\"VECTOR\\",\\"joins\\":[]}]","mapStateJSON":"{\\"zoom\\":2.46,\\"center\\":{\\"lon\\":-116.80747,\\"lat\\":54.63368},\\"timeFilters\\":{\\"from\\":\\"now-24h/h\\",\\"to\\":\\"now\\"},\\"refreshConfig\\":{\\"isPaused\\":true,\\"interval\\":0},\\"query\\":{\\"query\\":\\"\\",\\"language\\":\\"kuery\\"},\\"filters\\":[],\\"settings\\":{\\"autoFitToDataBounds\\":false,\\"backgroundColor\\":\\"#ffffff\\",\\"disableInteractive\\":false,\\"disableTooltipControl\\":false,\\"hideToolbarOverlay\\":false,\\"hideLayerControl\\":false,\\"hideViewControl\\":false,\\"initialLocation\\":\\"LAST_SAVED_LOCATION\\",\\"fixedLocation\\":{\\"lat\\":0,\\"lon\\":0,\\"zoom\\":2},\\"browserLocation\\":{\\"zoom\\":2},\\"maxZoom\\":24,\\"minZoom\\":0,\\"showScaleControl\\":false,\\"showSpatialFilters\\":true,\\"showTimesliderToggleButton\\":true,\\"spatialFiltersAlpa\\":0.3,\\"spatialFiltersFillColor\\":\\"#DA8B45\\",\\"spatialFiltersLineColor\\":\\"#DA8B45\\"}}","uiStateJSON":"{\\"isLayerTOCOpen\\":true,\\"openTOCDetails\\":[]}"},"mapCenter":{"lat":54.63368,"lon":-116.80747,"zoom":2.46},"mapBuffer":{"minLon":-225,"minLat":0,"maxLon":-45,"maxLat":79.17133},"isLayerTOCOpen":true,"openTOCDetails":[],"hiddenLayers":[],"enhancements":{},"type":"map"}}]',
timeRestore: false,
title: 'by value map',
version: 1,
},
migrationVersion: { dashboard: '8.0.0' },
coreMigrationVersion: '8.0.0',
namespaces: ['default'],
updated_at: '2022-02-15T18:04:14.790Z',
references: [
{
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
name: '6c75ee1d-28d9-4eea-8816-4110f3d8c154:layer_1_source_index_pattern',
type: 'index-pattern',
},
],
originId: undefined,
};

const contextMock = savedObjectsServiceMock.createMigrationContext();
const updated = migrateMapEmbeddable(dashboard, contextMock);
expect(updated.references).toEqual([
{
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
name: '6c75ee1d-28d9-4eea-8816-4110f3d8c154:layer_1_source_index_pattern',
type: 'index-pattern',
},
{
name: '6c75ee1d-28d9-4eea-8816-4110f3d8c154_layer_0_source_index_pattern',
type: 'index-pattern',
id: '90943e30-9a47-11e8-b64d-95841ca0b247',
},
]);

const panels = JSON.parse(updated.attributes.panelsJSON);
const layerList = JSON.parse(panels[0].embeddableConfig.attributes.layerListJSON);
expect(layerList[0].sourceDescriptor.indexPatternRefName).toEqual(
'6c75ee1d-28d9-4eea-8816-4110f3d8c154_layer_0_source_index_pattern'
);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import type { SavedObjectMigrationFn } from 'kibana/server';
import { extractReferences } from './extract_references';
import { SavedDashboardPanel } from '../../../common/types';

// Fix bug in by-value map embeddable where references where not properly extracted so that references could be replace with ids.
// https://github.com/elastic/kibana/issues/125595
export const migrateMapEmbeddable: SavedObjectMigrationFn<any, any> = (doc) => {
if (!doc.attributes || !doc.attributes.panelsJSON) {
return doc;
}

const dashboardReferences = doc.references ? [...doc.references] : [];

let panels: SavedDashboardPanel[] = [];
try {
panels = JSON.parse(doc.attributes.panelsJSON);
} catch (error) {
// ignore error
return doc;
}

const migratedPanels = panels.map((panel) => {
if (panel.type !== 'map' || !panel.embeddableConfig.attributes) {
return panel;
}

const { attributes, references } = extractReferences({
attributes: panel.embeddableConfig.attributes as Record<string, string>,
embeddableId: panel.panelIndex,
});
dashboardReferences.push(...references);
return {
...panel,
embeddableConfig: {
...panel.embeddableConfig,
attributes,
},
};
});

return {
...doc,
attributes: {
...doc.attributes,
panelsJSON: JSON.stringify(migratedPanels),
},
references: dashboardReferences,
};
};

0 comments on commit 00171f7

Please sign in to comment.