From 9f37b1fe51db5b10dbe12a2c6de4ecb9f70b7871 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Tue, 4 Sep 2018 15:09:57 +0200 Subject: [PATCH] Add functional tests for visualize loader API (#22595) (#22649) * Initial visualize loader functional tests * Extend plugin test README * Add temporary tz work around * Switch to Australia/North timezone * Add filtering tests * Add all tests * Remove unneeded uiExports * Improve explanation * Add saved object test, add retry --- .../visualize/loader/visualize_loader.ts | 7 + test/functional/config.js | 2 + .../visualize_embedding/data.json.gz | Bin 0 -> 1987 bytes .../visualize_embedding/mappings.json | 246 ++++++++++++++++++ test/functional/services/index.js | 1 + test/functional/services/table.js | 39 +++ test/plugin_functional/README.md | 9 + test/plugin_functional/config.js | 1 + .../plugins/visualize_embedding/index.js | 39 +++ .../plugins/visualize_embedding/package.json | 4 + .../plugins/visualize_embedding/public/app.js | 95 +++++++ .../public/components/main.js | 140 ++++++++++ .../visualize_embedding/public/embedding.js | 187 +++++++++++++ .../embedding_visualizations/embed_by_id.js | 188 +++++++++++++ .../embedding_visualizations/index.js | 38 +++ 15 files changed, 996 insertions(+) create mode 100644 test/functional/fixtures/es_archiver/visualize_embedding/data.json.gz create mode 100644 test/functional/fixtures/es_archiver/visualize_embedding/mappings.json create mode 100644 test/functional/services/table.js create mode 100644 test/plugin_functional/plugins/visualize_embedding/index.js create mode 100644 test/plugin_functional/plugins/visualize_embedding/package.json create mode 100644 test/plugin_functional/plugins/visualize_embedding/public/app.js create mode 100644 test/plugin_functional/plugins/visualize_embedding/public/components/main.js create mode 100644 test/plugin_functional/plugins/visualize_embedding/public/embedding.js create mode 100644 test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js create mode 100644 test/plugin_functional/test_suites/embedding_visualizations/index.js diff --git a/src/ui/public/visualize/loader/visualize_loader.ts b/src/ui/public/visualize/loader/visualize_loader.ts index b7877444de154c..24aa615cbb3435 100644 --- a/src/ui/public/visualize/loader/visualize_loader.ts +++ b/src/ui/public/visualize/loader/visualize_loader.ts @@ -62,6 +62,11 @@ class VisualizeLoader { * In most of the cases you will need this method, since it allows you to specify * filters, handlers, queries, etc. on the savedObject before rendering. * + * We do not encourage you to use this method, since it will most likely be changed + * or removed in a future version of Kibana. Rather embed a visualization by its id + * via the {@link #embedVisualizationWithId} method. + * + * @deprecated You should rather embed by id, since this method will be removed in the future. * @param element The DOM element to render the visualization into. * You can alternatively pass a jQuery element instead. * @param savedObj The savedObject as it could be retrieved by the @@ -106,6 +111,8 @@ class VisualizeLoader { element.className = 'visualize'; element.setAttribute('data-test-subj', 'visualizationLoader'); container.appendChild(element); + // We need the container to have display: flex so visualization will render correctly + container.style.display = 'flex'; // If params specified cssClass, we will set this to the element. if (params.cssClass) { diff --git a/test/functional/config.js b/test/functional/config.js index 885770a0ab0c48..3b126e1a0549bf 100644 --- a/test/functional/config.js +++ b/test/functional/config.js @@ -53,6 +53,7 @@ import { ComboBoxProvider, EmbeddingProvider, RenderableProvider, + TableProvider, } from './services'; export default async function ({ readConfigFile }) { @@ -111,6 +112,7 @@ export default async function ({ readConfigFile }) { comboBox: ComboBoxProvider, embedding: EmbeddingProvider, renderable: RenderableProvider, + table: TableProvider, }, servers: commonConfig.get('servers'), diff --git a/test/functional/fixtures/es_archiver/visualize_embedding/data.json.gz b/test/functional/fixtures/es_archiver/visualize_embedding/data.json.gz new file mode 100644 index 0000000000000000000000000000000000000000..31001f860a951f941017ba2b4c83c2fdcba4eb34 GIT binary patch literal 1987 zcmV;!2R!&6iwFoKFO6FO17u-zVJ>QOZ*BnXT}f}-I269;S2)I;iX=@s&Bd5QG6e#3 z(4@trw>S`JiIF&=M3tnR4gT-*xX7lwC8^_(2lh}{kK5zhNq$A2c6zY)qE*ya+S5lpiSRO)UgiUJ+W+U<6ux(L;N^)&2P(0AA$ z4Er44V2=5)zcBT#1@h!Z#ZyB^JVe(a>Ii zv4ASxxl8qM7&8&sv&Qa)PgUuwO79-LG_H8gl!B+8pUnK*=Ct?@CaU?4^6GNi^Yco0 z-U@sK@W2fY>;0N3INi9=5U6F^!9c& zdRVw&c<0wwb1L)=Q_;}08^V`i)53v#1{eB*4a@CWwr)n0h{E?1aWW6}c;{Q;D!@`o%Zj^&SYoj=^at~ z?*rQZ5W)DHM&_XSieUT*#*bk92*&sGZs7a+B7*T(`g{cAM=<^p(}-aFK?LJRFn$E% zZlOdN z!wANYVEj$4Jt7!Cg7G65KZ5b!@cGBPZALJD1mj0A{wuDTg40a|<3}+57R2T|K}Rrt z1mj0A{$@v?2*%&wT`hv~&H53HA2=9)Ohkhyc|^*W2Fh4RNPyW^Y+u=FL4;&EN9FVA z{LV}>bbgn-9G!oKK_S_7?;Hj{$g{ZM|6=)devIEJ8F&N0|`Do*7orQh%v(@qD*rI|Bjv?;5v z=GkUYHIyntBD}o<)1--=^Cy#tI@3t?eb!W|ai|7%p}L}6ojnIvGm|>fd%>03mJ>&& z`?c1uZNI=JYV#88U``4X?NxT#PtQyXbxtYfryVuvEtU(y)y^?oewiT1qnhv-elngZ zk5$nVLcea+q`x1WjWSsP#jZ1zmP7r9MwsdW-xX00Dv1yfkQRn@=(B<6hOGSbdg9t+ zL$o>mbNT(knj~1LH6UlduQdVquG>%L!94G=OePM31p5JFL;>szdskeC~Y8~m0zI-;bUYB^|SehIvHnmQk!t)h9d24 z83g(f#DL8B_@3*IbB6@t&TW>n?o%Zy0k(y_M!{oJOY^_p9n zs9b@!71cWV4Ewth^jQ#TGS)6+`&2>qDPABX9JRph-4X@gq(;UNQpVV}voYkG1g5d`Lp!2LMs98lA#KWr>H&!^1-7YiBl71o zZq&C#<9ZX5IsbRmI6EXbLR{3yw)P2vE zzDtYTSiG#LR`$9@P1wataz|vbZ}<~l&S;8(o=__4SvEdu@x>A+mN>D*i6u@fabk(H z{Ss$o8j7{gZ`VHBxCVb6A08ggmqAbF`O3_+aabZ^NppGy~B-6tDoUZcHnbcH&2x)ez3`Ob(2jz2h VeQD_WvhbP0@qZ(ohEyYW0010o^EChf literal 0 HcmV?d00001 diff --git a/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json b/test/functional/fixtures/es_archiver/visualize_embedding/mappings.json new file mode 100644 index 00000000000000..375a2fdafd60b7 --- /dev/null +++ b/test/functional/fixtures/es_archiver/visualize_embedding/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/services/index.js b/test/functional/services/index.js index 2c3f5eb8502ca7..027fe3b67f855b 100644 --- a/test/functional/services/index.js +++ b/test/functional/services/index.js @@ -30,5 +30,6 @@ export { FlyoutProvider } from './flyout'; export { EmbeddingProvider } from './embedding'; export { ComboBoxProvider } from './combo_box'; export { RenderableProvider } from './renderable'; +export { TableProvider } from './table'; export * from './dashboard'; diff --git a/test/functional/services/table.js b/test/functional/services/table.js new file mode 100644 index 00000000000000..bbe6322ccb6450 --- /dev/null +++ b/test/functional/services/table.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function TableProvider({ getService }) { + const testSubjects = getService('testSubjects'); + // const retry = getService('retry'); + + class Table { + + async getDataFromTestSubj(testSubj) { + const table = await testSubjects.find(testSubj); + // Convert the data into a nested array format: + // [ [cell1_in_row1, cell2_in_row1], [cell1_in_row2, cell2_in_row2] ] + const rows = await table.findAllByTagName('tr'); + return await Promise.all(rows.map(async row => { + const cells = await row.findAllByTagName('td'); + return await Promise.all(cells.map(async cell => cell.getVisibleText())); + })); + } + } + + return new Table(); +} diff --git a/test/plugin_functional/README.md b/test/plugin_functional/README.md index 99026f22b14c25..23a4a8b9d3b907 100644 --- a/test/plugin_functional/README.md +++ b/test/plugin_functional/README.md @@ -21,3 +21,12 @@ node scripts/functional_tests_server.js --config test/plugin_functional/config.j # Start a test run node scripts/functional_test_runner.js --config test/plugin_functional/config.js ``` + +## Run Kibana with a test plugin + +In case you want to start Kibana with one of the test plugins (e.g. for developing the +test plugin), you can just run: + +``` +yarn start --plugin-path=test/plugin_functional/plugins/ +``` \ No newline at end of file diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index 2a6607d126e6f4..fd641c9ac0e618 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -30,6 +30,7 @@ export default async function ({ readConfigFile }) { return { testFiles: [ require.resolve('./test_suites/app_plugins'), + require.resolve('./test_suites/embedding_visualizations'), require.resolve('./test_suites/panel_actions'), ], services: functionalConfig.get('services'), diff --git a/test/plugin_functional/plugins/visualize_embedding/index.js b/test/plugin_functional/plugins/visualize_embedding/index.js new file mode 100644 index 00000000000000..9674260e919c0a --- /dev/null +++ b/test/plugin_functional/plugins/visualize_embedding/index.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default function (kibana) { + return new kibana.Plugin({ + uiExports: { + app: { + title: 'Embedding Vis', + description: 'This is a sample plugin to test embedding of visualizations', + main: 'plugins/visualize_embedding/app', + } + }, + + init(server) { + // The following lines copy over some configuration variables from Kibana + // to this plugin. This will be needed when embedding visualizations, so that e.g. + // region map is able to get its configuration. + server.injectUiAppVars('visualize_embedding', async () => { + return await server.getInjectedUiAppVars('kibana'); + }); + } + }); +} diff --git a/test/plugin_functional/plugins/visualize_embedding/package.json b/test/plugin_functional/plugins/visualize_embedding/package.json new file mode 100644 index 00000000000000..d20cf8720c2618 --- /dev/null +++ b/test/plugin_functional/plugins/visualize_embedding/package.json @@ -0,0 +1,4 @@ +{ + "name": "visualize_embedding", + "version": "kibana" +} diff --git a/test/plugin_functional/plugins/visualize_embedding/public/app.js b/test/plugin_functional/plugins/visualize_embedding/public/app.js new file mode 100644 index 00000000000000..3f622c946a1159 --- /dev/null +++ b/test/plugin_functional/plugins/visualize_embedding/public/app.js @@ -0,0 +1,95 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { uiModules } from 'ui/modules'; +import chrome from 'ui/chrome'; + +// This is required so some default styles and required scripts/Angular modules are loaded, +// or the timezone setting is correctly applied. +import 'ui/autoload/all'; + +// These are all the required uiExports you need to import in case you want to embed visualizations. +import 'uiExports/visTypes'; +import 'uiExports/visResponseHandlers'; +import 'uiExports/visRequestHandlers'; +import 'uiExports/visEditorTypes'; +import 'uiExports/visualize'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/fieldFormats'; +import 'uiExports/search'; + +// ----------- TODO Remove once https://github.com/elastic/kibana/pull/22623 is merged + +import moment from 'moment-timezone'; + +function setDefaultTimezone(tz) { + moment.tz.setDefault(tz); +} + +function setStartDayOfWeek(day) { + const dow = moment.weekdays().indexOf(day); + moment.updateLocale(moment.locale(), { week: { dow } }); +} + +const uiSettings = chrome.getUiSettingsClient(); + +setDefaultTimezone(uiSettings.get('dateFormat:tz')); +setStartDayOfWeek(uiSettings.get('dateFormat:dow')); + +uiSettings.subscribe(({ key, newValue }) => { + if (key === 'dateFormat:tz') { + setDefaultTimezone(newValue); + } else if (key === 'dateFormat:dow') { + setStartDayOfWeek(newValue); + } +}); + +// ----------------- END OF REMOVAL ---------- + +import { Main } from './components/main'; + +const app = uiModules.get('apps/firewallDemoPlugin', ['kibana']); + +app.config($locationProvider => { + $locationProvider.html5Mode({ + enabled: false, + requireBase: false, + rewriteLinks: false, + }); +}); +app.config(stateManagementConfigProvider => + stateManagementConfigProvider.disable() +); + +function RootController($scope, $element) { + const domNode = $element[0]; + + // render react to DOM + render(
, domNode); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + unmountComponentAtNode(domNode); + }); +} + +chrome.setRootController('firewallDemoPlugin', RootController); diff --git a/test/plugin_functional/plugins/visualize_embedding/public/components/main.js b/test/plugin_functional/plugins/visualize_embedding/public/components/main.js new file mode 100644 index 00000000000000..677708dfe6e978 --- /dev/null +++ b/test/plugin_functional/plugins/visualize_embedding/public/components/main.js @@ -0,0 +1,140 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiLoadingChart, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiSelect, +} from '@elastic/eui'; + +import { embeddingSamples } from '../embedding'; + +const VISUALIZATION_OPTIONS = [ + { value: '', text: '' }, + { value: 'timebased', text: 'Time based' }, + { value: 'timebased_with-filters', text: 'Time based (with filters)' }, + { value: 'timebased_no-datehistogram', text: 'Time based data without date histogram' } +]; + +class Main extends React.Component { + + chartDiv = React.createRef(); + state = { + loading: false, + selectedParams: null, + selectedVis: null, + }; + + embedVisualization = async () => { + if (this.handler) { + // Whenever a visualization is about to be removed from DOM that you embedded, + // you need to call `destroy` on the handler to make sure the visualization is + // teared down correctly. + this.handler.destroy(); + this.chartDiv.current.innerHTML = ''; + } + + const { selectedParams, selectedVis } = this.state; + if (selectedParams && selectedVis) { + this.setState({ loading: true }); + const sample = embeddingSamples.find(el => el.id === selectedParams); + this.handler = await sample.run(this.chartDiv.current, selectedVis); + // handler.whenFirstRenderComplete() will return a promise that resolves once the first + // rendering after embedding has finished. + await this.handler.whenFirstRenderComplete(); + this.setState({ loading: false }); + } + } + + onChangeVisualization = async (ev) => { + this.setState({ + selectedVis: ev.target.value, + }, this.embedVisualization); + }; + + onSelectSample = async (ev) => { + this.setState({ + selectedParams: ev.target.value, + }, this.embedVisualization); + }; + + render() { + const samples = [ + { value: '', text: '' }, + ...embeddingSamples.map(({ id, title }) => ({ + value: id, + text: title, + })) + ]; + + return ( + + + + + + + + + + + + + + + + { this.state.loading && + + + + } + + + + {/* + The element you want to render into should have its dimension set (via a fixed height, flexbox, absolute positioning, etc.), + since the visualization will render with exactly the size of that element, i.e. the container size determines the + visualization size. + */} +
+ + + + + ); + } +} + +export { Main }; diff --git a/test/plugin_functional/plugins/visualize_embedding/public/embedding.js b/test/plugin_functional/plugins/visualize_embedding/public/embedding.js new file mode 100644 index 00000000000000..190e6331837b97 --- /dev/null +++ b/test/plugin_functional/plugins/visualize_embedding/public/embedding.js @@ -0,0 +1,187 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * This files shows a couple of examples how to use the visualize loader API + * to embed visualizations. + */ + +import { getVisualizeLoader } from 'ui/visualize'; +import chrome from 'ui/chrome'; + +export const embeddingSamples = [ + + { + id: 'none', + title: 'No parameters', + async run(domNode, id) { + // You always need to retrieve the visualize loader for embedding visualizations. + const loader = await getVisualizeLoader(); + // Use the embedVisualizationWithId method to embed a visualization by its id. The id is the id of the + // saved object in the .kibana index (you can find the id via Management -> Saved Objects). + // + // Pass in a DOM node that you want to embed that visualization into. Note: the loader will + // use the size of that DOM node. + // + // The call will return a handler for the visualization with methods to interact with it. + // Check the components/main.js file to see how this handler is used. Most important: you need to call + // `destroy` on the handler once you are about to remove the visualization from the DOM. + // + // Note: If the visualization you want to embed contains date histograms with an auto interval, you need + // to specify the timeRange parameter (see below). + return loader.embedVisualizationWithId(domNode, id, {}); + } + }, { + id: 'timerange', + title: 'timeRange', + async run(domNode, id) { + const loader = await getVisualizeLoader(); + // If you want to filter down the data to a specific time range, you can specify a + // timeRange in the parameters to the embedding call. + // You can either use an absolute time range as seen below. You can also specify + // a datemath string, like "now-7d", "now-1w/w" for the from or to key. + // You can also directly assign a moment JS or regular JavaScript Date object. + return loader.embedVisualizationWithId(domNode, id, { + timeRange: { + from: '2015-09-20 20:00:00.000', + to: '2015-09-21 20:00:00.000', + } + }); + } + }, { + id: 'query', + title: 'query', + async run(domNode, id) { + const loader = await getVisualizeLoader(); + // You can specify a query that should filter down the data via the query parameter. + // It must have a language key which must be one of the supported query languages of Kibana, + // which are at the moment: 'lucene' or 'kquery'. + // The query key must then hold the actual query in the specified language for filtering. + return loader.embedVisualizationWithId(domNode, id, { + query: { + language: 'lucene', + query: 'extension.raw:jpg', + } + }); + } + }, { + id: 'filters', + title: 'filters', + async run(domNode, id) { + const loader = await getVisualizeLoader(); + // You can specify an array of filters that should apply to the query. + // The format of a filter must match the format the filter bar is using internally. + // This has a query key, which holds the query part of an Elasticsearch query + // and a meta key allowing to set some meta values, most important for this API + // the `negate` option to negate the filter. + return loader.embedVisualizationWithId(domNode, id, { + filters: [ + { + query: { + bool: { + should: [ + { match_phrase: { 'extension.raw': 'jpg' } }, + { match_phrase: { 'extension.raw': 'png' } }, + ] + } + }, + meta: { + negate: true + } + } + ] + }); + } + }, { + id: 'filters_query_timerange', + title: 'filters & query & timeRange', + async run(domNode, id) { + const loader = await getVisualizeLoader(); + // You an of course combine timeRange, query and filters options all together + // to filter the data in the embedded visualization. + return loader.embedVisualizationWithId(domNode, id, { + timeRange: { + from: '2015-09-20 20:00:00.000', + to: '2015-09-21 20:00:00.000', + }, + query: { + language: 'lucene', + query: 'bytes:>2000' + }, + filters: [ + { + query: { + bool: { + should: [ + { match_phrase: { 'extension.raw': 'jpg' } }, + { match_phrase: { 'extension.raw': 'png' } }, + ] + } + }, + meta: { + negate: true + } + } + ] + }); + } + }, { + id: 'savedobject_filter_query_timerange', + title: 'filters & query & time (use saved object)', + async run(domNode, id) { + const loader = await getVisualizeLoader(); + // Besides embedding via the id of the visualizataion, the API offers the possibility to + // embed via the saved visualization object. + // + // WE ADVISE YOU NOT TO USE THIS INSIDE ANY PLUGIN! + // + // Since the format of the saved visualization object will change in the future and because + // this still requires you to talk to old Angular code, we do not encourage you to use this + // way of embedding in any plugin. It's likely it will be removed or changed in a future version. + const $injector = await chrome.dangerouslyGetActiveInjector(); + const savedVisualizations = $injector.get('savedVisualizations'); + const savedVis = await savedVisualizations.get(id); + return loader.embedVisualizationWithSavedObject(domNode, savedVis, { + timeRange: { + from: '2015-09-20 20:00:00.000', + to: '2015-09-21 20:00:00.000', + }, + query: { + language: 'lucene', + query: 'bytes:>2000' + }, + filters: [ + { + query: { + bool: { + should: [ + { match_phrase: { 'extension.raw': 'jpg' } }, + { match_phrase: { 'extension.raw': 'png' } }, + ] + } + }, + meta: { + negate: true + } + } + ] + }); + } + } +]; diff --git a/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js b/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js new file mode 100644 index 00000000000000..e068627e5e80e4 --- /dev/null +++ b/test/plugin_functional/test_suites/embedding_visualizations/embed_by_id.js @@ -0,0 +1,188 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from 'expect.js'; + +export default function ({ getService }) { + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const table = getService('table'); + const retry = getService('retry'); + + async function selectVis(id) { + await testSubjects.click('visSelect'); + await find.clickByCssSelector(`option[value="${id}"]`); + } + + async function selectParams(id) { + await testSubjects.click('embeddingParamsSelect'); + await find.clickByCssSelector(`option[value="${id}"]`); + await retry.try(async () => { + await testSubjects.waitForDeleted('visLoadingIndicator'); + }); + } + + async function getTableData() { + const data = await table.getDataFromTestSubj('paginated-table-body'); + // Strip away empty rows (at the bottom) + return data.filter(row => !row.every(cell => !cell.trim())); + } + + describe('embed by id', function describeIndexTests() { + + describe('vis on timebased data without date histogram', () => { + before(async () => { + await selectVis('timebased_no-datehistogram'); + }); + + it('should correctly embed', async () => { + await selectParams('none'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['jpg', '9,109'], + ['css', '2,159'], + ['png', '1,373'], + ['gif', '918'], + ['php', '445'], + ]); + }); + + it('should correctly embed specifying a timeRange', async () => { + await selectParams('timerange'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['jpg', '3,005'], + ['css', '720'], + ['png', '455'], + ['gif', '300'], + ['php', '142'], + ]); + }); + + it('should correctly embed specifying a query', async () => { + await selectParams('query'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['jpg', '9,109'], + ]); + }); + + it('should correctly embed specifying filters', async () => { + await selectParams('filters'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['css', '2,159'], + ['gif', '918'], + ['php', '445'], + ]); + }); + + it('should correctly embed specifying filters and query and timeRange', async () => { + await selectParams('filters_query_timerange'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['css', '678'], + ['php', '110'], + ]); + }); + }); + + describe('vis on timebased data with date histogram with interval auto', () => { + before(async () => { + await selectVis('timebased'); + }); + + it('should correctly embed specifying a timeRange', async () => { + await selectParams('timerange'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['2015-09-20 20:00', '45.159KB', '5.65KB'], + ['2015-09-21 00:00', '42.428KB', '5.345KB'], + ['2015-09-21 04:00', '43.717KB', '5.35KB'], + ['2015-09-21 08:00', '43.228KB', '5.538KB'], + ['2015-09-21 12:00', '42.83KB', '5.669KB'], + ['2015-09-21 16:00', '44.908KB', '5.673KB'], + ]); + }); + + it('should correctly embed specifying filters and query and timeRange', async () => { + await selectParams('filters_query_timerange'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['2015-09-20 20:00', '45.391KB', '5.692KB'], + ['2015-09-21 00:00', '46.57KB', '5.953KB'], + ['2015-09-21 04:00', '47.339KB', '6.636KB'], + ['2015-09-21 08:00', '40.5KB', '6.133KB'], + ['2015-09-21 12:00', '41.31KB', '5.84KB'], + ['2015-09-21 16:00', '48.012KB', '6.003KB'], + ]); + }); + }); + + describe('vis on timebased data with date histogram with interval auto and saved filters', () => { + before(async () => { + await selectVis('timebased_with-filters'); + }); + + it('should correctly embed specifying a timeRange', async () => { + await selectParams('timerange'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['2015-09-20 20:00', '21.221KB', '2.66KB'], + ['2015-09-21 00:00', '22.054KB', '2.63KB'], + ['2015-09-21 04:00', '15.592KB', '2.547KB'], + ['2015-09-21 08:00', '4.656KB', '2.328KB'], + ['2015-09-21 12:00', '17.887KB', '2.694KB'], + ['2015-09-21 16:00', '20.533KB', '2.529KB'], + ]); + }); + + it('should correctly embed specifying filters and query and timeRange', async () => { + await selectParams('filters_query_timerange'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['2015-09-20 20:00', '24.567KB', '3.498KB'], + ['2015-09-21 00:00', '25.984KB', '3.589KB'], + ['2015-09-21 04:00', '2.543KB', '2.543KB'], + ['2015-09-21 12:00', '5.783KB', '2.927KB'], + ['2015-09-21 16:00', '21.107KB', '3.44KB'], + ]); + }); + }); + + describe('vis visa saved object on timebased data with date histogram with interval auto and saved filters', () => { + before(async () => { + await selectVis('timebased_with-filters'); + }); + + it('should correctly embed specifying filters and query and timeRange', async () => { + await selectParams('savedobject_filter_query_timerange'); + const data = await getTableData(); + expect(data).to.be.eql([ + ['2015-09-20 20:00', '24.567KB', '3.498KB'], + ['2015-09-21 00:00', '25.984KB', '3.589KB'], + ['2015-09-21 04:00', '2.543KB', '2.543KB'], + ['2015-09-21 12:00', '5.783KB', '2.927KB'], + ['2015-09-21 16:00', '21.107KB', '3.44KB'], + ]); + }); + }); + }); + +} diff --git a/test/plugin_functional/test_suites/embedding_visualizations/index.js b/test/plugin_functional/test_suites/embedding_visualizations/index.js new file mode 100644 index 00000000000000..6aa6a03b6984d3 --- /dev/null +++ b/test/plugin_functional/test_suites/embedding_visualizations/index.js @@ -0,0 +1,38 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default function ({ getService, getPageObjects, loadTestFile }) { + const remote = getService('remote'); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['common', 'header']); + + describe('embedding visualizations', function () { + before(async () => { + await esArchiver.loadIfNeeded('../functional/fixtures/es_archiver/logstash_functional'); + await esArchiver.load('../functional/fixtures/es_archiver/visualize_embedding'); + await kibanaServer.uiSettings.replace({ 'dateFormat:tz': 'Australia/North', 'defaultIndex': 'logstash-*' }); + await remote.setWindowSize(1300, 900); + await PageObjects.common.navigateToApp('settings'); + await PageObjects.header.clickGlobalNavigationLink('Embedding Vis'); + }); + + loadTestFile(require.resolve('./embed_by_id')); + }); +}