mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
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:
parent
d3416c0189
commit
210d6f2df1
7 changed files with 379 additions and 22 deletions
|
@ -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,
|
||||
|
|
|
@ -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
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
Binary file not shown.
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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() {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue