Legacy SO import: Fix bug causing multiple overrides to only show the last confirm modal (#76482)

* Legacy SO import: Fix bug causing multiple overrides to only show the last confirm modal

* eslint

* fix for loops
This commit is contained in:
Pierre Gayvallet 2020-09-03 08:50:03 +02:00 committed by GitHub
parent d3416c0189
commit 210d6f2df1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 379 additions and 22 deletions

View file

@ -160,10 +160,6 @@ function groupByType(docs: SavedObjectsRawDoc[]): Record<string, SavedObjectsRaw
}, defaultDocTypes);
}
async function awaitEachItemInParallel<T, R>(list: T[], op: (item: T) => R) {
return await Promise.all(list.map((item) => op(item)));
}
export async function resolveIndexPatternConflicts(
resolutions: Array<{ oldId: string; newId: string }>,
conflictedIndexPatterns: any[],
@ -175,7 +171,7 @@ export async function resolveIndexPatternConflicts(
) {
let importCount = 0;
await awaitEachItemInParallel(conflictedIndexPatterns, async ({ obj, doc }) => {
for (const { obj, doc } of conflictedIndexPatterns) {
const serializedSearchSource = JSON.parse(
doc._source.kibanaSavedObjectMeta?.searchSourceJSON || '{}'
);
@ -220,7 +216,7 @@ export async function resolveIndexPatternConflicts(
if (!allResolved) {
// The user decided to skip this conflict so do nothing
return;
continue;
}
obj.searchSource = await dependencies.search.searchSource.create(
serializedSearchSourceWithInjectedReferences
@ -228,17 +224,17 @@ export async function resolveIndexPatternConflicts(
if (await saveObject(obj, overwriteAll)) {
importCount++;
}
});
}
return importCount;
}
export async function saveObjects(objs: SavedObject[], overwriteAll: boolean) {
let importCount = 0;
await awaitEachItemInParallel(objs, async (obj) => {
for (const obj of objs) {
if (await saveObject(obj, overwriteAll)) {
importCount++;
}
});
}
return importCount;
}
@ -253,16 +249,16 @@ export async function resolveSavedSearches(
overwriteAll: boolean
) {
let importCount = 0;
await awaitEachItemInParallel(savedSearches, async (searchDoc) => {
for (const searchDoc of savedSearches) {
const obj = await getSavedObject(searchDoc, services);
if (!obj) {
// Just ignore?
return;
continue;
}
if (await importDocument(obj, searchDoc, overwriteAll)) {
importCount++;
}
});
}
return importCount;
}
@ -280,7 +276,10 @@ export async function resolveSavedObjects(
let importedObjectCount = 0;
const failedImports: FailedImport[] = [];
// Start with the index patterns since everything is dependent on them
await awaitEachItemInParallel(docTypes.indexPatterns, async (indexPatternDoc) => {
// As the confirmation opens a modal, and as we only allow one modal at a time
// (opening a new one close the previous with a rejection)
// we can't do that in parallel
for (const indexPatternDoc of docTypes.indexPatterns) {
try {
const importedIndexPatternId = await importIndexPattern(
indexPatternDoc,
@ -294,7 +293,7 @@ export async function resolveSavedObjects(
} catch (error) {
failedImports.push({ obj: indexPatternDoc as any, error });
}
});
}
// We want to do the same for saved searches, but we want to keep them separate because they need
// to be applied _first_ because other saved objects can be dependent on those saved searches existing
@ -311,7 +310,7 @@ export async function resolveSavedObjects(
// likely that these saved objects will work once resaved so keep them around to resave them.
const conflictedSavedObjectsLinkedToSavedSearches: any[] = [];
await awaitEachItemInParallel(docTypes.searches, async (searchDoc) => {
for (const searchDoc of docTypes.searches) {
const obj = await getSavedObject(searchDoc, services);
try {
@ -329,9 +328,9 @@ export async function resolveSavedObjects(
failedImports.push({ obj, error });
}
}
});
}
await awaitEachItemInParallel(docTypes.other, async (otherDoc) => {
for (const otherDoc of docTypes.other) {
const obj = await getSavedObject(otherDoc, services);
try {
@ -350,7 +349,7 @@ export async function resolveSavedObjects(
failedImports.push({ obj, error });
}
}
});
}
return {
conflictedIndexPatterns,

View file

@ -21,6 +21,8 @@ import expect from '@kbn/expect';
import path from 'path';
import { keyBy } from 'lodash';
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export default function ({ getService, getPageObjects }) {
const kibanaServer = getService('kibanaServer');
const esArchiver = getService('esArchiver');
@ -203,12 +205,12 @@ export default function ({ getService, getPageObjects }) {
// delete .kibana index and then wait for Kibana to re-create it
await kibanaServer.uiSettings.replace({});
await PageObjects.settings.navigateTo();
await esArchiver.load('management');
await esArchiver.load('saved_objects_imports');
await PageObjects.settings.clickKibanaSavedObjects();
});
afterEach(async function () {
await esArchiver.unload('management');
await esArchiver.unload('saved_objects_imports');
});
it('should import saved objects', async function () {
@ -280,6 +282,54 @@ export default function ({ getService, getPageObjects }) {
expect(isSuccessful).to.be(true);
});
it('should allow the user to confirm overriding multiple duplicate saved objects', async function () {
// This data has already been loaded by the "visualize" esArchive. We'll load it again
// so that we can override the existing visualization.
await PageObjects.savedObjects.importFile(
path.join(__dirname, 'exports', '_import_objects_multiple_exists.json'),
false
);
await PageObjects.savedObjects.checkImportLegacyWarning();
await PageObjects.savedObjects.checkImportConflictsWarning();
await PageObjects.settings.associateIndexPattern('logstash-*', 'logstash-*');
await PageObjects.savedObjects.clickConfirmChanges();
// Override the visualizations.
await PageObjects.common.clickConfirmOnModal(false);
// as the second confirm can pop instantly, we can't wait for it to be hidden
// with is why we call clickConfirmOnModal with ensureHidden: false in previous statement
// but as the initial popin can take a few ms before fading, we need to wait a little
// to avoid clicking twice on the same modal.
await delay(1000);
await PageObjects.common.clickConfirmOnModal(false);
const isSuccessful = await testSubjects.exists('importSavedObjectsSuccess');
expect(isSuccessful).to.be(true);
});
it('should allow the user to confirm overriding multiple duplicate index patterns', async function () {
// This data has already been loaded by the "visualize" esArchive. We'll load it again
// so that we can override the existing visualization.
await PageObjects.savedObjects.importFile(
path.join(__dirname, 'exports', '_import_index_patterns_multiple_exists.json'),
false
);
// Override the index patterns.
await PageObjects.common.clickConfirmOnModal(false);
// as the second confirm can pop instantly, we can't wait for it to be hidden
// with is why we call clickConfirmOnModal with ensureHidden: false in previous statement
// but as the initial popin can take a few ms before fading, we need to wait a little
// to avoid clicking twice on the same modal.
await delay(1000);
await PageObjects.common.clickConfirmOnModal(false);
const isSuccessful = await testSubjects.exists('importSavedObjectsSuccess');
expect(isSuccessful).to.be(true);
});
it('should import saved objects linked to saved searches', async function () {
await PageObjects.savedObjects.importFile(
path.join(__dirname, 'exports', '_import_objects_saved_search.json')

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,36 @@
[
{
"_id": "test-1",
"_type": "visualization",
"_source": {
"title": "Visualization test 1",
"visState": "{\"title\":\"New Visualization\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}",
"uiStateJSON": "{}",
"description": "AreaChart",
"version": 1,
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
}
},
"_meta": {
"savedObjectVersion": 2
}
},
{
"_id": "test-2",
"_type": "visualization",
"_source": {
"title": "Visualization test 2",
"visState": "{\"title\":\"New Visualization\",\"type\":\"area\",\"params\":{\"shareYAxis\":true,\"addTooltip\":true,\"addLegend\":true,\"smoothLines\":false,\"scale\":\"linear\",\"interpolate\":\"linear\",\"mode\":\"stacked\",\"times\":[],\"addTimeMarker\":false,\"defaultYExtents\":false,\"setYExtents\":false,\"yAxis\":{}},\"aggs\":[{\"id\":\"1\",\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"type\":\"date_histogram\",\"schema\":\"segment\",\"params\":{\"field\":\"@timestamp\",\"interval\":\"auto\",\"customInterval\":\"2h\",\"min_doc_count\":1,\"extended_bounds\":{}}}],\"listeners\":{}}",
"uiStateJSON": "{}",
"description": "AreaChart",
"version": 1,
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"index\":\"logstash-*\",\"query\":{\"query_string\":{\"query\":\"*\",\"analyze_wildcard\":true}},\"filter\":[]}"
}
},
"_meta": {
"savedObjectVersion": 2
}
}
]

View file

@ -0,0 +1,244 @@
{
"type": "index",
"value": {
"index": ".kibana",
"mappings": {
"properties": {
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"dashboard": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"optionsJSON": {
"type": "text"
},
"panelsJSON": {
"type": "text"
},
"refreshInterval": {
"properties": {
"display": {
"type": "keyword"
},
"pause": {
"type": "boolean"
},
"section": {
"type": "integer"
},
"value": {
"type": "integer"
}
}
},
"timeFrom": {
"type": "keyword"
},
"timeRestore": {
"type": "boolean"
},
"timeTo": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"index-pattern": {
"dynamic": "strict",
"properties": {
"fieldFormatMap": {
"type": "text"
},
"fields": {
"type": "text"
},
"intervalName": {
"type": "keyword"
},
"notExpandable": {
"type": "boolean"
},
"sourceFilters": {
"type": "text"
},
"timeFieldName": {
"type": "keyword"
},
"title": {
"type": "text"
}
}
},
"search": {
"dynamic": "strict",
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"server": {
"dynamic": "strict",
"properties": {
"uuid": {
"type": "keyword"
}
}
},
"timelion-sheet": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"timelion_chart_height": {
"type": "integer"
},
"timelion_columns": {
"type": "integer"
},
"timelion_interval": {
"type": "keyword"
},
"timelion_other_interval": {
"type": "keyword"
},
"timelion_rows": {
"type": "integer"
},
"timelion_sheet": {
"type": "text"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"type": {
"type": "keyword"
},
"url": {
"dynamic": "strict",
"properties": {
"accessCount": {
"type": "long"
},
"accessDate": {
"type": "date"
},
"createDate": {
"type": "date"
},
"url": {
"fields": {
"keyword": {
"ignore_above": 2048,
"type": "keyword"
}
},
"type": "text"
}
}
},
"visualization": {
"dynamic": "strict",
"properties": {
"description": {
"type": "text"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"savedSearchId": {
"type": "keyword"
},
"title": {
"type": "text"
},
"uiStateJSON": {
"type": "text"
},
"version": {
"type": "integer"
},
"visState": {
"type": "text"
}
}
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}

View file

@ -332,12 +332,14 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
});
}
async clickConfirmOnModal() {
async clickConfirmOnModal(ensureHidden = true) {
log.debug('Clicking modal confirm');
// make sure this data-test-subj 'confirmModalTitleText' exists because we're going to wait for it to be gone later
await testSubjects.exists('confirmModalTitleText');
await testSubjects.click('confirmModalConfirmButton');
await this.ensureModalOverlayHidden();
if (ensureHidden) {
await this.ensureModalOverlayHidden();
}
}
async pressEnterKey() {