Skip to content

Commit fca64ad

Browse files
authored
[Maps] implement references for saved objects (#31745) (#31957)
* [Maps] implement references for saved objects * add source to ref name, check that source type is ES_SEARCH or ES_GEO_GRID * extract out common find reference into a function * add migration version to sample data objects * joins are on layer descriptor and not source descriptor * update one es_archive saved object to have layerListJSON stored in references to ensure injectReferences is really working in SavedGisMap * update sample data saved objects to include applied migration * add API test to verify migration is applied when imported saved object
1 parent f3058e9 commit fca64ad

File tree

16 files changed

+488
-9
lines changed

16 files changed

+488
-9
lines changed

x-pack/plugins/maps/common/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
export const GIS_API_PATH = 'api/maps';
88

99
export const EMS_FILE = 'EMS_FILE';
10+
export const ES_GEO_GRID = 'ES_GEO_GRID';
11+
export const ES_SEARCH = 'ES_SEARCH';
1012

1113
export const DECIMAL_DEGREES_PRECISION = 5; // meters precision
1214

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
// Can not use public Layer classes to extract references since this logic must run in both client and server.
8+
9+
import _ from 'lodash';
10+
import { ES_GEO_GRID, ES_SEARCH } from '../constants';
11+
12+
function doesSourceUseIndexPattern(layerDescriptor) {
13+
const sourceType = _.get(layerDescriptor, 'sourceDescriptor.type');
14+
return sourceType === ES_GEO_GRID || sourceType === ES_SEARCH;
15+
}
16+
17+
export function extractReferences({ attributes, references = [] }) {
18+
if (!attributes.layerListJSON) {
19+
return { attributes, references };
20+
}
21+
22+
const extractedReferences = [];
23+
24+
const layerList = JSON.parse(attributes.layerListJSON);
25+
layerList.forEach((layer, layerIndex) => {
26+
27+
// Extract index-pattern references from source descriptor
28+
if (doesSourceUseIndexPattern(layer) && _.has(layer, 'sourceDescriptor.indexPatternId')) {
29+
const refName = `layer_${layerIndex}_source_index_pattern`;
30+
extractedReferences.push({
31+
name: refName,
32+
type: 'index-pattern',
33+
id: layer.sourceDescriptor.indexPatternId,
34+
});
35+
delete layer.sourceDescriptor.indexPatternId;
36+
layer.sourceDescriptor.indexPatternRefName = refName;
37+
}
38+
39+
// Extract index-pattern references from join
40+
const joins = _.get(layer, 'joins', []);
41+
joins.forEach((join, joinIndex) => {
42+
if (_.has(join, 'right.indexPatternId')) {
43+
const refName = `layer_${layerIndex}_join_${joinIndex}_index_pattern`;
44+
extractedReferences.push({
45+
name: refName,
46+
type: 'index-pattern',
47+
id: join.right.indexPatternId,
48+
});
49+
delete join.right.indexPatternId;
50+
join.right.indexPatternRefName = refName;
51+
}
52+
});
53+
});
54+
55+
return {
56+
attributes: {
57+
...attributes,
58+
layerListJSON: JSON.stringify(layerList),
59+
},
60+
references: references.concat(extractedReferences),
61+
};
62+
}
63+
64+
function findReference(targetName, references) {
65+
const reference = references.find(reference => reference.name === targetName);
66+
if (!reference) {
67+
throw new Error(`Could not find reference "${targetName}"`);
68+
}
69+
return reference;
70+
}
71+
72+
export function injectReferences({ attributes, references }) {
73+
if (!attributes.layerListJSON) {
74+
return { attributes };
75+
}
76+
77+
const layerList = JSON.parse(attributes.layerListJSON);
78+
layerList.forEach((layer) => {
79+
80+
// Inject index-pattern references into source descriptor
81+
if (doesSourceUseIndexPattern(layer) && _.has(layer, 'sourceDescriptor.indexPatternRefName')) {
82+
const reference = findReference(layer.sourceDescriptor.indexPatternRefName, references);
83+
layer.sourceDescriptor.indexPatternId = reference.id;
84+
delete layer.sourceDescriptor.indexPatternRefName;
85+
}
86+
87+
// Inject index-pattern references into join
88+
const joins = _.get(layer, 'joins', []);
89+
joins.forEach((join) => {
90+
if (_.has(join, 'right.indexPatternRefName')) {
91+
const reference = findReference(join.right.indexPatternRefName, references);
92+
join.right.indexPatternId = reference.id;
93+
delete join.right.indexPatternRefName;
94+
}
95+
});
96+
});
97+
98+
return {
99+
attributes: {
100+
...attributes,
101+
layerListJSON: JSON.stringify(layerList),
102+
},
103+
};
104+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
/* eslint max-len: 0 */
8+
9+
import { extractReferences, injectReferences } from './references';
10+
import { ES_GEO_GRID, ES_SEARCH } from '../constants';
11+
12+
const layerListJSON = {
13+
esSearchSource: {
14+
withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${ES_SEARCH}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`,
15+
withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${ES_SEARCH}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`,
16+
},
17+
esGeoGridSource: {
18+
withIndexPatternId: `[{\"sourceDescriptor\":{\"type\":\"${ES_GEO_GRID}\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\"}}]`,
19+
withIndexPatternRef: `[{\"sourceDescriptor\":{\"type\":\"${ES_GEO_GRID}\",\"indexPatternRefName\":\"layer_0_source_index_pattern\"}}]`,
20+
},
21+
join: {
22+
withIndexPatternId: '[{\"joins\":[{\"right\":{\"indexPatternId\":\"e20b2a30-f735-11e8-8ce0-9723965e01e3\"}}]}]',
23+
withIndexPatternRef: '[{\"joins\":[{\"right\":{\"indexPatternRefName\":\"layer_0_join_0_index_pattern\"}}]}]',
24+
}
25+
};
26+
27+
describe('extractReferences', () => {
28+
29+
test('Should handle missing layerListJSON attribute', () => {
30+
const attributes = {
31+
title: 'my map',
32+
};
33+
expect(extractReferences({ attributes })).toEqual({
34+
attributes: {
35+
title: 'my map',
36+
},
37+
references: [],
38+
});
39+
});
40+
41+
test('Should extract index-pattern reference from ES search source descriptor', () => {
42+
const attributes = {
43+
title: 'my map',
44+
layerListJSON: layerListJSON.esSearchSource.withIndexPatternId,
45+
};
46+
expect(extractReferences({ attributes })).toEqual({
47+
attributes: {
48+
title: 'my map',
49+
layerListJSON: layerListJSON.esSearchSource.withIndexPatternRef,
50+
},
51+
references: [
52+
{
53+
id: 'c698b940-e149-11e8-a35a-370a8516603a',
54+
name: 'layer_0_source_index_pattern',
55+
type: 'index-pattern',
56+
}
57+
],
58+
});
59+
});
60+
61+
test('Should extract index-pattern reference from ES geo grid source descriptor', () => {
62+
const attributes = {
63+
title: 'my map',
64+
layerListJSON: layerListJSON.esGeoGridSource.withIndexPatternId,
65+
};
66+
expect(extractReferences({ attributes })).toEqual({
67+
attributes: {
68+
title: 'my map',
69+
layerListJSON: layerListJSON.esGeoGridSource.withIndexPatternRef,
70+
},
71+
references: [
72+
{
73+
id: 'c698b940-e149-11e8-a35a-370a8516603a',
74+
name: 'layer_0_source_index_pattern',
75+
type: 'index-pattern',
76+
}
77+
],
78+
});
79+
});
80+
81+
test('Should extract index-pattern reference from joins', () => {
82+
const attributes = {
83+
title: 'my map',
84+
layerListJSON: layerListJSON.join.withIndexPatternId,
85+
};
86+
expect(extractReferences({ attributes })).toEqual({
87+
attributes: {
88+
title: 'my map',
89+
layerListJSON: layerListJSON.join.withIndexPatternRef,
90+
},
91+
references: [
92+
{
93+
id: 'e20b2a30-f735-11e8-8ce0-9723965e01e3',
94+
name: 'layer_0_join_0_index_pattern',
95+
type: 'index-pattern',
96+
}
97+
],
98+
});
99+
});
100+
});
101+
102+
describe('injectReferences', () => {
103+
test('Should handle missing layerListJSON attribute', () => {
104+
const attributes = {
105+
title: 'my map',
106+
};
107+
expect(injectReferences({ attributes })).toEqual({
108+
attributes: {
109+
title: 'my map',
110+
}
111+
});
112+
});
113+
114+
test('Should inject index-pattern reference into ES search source descriptor', () => {
115+
const attributes = {
116+
title: 'my map',
117+
layerListJSON: layerListJSON.esSearchSource.withIndexPatternRef,
118+
};
119+
const references = [
120+
{
121+
id: 'c698b940-e149-11e8-a35a-370a8516603a',
122+
name: 'layer_0_source_index_pattern',
123+
type: 'index-pattern',
124+
}
125+
];
126+
expect(injectReferences({ attributes, references })).toEqual({
127+
attributes: {
128+
title: 'my map',
129+
layerListJSON: layerListJSON.esSearchSource.withIndexPatternId,
130+
}
131+
});
132+
});
133+
134+
test('Should inject index-pattern reference into ES geo grid source descriptor', () => {
135+
const attributes = {
136+
title: 'my map',
137+
layerListJSON: layerListJSON.esGeoGridSource.withIndexPatternRef,
138+
};
139+
const references = [
140+
{
141+
id: 'c698b940-e149-11e8-a35a-370a8516603a',
142+
name: 'layer_0_source_index_pattern',
143+
type: 'index-pattern',
144+
}
145+
];
146+
expect(injectReferences({ attributes, references })).toEqual({
147+
attributes: {
148+
title: 'my map',
149+
layerListJSON: layerListJSON.esGeoGridSource.withIndexPatternId,
150+
}
151+
});
152+
});
153+
154+
test('Should inject index-pattern reference into joins', () => {
155+
const attributes = {
156+
title: 'my map',
157+
layerListJSON: layerListJSON.join.withIndexPatternRef,
158+
};
159+
const references = [
160+
{
161+
id: 'e20b2a30-f735-11e8-8ce0-9723965e01e3',
162+
name: 'layer_0_join_0_index_pattern',
163+
type: 'index-pattern',
164+
}
165+
];
166+
expect(injectReferences({ attributes, references })).toEqual({
167+
attributes: {
168+
title: 'my map',
169+
layerListJSON: layerListJSON.join.withIndexPatternId,
170+
}
171+
});
172+
});
173+
});

x-pack/plugins/maps/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import fligthsSavedObjects from './server/sample_data/flights_saved_objects.json
1111
import webLogsSavedObjects from './server/sample_data/web_logs_saved_objects.json';
1212
import mappings from './mappings.json';
1313
import { checkLicense } from './check_license';
14+
import { migrations } from './migrations';
1415
import { watchStatusAndLicenseToInitialize } from
1516
'../../server/lib/watch_status_and_license_to_initialize';
1617
import { initTelemetryCollection } from './server/maps_telemetry';
@@ -48,7 +49,8 @@ export function maps(kibana) {
4849
isNamespaceAgnostic: true
4950
}
5051
},
51-
mappings
52+
mappings,
53+
migrations,
5254
},
5355
config(Joi) {
5456
return Joi.object({

x-pack/plugins/maps/migrations.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
import { extractReferences } from './common/migrations/references';
8+
9+
export const migrations = {
10+
'map': {
11+
'7.1.0': (doc) => {
12+
const { attributes, references } = extractReferences(doc);
13+
14+
return {
15+
...doc,
16+
attributes,
17+
references,
18+
};
19+
},
20+
},
21+
};

x-pack/plugins/maps/public/angular/services/saved_gis_map.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from '../../selectors/map_selectors';
2020
import { convertMapExtentToPolygon } from '../../elasticsearch_geo_utils';
2121
import { copyPersistentState } from '../../store/util';
22+
import { extractReferences, injectReferences } from '../../../common/migrations/references';
2223

2324
const module = uiModules.get('app/maps');
2425

@@ -30,6 +31,15 @@ module.factory('SavedGisMap', function (Private) {
3031
type: SavedGisMap.type,
3132
mapping: SavedGisMap.mapping,
3233
searchSource: SavedGisMap.searchsource,
34+
extractReferences,
35+
injectReferences: (savedObject, references) => {
36+
const { attributes } = injectReferences({
37+
attributes: { layerListJSON: savedObject.layerListJSON },
38+
references
39+
});
40+
41+
savedObject.layerListJSON = attributes.layerListJSON;
42+
},
3343

3444
// if this is null/undefined then the SavedObject will be assigned the defaults
3545
id: id,

x-pack/plugins/maps/public/shared/layers/sources/es_geo_grid_source/es_geo_grid_source.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { RENDER_AS } from './render_as';
1919
import { CreateSourceEditor } from './create_source_editor';
2020
import { UpdateSourceEditor } from './update_source_editor';
2121
import { GRID_RESOLUTION } from '../../grid_resolution';
22+
import { ES_GEO_GRID } from '../../../../../common/constants';
2223
import { filterPropertiesForTooltip } from '../../util';
2324

2425
const COUNT_PROP_LABEL = 'count';
@@ -49,7 +50,7 @@ const aggSchemas = new Schemas([
4950

5051
export class ESGeoGridSource extends AbstractESSource {
5152

52-
static type = 'ES_GEO_GRID';
53+
static type = ES_GEO_GRID;
5354
static title = 'Grid aggregation';
5455
static description = 'Geospatial data grouped in grids with metrics for each gridded cell';
5556

x-pack/plugins/maps/public/shared/layers/sources/es_search_source/es_search_source.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import { AbstractESSource } from '../es_source';
1212
import { hitsToGeoJson } from '../../../../elasticsearch_geo_utils';
1313
import { CreateSourceEditor } from './create_source_editor';
1414
import { UpdateSourceEditor } from './update_source_editor';
15+
import { ES_SEARCH } from '../../../../../common/constants';
1516

1617
const DEFAULT_LIMIT = 2048;
1718

1819
export class ESSearchSource extends AbstractESSource {
1920

20-
static type = 'ES_SEARCH';
21+
static type = ES_SEARCH;
2122
static title = 'Documents';
2223
static description = 'Geospatial data from a Kibana index pattern';
2324

0 commit comments

Comments
 (0)