From 896cb26010668e8550965998ea3e15e7271bfee2 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Wed, 4 Oct 2017 20:42:07 -0400 Subject: [PATCH] [Management] Allows for imports to select existing index (#14137) * Adds ability to change index pattern on import Signed-off-by: Tyler Smalley * UI changes. Use a table in the modal grouped by index pattern id instead of multiple modals. * PR feedback * PR feedback * PR updates * Handle skip properly * Fix error when there were no existing index patterns * Tests for the new import conflict logic * Fix invisible filters caused by missing index pattern (#14131) "invisible filters" occur when the mapping chain throws an error. If a single filter throws an error, the entire chain rejects. As a result, not even the valid filters appear in the filter bar because they never get added to the scope. However the filters still exist in app state and still get sent with each search request. The most common error occurs when the filter's meta.index property points to a non-existing index pattern. Since this property is only used for looking up field formatters and it is not essential for a working filter, we now fall back on raw values instead of failing if the index pattern is not found. See the PR this one replaces for discussion about other solutions we tried and why we chose to go this route. * Show query and filter bars even when there's a linked search (#14212) The query bar used to be hidden in the presence of a linked search because unlike filters, queries didn't get merged when flattening a SearchSource hierarchy. That means a query in the query bar would override the query in the linked search. This is no longer the case. As of 6.0 we include all queries in the SearchSource hierarchy in the final request, so there's no longer any reason to hide the query bar. Since filters created via a Vis show up in the query bar when Kuery is selected, these filters now appear correctly even when there's a linked search in the vis editor. Previously when unlinking a saved search visualize would insert the query and filters from the saved search into app state before removing the SearchSource from the hierarcy. This posed a problem because combining two lucene query strings isn't as easy as combing two sets of filters. We decided this behavior was a bit counterintuitive anyway. If the user wants to unlink the saved search, they probably want to discard it, not combine it with their local changes. So I've also updated the unlinking functionality to discard the saved search. * limit wait time for baselayer (#14047) * adding scope appy back (#14269) * remove junk tests (#14191) * We are using the index pattern id now * Use the index pattern id here too * Use an isolated es env for these tests * Revert "Fix invisible filters caused by missing index pattern (#14131)" This reverts commit e09d7ad1f967347010503a6b9aea4c2015d2dde3. * Revert "Show query and filter bars even when there's a linked search (#14212)" This reverts commit 3aee7c2bf07182f4ec98e4358ad53633c928a47b. * Revert "limit wait time for baselayer (#14047)" This reverts commit 44a71071ac330ca22fdb0625b8d8d91bbf5b0183. * Revert "adding scope appy back (#14269)" This reverts commit 51b6b51aac34941448706d8eb8fbad8076d2c81d. * Revert "remove junk tests (#14191)" This reverts commit f06c18332b23a5a5b9ae008fbe3b1448c52fafd8. * Revert these --- .../management/sections/objects/_objects.html | 5 +- .../management/sections/objects/_objects.js | 146 +++++++---- .../sections/objects/change_index_modal.js | 215 +++++++++++++++ .../objects/show_change_index_modal.js | 28 ++ .../kibana/public/management/styles/main.less | 4 + .../courier/saved_object/saved_object.js | 8 +- .../apps/management/_import_objects.js | 93 +++++++ .../exports/_import_objects-conflicts.json | 19 ++ .../management/exports/_import_objects.json | 19 ++ test/functional/apps/management/index.js | 1 + .../es_archiver/management/data.json.gz | Bin 0 -> 1279 bytes .../es_archiver/management/mappings.json | 246 ++++++++++++++++++ test/functional/page_objects/common_page.js | 8 +- test/functional/page_objects/settings_page.js | 27 ++ 14 files changed, 762 insertions(+), 57 deletions(-) create mode 100644 src/core_plugins/kibana/public/management/sections/objects/change_index_modal.js create mode 100644 src/core_plugins/kibana/public/management/sections/objects/show_change_index_modal.js create mode 100644 test/functional/apps/management/_import_objects.js create mode 100644 test/functional/apps/management/exports/_import_objects-conflicts.json create mode 100644 test/functional/apps/management/exports/_import_objects.json create mode 100644 test/functional/fixtures/es_archiver/management/data.json.gz create mode 100644 test/functional/fixtures/es_archiver/management/mappings.json diff --git a/src/core_plugins/kibana/public/management/sections/objects/_objects.html b/src/core_plugins/kibana/public/management/sections/objects/_objects.html index e68ebd09e094..7b3a8e2e3418 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/_objects.html +++ b/src/core_plugins/kibana/public/management/sections/objects/_objects.html @@ -11,6 +11,7 @@

- +
@@ -154,6 +156,7 @@

diff --git a/src/core_plugins/kibana/public/management/sections/objects/_objects.js b/src/core_plugins/kibana/public/management/sections/objects/_objects.js index 80a5f8991309..c030568450fb 100644 --- a/src/core_plugins/kibana/public/management/sections/objects/_objects.js +++ b/src/core_plugins/kibana/public/management/sections/objects/_objects.js @@ -7,10 +7,25 @@ import 'ui/directives/file_upload'; import uiRoutes from 'ui/routes'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { uiModules } from 'ui/modules'; +import { showChangeIndexModal } from './show_change_index_modal'; +import { SavedObjectNotFound } from 'ui/errors'; + +const indexPatternsResolutions = { + indexPatterns: function (Private) { + const savedObjectsClient = Private(SavedObjectsClientProvider); + + return savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title'], + perPage: 10000 + }).then(response => response.savedObjects); + } +}; uiRoutes .when('/management/kibana/objects', { - template: objectIndexHTML + template: objectIndexHTML, + resolve: indexPatternsResolutions }); uiRoutes @@ -19,7 +34,7 @@ uiRoutes }); uiModules.get('apps/management') -.directive('kbnManagementObjects', function (kbnIndex, Notifier, Private, kbnUrl, Promise, confirmModal) { +.directive('kbnManagementObjects', function ($route, kbnIndex, Notifier, Private, kbnUrl, Promise, confirmModal) { const savedObjectsClient = Private(SavedObjectsClientProvider); return { @@ -188,61 +203,92 @@ uiModules.get('apps/management') } ); }) - .then((overwriteAll) => { - function importDocument(doc) { - const { service } = find($scope.services, { type: doc._type }) || {}; + .then((overwriteAll) => { + const conflictedIndexPatterns = []; - if (!service) { - const msg = `Skipped import of "${doc._source.title}" (${doc._id})`; - const reason = `Invalid type: "${doc._type}"`; + function importDocument(doc) { + const { service } = find($scope.services, { type: doc._type }) || {}; - notify.warning(`${msg}, ${reason}`, { - lifetime: 0, - }); - - return; - } + if (!service) { + const msg = `Skipped import of "${doc._source.title}" (${doc._id})`; + const reason = `Invalid type: "${doc._type}"`; - return service.get() - .then(function (obj) { - obj.id = doc._id; - return obj.applyESResp(doc) - .then(() => { - return obj.save({ confirmOverwrite : !overwriteAll }); - }) - .catch((err) => { - // swallow errors here so that the remaining promise chain executes - err.message = `Importing ${obj.title} (${obj.id}) failed: ${err.message}`; - notify.error(err); - }); - }); - } - - function groupByType(docs) { - const defaultDocTypes = { - searches: [], - other: [], - }; + notify.warning(`${msg}, ${reason}`, { + lifetime: 0, + }); - return docs.reduce((types, doc) => { - switch (doc._type) { - case 'search': - types.searches.push(doc); - break; - default: - types.other.push(doc); - } - return types; - }, defaultDocTypes); + return; } - const docTypes = groupByType(docs); + return service.get() + .then(function (obj) { + obj.id = doc._id; + return obj.applyESResp(doc) + .then(() => { + return obj.save({ confirmOverwrite : !overwriteAll }); + }) + .catch((err) => { + if (err instanceof SavedObjectNotFound && err.savedObjectType === 'index-pattern') { + conflictedIndexPatterns.push({ obj, doc }); + return; + } + + // swallow errors here so that the remaining promise chain executes + err.message = `Importing ${obj.title} (${obj.id}) failed: ${err.message}`; + notify.error(err); + }); + }); + } + + function groupByType(docs) { + const defaultDocTypes = { + searches: [], + other: [], + }; - return Promise.map(docTypes.searches, importDocument) - .then(() => Promise.map(docTypes.other, importDocument)) - .then(refreshData) - .catch(notify.error); - }); + return docs.reduce((types, doc) => { + switch (doc._type) { + case 'search': + types.searches.push(doc); + break; + default: + types.other.push(doc); + } + return types; + }, defaultDocTypes); + } + + const docTypes = groupByType(docs); + + return Promise.map(docTypes.searches, importDocument) + .then(() => Promise.map(docTypes.other, importDocument)) + .then(() => { + if (conflictedIndexPatterns.length) { + showChangeIndexModal( + (objs) => { + return Promise.map( + conflictedIndexPatterns, + ({ obj }) => { + const oldIndexId = obj.searchSource.getOwn('index'); + const newIndexId = objs.find(({ oldId }) => oldId === oldIndexId).newId; + if (newIndexId === oldIndexId) { + // Skip + return; + } + return obj.hydrateIndexPattern(newIndexId) + .then(() => obj.save({ confirmOverwrite : !overwriteAll })); + } + ).then(refreshData); + }, + conflictedIndexPatterns, + $route.current.locals.indexPatterns, + ); + } else { + return refreshData(); + } + }) + .catch(notify.error); + }); }; // TODO: Migrate all scope methods to the controller. diff --git a/src/core_plugins/kibana/public/management/sections/objects/change_index_modal.js b/src/core_plugins/kibana/public/management/sections/objects/change_index_modal.js new file mode 100644 index 000000000000..7594caf8d6e0 --- /dev/null +++ b/src/core_plugins/kibana/public/management/sections/objects/change_index_modal.js @@ -0,0 +1,215 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { groupBy, mapValues, take, get } from 'lodash'; + +import { + KuiModal, + KuiModalHeader, + KuiModalHeaderTitle, + KuiModalBody, + KuiModalBodyText, + KuiModalFooter, + KuiButton, + KuiModalOverlay, + KuiTable, + KuiTableBody, + KuiTableHeader, + KuiTableHeaderCell, + KuiTableRow, + KuiTableRowCell, + KuiControlledTable, + KuiToolBar, + KuiToolBarSection, + KuiPager, +} from 'ui_framework/components'; + +import { keyCodes } from 'ui_framework/services'; + +export class ChangeIndexModal extends React.Component { + constructor(props) { + super(props); + + const byId = groupBy(props.conflictedObjects, ({ obj }) => obj.searchSource.getOwn('index')); + this.state = { + page: 0, + perPage: 10, + objects: mapValues(byId, (list, indexPatternId) => { + return { + newIndexPatternId: get(props, 'indices[0].id'), + list: list.map(({ doc }) => { + return { + id: indexPatternId, + type: doc._type, + name: doc._source.title, + }; + }) + }; + }) + }; + } + + changeIndex = () => { + const result = Object.keys(this.state.objects).map(indexPatternId => ({ + oldId: indexPatternId, + newId: this.state.objects[indexPatternId].newIndexPatternId, + })); + this.props.onChange(result); + }; + + onIndexChange = (id, event) => { + event.persist(); + this.setState(state => { + return { + objects: { + ...state.objects, + [id]: { + ...state.objects[id], + newIndexPatternId: event.target.value, + } + } + }; + }); + }; + + onKeyDown = (event) => { + if (event.keyCode === keyCodes.ESCAPE) { + this.props.onClose(); + } + }; + + render() { + const { page, perPage } = this.state; + const totalIndexPatterns = Object.keys(this.state.objects).length; + const indexPatternIds = Object.keys(this.state.objects).slice(page, page + perPage); + const rows = indexPatternIds.map((indexPatternId, key) => { + const objects = this.state.objects[indexPatternId].list; + const sample = take(objects, 5).map((obj, key) => {obj.name}
); + + return ( + + + {indexPatternId} + + + {objects.length} + + + {sample} + + + + + + ); + }); + + const TableComponent = () => ( + + + + ID + + + Count + + + Sample of affected objects + + + New index pattern + + + + {rows} + + + ); + + return ( + + + + + Index Pattern Conflicts + + + + +

+ The following saved objects use index patterns that do not exist. + Please select the index patterns you'd like re-associated them with. +

+
+ { totalIndexPatterns > perPage + ? + + + + = 1} + hasNextPage={page < totalIndexPatterns} + endNumber={Math.min(totalIndexPatterns, page + perPage)} + totalItems={totalIndexPatterns} + onNextPage={() => this.setState({ page: page + 1 })} + onPreviousPage={() => this.setState({ page: page - 1 })} + /> + + + + + : + + } +
+ + + + Cancel + + + Confirm all changes + + +
+
+ ); + } +} + +ChangeIndexModal.propTypes = { + onChange: PropTypes.func, + onClose: PropTypes.func, + conflictedObjects: PropTypes.arrayOf(PropTypes.shape({ + obj: PropTypes.object.isRequired, + doc: PropTypes.object.isRequired, + })).isRequired, + indices: PropTypes.array +}; diff --git a/src/core_plugins/kibana/public/management/sections/objects/show_change_index_modal.js b/src/core_plugins/kibana/public/management/sections/objects/show_change_index_modal.js new file mode 100644 index 000000000000..adacdceff702 --- /dev/null +++ b/src/core_plugins/kibana/public/management/sections/objects/show_change_index_modal.js @@ -0,0 +1,28 @@ +import { ChangeIndexModal } from './change_index_modal'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +export function showChangeIndexModal(onChange, conflictedObjects, indices = []) { + const container = document.createElement('div'); + const closeModal = () => { + document.body.removeChild(container); + }; + + const onIndexChangeConfirmed = (newIndex) => { + onChange(newIndex); + closeModal(); + }; + + document.body.appendChild(container); + + const element = ( + + ); + + ReactDOM.render(element, container); +} diff --git a/src/core_plugins/kibana/public/management/styles/main.less b/src/core_plugins/kibana/public/management/styles/main.less index fab917773321..80a1a8fe8e96 100644 --- a/src/core_plugins/kibana/public/management/styles/main.less +++ b/src/core_plugins/kibana/public/management/styles/main.less @@ -125,6 +125,10 @@ kbn-management-objects { } } +.managementChangeIndexModal { + width: 800px; +} + kbn-management-advanced { // super specific rule to override bootstrap's equally specific rule // https://github.com/twbs/bootstrap/blob/1f329f8f17aa989eabc6e94bdcab93e69ef0e463/less/tables.less#L35 diff --git a/src/ui/public/courier/saved_object/saved_object.js b/src/ui/public/courier/saved_object/saved_object.js index 92bec64d2e53..4164534c3941 100644 --- a/src/ui/public/courier/saved_object/saved_object.js +++ b/src/ui/public/courier/saved_object/saved_object.js @@ -118,7 +118,7 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm * * @return {Promise} */ - const hydrateIndexPattern = () => { + this.hydrateIndexPattern = (id) => { if (!this.searchSource) { return Promise.resolve(null); } @@ -128,7 +128,7 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm return Promise.resolve(null); } - let index = config.indexPattern || this.searchSource.getOwn('index'); + let index = id || config.indexPattern || this.searchSource.getOwn('index'); if (!index) { return Promise.resolve(null); @@ -163,7 +163,7 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm if (!this.id) { // just assign the defaults and be done _.assign(this, this.defaults); - return hydrateIndexPattern().then(() => { + return this.hydrateIndexPattern().then(() => { return afterESResp.call(this); }); } @@ -217,7 +217,7 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm return Promise.try(() => { parseSearchSource(meta.searchSourceJSON); - return hydrateIndexPattern(); + return this.hydrateIndexPattern(); }).then(() => { return Promise.cast(afterESResp.call(this, resp)); }); diff --git a/test/functional/apps/management/_import_objects.js b/test/functional/apps/management/_import_objects.js new file mode 100644 index 000000000000..090ea925fd9e --- /dev/null +++ b/test/functional/apps/management/_import_objects.js @@ -0,0 +1,93 @@ +import expect from 'expect.js'; +import path from 'path'; + +export default function ({ getService, getPageObjects }) { + const retry = getService('retry'); + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + const PageObjects = getPageObjects(['common', 'settings']); + + describe('import objects', function describeIndexTests() { + before(async function () { + // delete .kibana index and then wait for Kibana to re-create it + await kibanaServer.uiSettings.replace({}); + await PageObjects.settings.navigateTo(); + await esArchiver.load('management'); + }); + + after(async function () { + await esArchiver.unload('management'); + }); + + it('should import saved objects normally', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects.json')); + await PageObjects.common.clickConfirmOnModal(); + await PageObjects.settings.clickVisualizationsTab(); + const rowCount = await retry.try(async () => { + const rows = await PageObjects.settings.getVisualizationRows(); + if (rows.length !== 2) { + throw 'Not loaded yet'; + } + return rows.length; + }); + expect(rowCount).to.be(2); + }); + + it('should import conflicts using a confirm modal', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects-conflicts.json')); + await PageObjects.common.clickConfirmOnModal(); + await PageObjects.settings.setImportIndexFieldOption(2); + await PageObjects.settings.clickChangeIndexConfirmButton(); + await PageObjects.settings.clickVisualizationsTab(); + const rowCount = await retry.try(async () => { + const rows = await PageObjects.settings.getVisualizationRows(); + if (rows.length !== 2) { + throw 'Not loaded yet'; + } + return rows.length; + }); + expect(rowCount).to.be(2); + }); + + it('should allow for overrides', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects.json')); + await PageObjects.common.clickConfirmOnModal(); + await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects.json')); + await PageObjects.common.clickConfirmOnModal(); + await PageObjects.settings.clickVisualizationsTab(); + const rowCount = await retry.try(async () => { + const rows = await PageObjects.settings.getVisualizationRows(); + if (rows.length !== 2) { + throw 'Not loaded yet'; + } + return rows.length; + }); + expect(rowCount).to.be(2); + }); + + it('should allow for cancelling overrides', async function () { + await PageObjects.settings.navigateTo(); + await PageObjects.settings.clickKibanaSavedObjects(); + await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects.json')); + await PageObjects.common.clickConfirmOnModal(); + await PageObjects.settings.importFile(path.join(__dirname, 'exports', '_import_objects.json')); + await PageObjects.common.clickCancelOnModal(true); + await PageObjects.common.clickConfirmOnModal(); + await PageObjects.settings.clickVisualizationsTab(); + const rowCount = await retry.try(async () => { + const rows = await PageObjects.settings.getVisualizationRows(); + if (rows.length !== 2) { + throw 'Not loaded yet'; + } + return rows.length; + }); + expect(rowCount).to.be(2); + }); + }); +} diff --git a/test/functional/apps/management/exports/_import_objects-conflicts.json b/test/functional/apps/management/exports/_import_objects-conflicts.json new file mode 100644 index 000000000000..4ca017bacac8 --- /dev/null +++ b/test/functional/apps/management/exports/_import_objects-conflicts.json @@ -0,0 +1,19 @@ +[ + { + "_id": "082f1d60-a2e7-11e7-bb30-233be9be6a15", + "_type": "visualization", + "_source": { + "title": "Log Agents", + "visState": "{\"title\":\"Log Agents\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"agent.raw: Descending\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"agent.raw\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"d1e4c910-a2e6-11e7-bb30-233be9be6a15\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + } +] diff --git a/test/functional/apps/management/exports/_import_objects.json b/test/functional/apps/management/exports/_import_objects.json new file mode 100644 index 000000000000..48015d64133f --- /dev/null +++ b/test/functional/apps/management/exports/_import_objects.json @@ -0,0 +1,19 @@ +[ + { + "_id": "082f1d60-a2e7-11e7-bb30-233be9be6a15", + "_type": "visualization", + "_source": { + "title": "Log Agents", + "visState": "{\"title\":\"Log Agents\",\"type\":\"area\",\"params\":{\"type\":\"area\",\"grid\":{\"categoryLines\":false,\"style\":{\"color\":\"#eee\"}},\"categoryAxes\":[{\"id\":\"CategoryAxis-1\",\"type\":\"category\",\"position\":\"bottom\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\"},\"labels\":{\"show\":true,\"truncate\":100},\"title\":{\"text\":\"agent.raw: Descending\"}}],\"valueAxes\":[{\"id\":\"ValueAxis-1\",\"name\":\"LeftAxis-1\",\"type\":\"value\",\"position\":\"left\",\"show\":true,\"style\":{},\"scale\":{\"type\":\"linear\",\"mode\":\"normal\"},\"labels\":{\"show\":true,\"rotate\":0,\"filter\":false,\"truncate\":100},\"title\":{\"text\":\"Count\"}}],\"seriesParams\":[{\"show\":\"true\",\"type\":\"area\",\"mode\":\"stacked\",\"data\":{\"label\":\"Count\",\"id\":\"1\"},\"drawLinesBetweenPoints\":true,\"showCircles\":true,\"interpolate\":\"linear\",\"valueAxis\":\"ValueAxis-1\"}],\"addTooltip\":true,\"addLegend\":true,\"legendPosition\":\"right\",\"times\":[],\"addTimeMarker\":false},\"aggs\":[{\"id\":\"1\",\"enabled\":true,\"type\":\"count\",\"schema\":\"metric\",\"params\":{}},{\"id\":\"2\",\"enabled\":true,\"type\":\"terms\",\"schema\":\"segment\",\"params\":{\"field\":\"agent.raw\",\"size\":5,\"order\":\"desc\",\"orderBy\":\"1\"}}]}", + "uiStateJSON": "{}", + "description": "", + "version": 1, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\"index\":\"f1e4c910-a2e6-11e7-bb30-233be9be6a15\",\"filter\":[],\"query\":{\"query\":\"\",\"language\":\"lucene\"}}" + } + }, + "_meta": { + "savedObjectVersion": 2 + } + } +] diff --git a/test/functional/apps/management/index.js b/test/functional/apps/management/index.js index df5de51c2357..1277378a3a78 100644 --- a/test/functional/apps/management/index.js +++ b/test/functional/apps/management/index.js @@ -23,6 +23,7 @@ export default function ({ getService, loadTestFile }) { loadTestFile(require.resolve('./_scripted_fields')); loadTestFile(require.resolve('./_index_pattern_filter')); loadTestFile(require.resolve('./_scripted_fields_filter')); + loadTestFile(require.resolve('./_import_objects')); }); } diff --git a/test/functional/fixtures/es_archiver/management/data.json.gz b/test/functional/fixtures/es_archiver/management/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..e7df69e5d114669992bf4c4b2eedcc92f8871246 GIT binary patch literal 1279 zcmVQOZ*Bn1TT5@-I1s+)R~SAnVk1s=yQohoiZ0Ma zn;yJapojy3BF7RplBkkYY}fFA?~syg$#&6fn`C$~tl{g^Jjvl`b2bOpfn}AWZ14deWg%eYI*jjS%nIf^ofzl33Pq7qTN0{D3QXW?)G&@EBcNVMs)A5J zIaoHX4CryjjIGICFh!Nejj6vllbE)WXE3!aA(mbOYK-!F7MJsLys=B^+q8I-m{w?kc@AR6n38dzjK<#y1WtVe z$t@5vC#tiCeFRY}Jn`?;|->)ENh#ig3DmAFWQJPkx{e>8L zKsvge2?CWVRbi-MT<2g6{idN!fT@6Xya!43aDdewnOI^gLn$hsswB7IlYWIbQX0*P zugvImbGP{p#;Z|#d6(QKzFrB>S`kM87u;yD#q)SSwzm- zTC1I$H#rD_jy6W3Xx?D!IE@Y%n?N_TuF_S~>)hY7Pd8rgdX1gn;u=qDElyjvPde1v zUT5q9#HaW_;PxZN_@0?KqeETc>CBtn4#2LO$Enlqn->4lUn+2yi;j{0Z`69w9mS8T pJ2&nrTFOk+Ww>=za5}P(ne{4nd$%3#5N%I-s&9g_+BrQm003D1g|7er literal 0 HcmV?d00001 diff --git a/test/functional/fixtures/es_archiver/management/mappings.json b/test/functional/fixtures/es_archiver/management/mappings.json new file mode 100644 index 000000000000..375a2fdafd60 --- /dev/null +++ b/test/functional/fixtures/es_archiver/management/mappings.json @@ -0,0 +1,246 @@ +{ + "type": "index", + "value": { + "index": ".kibana", + "settings": { + "index": { + "number_of_shards": "1", + "number_of_replicas": "1" + } + }, + "mappings": { + "doc": { + "properties": { + "type": { + "type": "keyword" + }, + "server": { + "dynamic": "strict", + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "url": { + "dynamic": "strict", + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 2048 + } + } + } + } + }, + "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" + } + } + }, + "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" + } + } + }, + "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" + } + } + }, + "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" + } + } + }, + "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" + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/test/functional/page_objects/common_page.js b/test/functional/page_objects/common_page.js index 7e093da521a8..3773a8856404 100644 --- a/test/functional/page_objects/common_page.js +++ b/test/functional/page_objects/common_page.js @@ -230,10 +230,14 @@ export function CommonPageProvider({ getService, getPageObjects }) { await remote.pressKeys('\uE007'); } - async clickCancelOnModal() { + // pass in true if your test will show multiple modals + // in succession + async clickCancelOnModal(overlayWillStay = false) { log.debug('Clicking modal cancel'); await testSubjects.click('confirmModalCancelButton'); - await this.ensureModalOverlayHidden(); + if (!overlayWillStay) { + await this.ensureModalOverlayHidden(); + } } async isConfirmModalOpen() { diff --git a/test/functional/page_objects/settings_page.js b/test/functional/page_objects/settings_page.js index 86622efc5afa..712c0a255960 100644 --- a/test/functional/page_objects/settings_page.js +++ b/test/functional/page_objects/settings_page.js @@ -27,6 +27,10 @@ export function SettingsPageProvider({ getService, getPageObjects }) { await this.clickLinkText('Advanced Settings'); } + async clickKibanaSavedObjects() { + await this.clickLinkText('Saved Objects'); + } + async clickKibanaIndices() { log.debug('clickKibanaIndices link'); await this.clickLinkText('Index Patterns'); @@ -493,6 +497,29 @@ export function SettingsPageProvider({ getService, getPageObjects }) { log.debug('set scripted field script = ' + script); await testSubjects.setValue('editorFieldScript', script); } + + async importFile(path) { + log.debug(`importFile(${path})`); + await remote.findById('testfile').type(path); + } + + async setImportIndexFieldOption(child) { + await remote.setFindTimeout(defaultFindTimeout) + .findByCssSelector(`select[data-test-subj="managementChangeIndexSelection"] > option:nth-child(${child})`) + .click(); + } + + async clickChangeIndexConfirmButton() { + await (await testSubjects.find('changeIndexConfirmButton')).click(); + } + + async clickVisualizationsTab() { + await (await testSubjects.find('objectsTab-visualizations')).click(); + } + + async getVisualizationRows() { + return await testSubjects.findAll(`objectsTableRow`); + } } return new SettingsPage();