Skip to content

Commit

Permalink
Resolve filter index references when importing saved objects (#42974) (
Browse files Browse the repository at this point in the history
…#44480)

* Resolve filter index references when importing saved objects

* Tests for filter index resolution

* saved_object.js test for serialization of non-existing searchSource indexes
  • Loading branch information
rudolf authored Aug 30, 2019
1 parent ffbb7e7 commit 957f667
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 12 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -418,8 +418,12 @@ describe('Flyout', () => {
},
obj: {
searchSource: {
getOwnField: () => 'MyIndexPattern*',
getOwnField: (field) => {
if(field === 'index') { return 'MyIndexPattern*';}
if(field === 'filter') { return [{ meta: { index: 'filterIndex' } }];}
},
},
_serialize: () => { return { references: [{ id: 'MyIndexPattern*' }, { id: 'filterIndex' }] };},
},
},
];
Expand Down Expand Up @@ -477,7 +481,17 @@ describe('Flyout', () => {
type: 'index-pattern',
},
],
},
}, {
'existingIndexPatternId': 'filterIndex',
'list': [
{
'id': 'filterIndex',
'title': 'MyIndexPattern*',
'type': 'index-pattern',
},
],
'newIndexPatternId': undefined,
}
],
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { groupBy, take, get as getField } from 'lodash';
import { take, get as getField } from 'lodash';
import {
EuiFlyout,
EuiFlyoutBody,
Expand Down Expand Up @@ -273,9 +273,15 @@ class FlyoutUI extends Component {
confirmModalPromise
);

const byId = groupBy(conflictedIndexPatterns, ({ obj }) =>
obj.searchSource.getOwnField('index')
);
const byId = {};
conflictedIndexPatterns
.map(({ doc, obj }) => {
return { doc, obj: obj._serialize() };
}).forEach(({ doc, obj }) =>
obj.references.forEach(ref => {
byId[ref.id] = byId[ref.id] != null ? byId[ref.id].concat({ doc, obj }) : [{ doc, obj }];
})
);
const unmatchedReferences = Object.entries(byId).reduce(
(accum, [existingIndexPatternId, list]) => {
accum.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ describe('resolveSavedObjects', () => {
{
obj: {
searchSource: {
getOwnField: () => '1',
getOwnField: (field) => {
return field === 'index' ? '1' : undefined;
},
},
hydrateIndexPattern,
save,
Expand All @@ -247,7 +249,9 @@ describe('resolveSavedObjects', () => {
{
obj: {
searchSource: {
getOwnField: () => '3',
getOwnField: (field) => {
return field === 'index' ? '3' : undefined;
},
},
hydrateIndexPattern,
save,
Expand Down Expand Up @@ -283,6 +287,63 @@ describe('resolveSavedObjects', () => {
expect(hydrateIndexPattern).toHaveBeenCalledWith('2');
expect(hydrateIndexPattern).toHaveBeenCalledWith('4');
});

it('should resolve filter index conflicts', async () => {
const hydrateIndexPattern = jest.fn();
const save = jest.fn();

const conflictedIndexPatterns = [
{
obj: {
searchSource: {
getOwnField: (field) => {
return field === 'index' ? '1' : [{ meta: { index: 'filterIndex' } }];
},
setField: jest.fn(),
},
hydrateIndexPattern,
save,
},
},
{
obj: {
searchSource: {
getOwnField: (field) => {
return field === 'index' ? '3' : undefined;
},
},
hydrateIndexPattern,
save,
},
},
];

const resolutions = [
{
oldId: '1',
newId: '2',
},
{
oldId: '3',
newId: '4',
},
{
oldId: 'filterIndex',
newId: 'newFilterIndex',
},
];

const overwriteAll = false;

await resolveIndexPatternConflicts(
resolutions,
conflictedIndexPatterns,
overwriteAll
);

expect(conflictedIndexPatterns[0].obj.searchSource.setField).toHaveBeenCalledWith('filter', [{ meta: { index: 'newFilterIndex' } }]);
expect(save.mock.calls.length).toBe(2);
});
});

describe('saveObjects', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,19 +140,38 @@ export async function resolveIndexPatternConflicts(
overwriteAll
) {
let importCount = 0;

await awaitEachItemInParallel(conflictedIndexPatterns, async ({ obj }) => {
// Resolve search index reference:
let oldIndexId = obj.searchSource.getOwnField('index');
// Depending on the object, this can either be the raw id or the actual index pattern object
if (typeof oldIndexId !== 'string') {
oldIndexId = oldIndexId.id;
}
const resolution = resolutions.find(({ oldId }) => oldId === oldIndexId);
let resolution = resolutions.find(({ oldId }) => oldId === oldIndexId);
if (resolution) {
const newIndexId = resolution.newId;
await obj.hydrateIndexPattern(newIndexId);
}

// Resolve filter index reference:
const filter = (obj.searchSource.getOwnField('filter') || []).map((filter) => {
if (!(filter.meta && filter.meta.index)) {
return filter;
}

resolution = resolutions.find(({ oldId }) => oldId === filter.meta.index);
return resolution ? ({ ...filter, ...{ meta: { ...filter.meta, index: resolution.newId } } }) : filter;
});

if (filter.length > 0) {
obj.searchSource.setField('filter', filter);
}

if (!resolution) {
// The user decided to skip this conflict so do nothing
return;
}
const newIndexId = resolution.newId;
await obj.hydrateIndexPattern(newIndexId);
if (await saveObject(obj, overwriteAll)) {
importCount++;
}
Expand Down
34 changes: 34 additions & 0 deletions src/legacy/ui/public/saved_objects/__tests__/saved_object.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,40 @@ describe('Saved Object', function () {
});
});

it('when index in searchSourceJSON is not found', () => {
const id = '123';
stubESResponse(getMockedDocResponse(id));
return createInitializedSavedObject({ type: 'dashboard', searchSource: true })
.then((savedObject) => {
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
return BluebirdPromise.resolve({
id,
version: 2,
type: 'dashboard',
});
});
savedObject.searchSource.setFields({ 'index': 'non-existant-index' });
return savedObject
.save()
.then(() => {
expect(savedObjectsClientStub.create.getCall(0).args[1]).to.eql({
kibanaSavedObjectMeta: {
searchSourceJSON: JSON.stringify({
indexRefName: 'kibanaSavedObjectMeta.searchSourceJSON.index',
}),
},
});
const { references } = savedObjectsClientStub.create.getCall(0).args[2];
expect(references).to.have.length(1);
expect(references[0]).to.eql({
name: 'kibanaSavedObjectMeta.searchSourceJSON.index',
type: 'index-pattern',
id: 'non-existant-index',
});
});
});
});

it('when indexes exists in filter of searchSourceJSON', () => {
const id = '123';
stubESResponse(getMockedDocResponse(id));
Expand Down
5 changes: 4 additions & 1 deletion src/legacy/ui/public/saved_objects/saved_object.js
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,10 @@ export function SavedObjectProvider(Promise, Private, confirmModalPromise, index
if (this.searchSource) {
let searchSourceFields = _.omit(this.searchSource.getFields(), ['sort', 'size']);
if (searchSourceFields.index) {
const { id: indexId } = searchSourceFields.index;
// searchSourceFields.index will normally be an IndexPattern, but can be a string in two scenarios:
// (1) `init()` (and by extension `hydrateIndexPattern()`) hasn't been called on this Saved Object
// (2) The IndexPattern doesn't exist, so we fail to resolve it in `hydrateIndexPattern()`
const indexId = typeof (searchSourceFields.index) === 'string' ? searchSourceFields.index : searchSourceFields.index.id;
const refName = 'kibanaSavedObjectMeta.searchSourceJSON.index';
references.push({
name: refName,
Expand Down

0 comments on commit 957f667

Please sign in to comment.