[Maps] Map embeddable (#31473)

* [Maps] embeddable

* fix embeddable registration

* get embeddable to render

* render map embeddable

* support filter pills on dashboard

* fix layerName error

* update getBoundsForFilters to not record bounds request in inspector

* set isReadOnly

* fix bug where different states are sharing same inspectorAdapters instance

* populate indexPatterns so container has index patterns for type ahead and filter bar

* pass refreshConfig from container to map store

* add functional test verifying index patterns passed to container

* remove data.json files commited by mistake

* add functional test ensuring inspector adaptors are not shared between embeddables

* add functional tests for filters getting passed to embeddable

* remove embeddable dashboard from web logs sample data saved objects

* add slash to edit path

* fix typo

* pass previous query state on reload, add functional tests to verify reload and refresh timer

* remove unnecessary check in getFilters

* review feedback from thomasneirynck

* fix resize bug

* remove animationFrame from resize callback
This commit is contained in:
Nathan Reese 2019-03-01 12:06:46 -07:00 committed by GitHub
parent d89a97fb14
commit 54bbdbaedb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 447 additions and 96 deletions

View file

@ -116,6 +116,7 @@ class RequestSelector extends Component {
>
<EuiContextMenuPanel
items={this.props.requests.map(this.renderRequestDropdownItem)}
data-test-subj="inspectorRequestChooserMenuPanel"
/>
</EuiPopover>
);
@ -140,7 +141,7 @@ class RequestSelector extends Component {
</EuiFlexItem>
<EuiFlexItem grow={true}>
{requests.length <= 1 &&
<div className="insRequestSelector__singleRequest">
<div className="insRequestSelector__singleRequest" data-test-subj="inspectorRequestName">
{selectedRequest.name}
</div>
}

View file

@ -213,6 +213,7 @@ class FilterEditorUI extends Component<Props, State> {
onChange={this.onIndexPatternChange}
singleSelection={{ asPlainText: true }}
isClearable={false}
data-test-subj="filterIndexPatternsSelect"
/>
</EuiFormRow>
</EuiFlexItem>

View file

@ -88,6 +88,11 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) {
await testSubjects.click(CUSTOMIZE_PANEL_DATA_TEST_SUBJ);
}
async openInspectorByTitle(title) {
const header = await this.getPanelHeading(title);
await this.openInspector(header);
}
async openInspector(parent) {
await this.openContextMenu(parent);
await testSubjects.click(OPEN_INSPECTOR_TEST_SUBJ);

View file

@ -127,6 +127,18 @@ export function FilterBarProvider({ getService, getPageObjects }) {
await testSubjects.click('cancelSaveFilter');
}
}
async getIndexPatterns() {
await testSubjects.click('addFilter');
const indexPatterns = await comboBox.getOptionsList('filterIndexPatternsSelect');
await this.ensureFieldEditorModalIsClosed();
return indexPatterns.trim().split('\n').join(',');
}
async selectIndexPattern(indexPatternTitle) {
await testSubjects.click('addFilter');
await comboBox.set('filterIndexPatternsSelect', indexPatternTitle);
}
}
return new FilterBar();

View file

@ -140,5 +140,29 @@ export function InspectorProvider({ getService }) {
});
await renderable.waitForRender();
}
async openInspectorView(viewId) {
log.debug(`Open Inspector view ${viewId}`);
await testSubjects.click('inspectorViewChooser');
await testSubjects.click(viewId);
}
async openInspectorRequestsView() {
await this.openInspectorView('inspectorViewChooserRequests');
}
async getRequestNames() {
await this.openInspectorRequestsView();
const requestChooserExists = await testSubjects.exists('inspectorRequestChooser');
if (requestChooserExists) {
await testSubjects.click('inspectorRequestChooser');
const menu = await testSubjects.find('inspectorRequestChooserMenuPanel');
const requestNames = await menu.getVisibleText();
return requestNames.trim().split('\n').join(',');
}
const singleRequest = await testSubjects.find('inspectorRequestName');
return await singleRequest.getVisibleText();
}
};
}

View file

@ -39,6 +39,9 @@ export function maps(kibana) {
isEmsEnabled: mapConfig.includeElasticMapsService,
};
},
embeddableFactories: [
'plugins/maps/embeddable/map_embeddable_factory_provider'
],
inspectorViews: [
'plugins/maps/inspector/views/register_views',
],

View file

@ -479,7 +479,7 @@ export function removeLayer(id) {
};
}
export function setQuery({ query, timeFilters }) {
export function setQuery({ query, timeFilters, filters = [] }) {
return async (dispatch, getState) => {
dispatch({
type: SET_QUERY,
@ -489,6 +489,7 @@ export function setQuery({ query, timeFilters }) {
// ensure query changes to trigger re-fetch even when query is the same because "Refresh" clicked
queryLastTriggeredAt: (new Date()).toISOString(),
},
filters,
});
const dataFilters = getDataFilters(getState());

View file

@ -7,17 +7,9 @@
import './saved_gis_map';
import { uiModules } from 'ui/modules';
import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects';
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';
const module = uiModules.get('app/maps');
// Register this service with the saved object registry so it can be
// edited by the object editor.
SavedObjectRegistryProvider.register({
service: 'gisMapSavedObjectLoader',
title: 'gisMaps'
});
// This is the only thing that gets injected into controllers
module.service('gisMapSavedObjectLoader', function (Private, SavedGisMap, kbnIndex, kbnUrl, $http, chrome) {
const savedObjectClient = Private(SavedObjectsClientProvider);

View file

@ -39,6 +39,15 @@ module.factory('SavedGisMap', function (Private) {
});
savedObject.layerListJSON = attributes.layerListJSON;
const indexPatternIds = references
.filter(reference => {
return reference.type === 'index-pattern';
})
.map(reference => {
return reference.id;
});
savedObject.indexPatternIds = _.uniq(indexPatternIds);
},
// if this is null/undefined then the SavedObject will be assigned the defaults

View file

@ -93,8 +93,7 @@ export class MBMapContainer extends React.Component {
originalMbBoxRemoveLayerFunc.apply(this._mbMap, [id]);
};
this.assignSizeWatch();
this._initResizerChecker();
// moveend callback is debounced to avoid updating map extent state while map extent is still changing
// moveend is fired while the map extent is still changing in the following scenarios
@ -149,20 +148,11 @@ export class MBMapContainer extends React.Component {
}
}
assignSizeWatch() {
_initResizerChecker() {
this._checker = new ResizeChecker(this.refs.mapContainer);
this._checker.on('resize', (() => {
let lastWidth = window.innerWidth;
let lastHeight = window.innerHeight;
return () => {
if (lastWidth === window.innerWidth
&& lastHeight === window.innerHeight && this._mbMap) {
this._mbMap.resize();
}
lastWidth = window.innerWidth;
lastHeight = window.innerHeight;
};
})());
this._checker.on('resize', () => {
this._mbMap.resize();
});
}
_syncMbMapWithMapState = () => {

View file

@ -0,0 +1,114 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
import React from 'react';
import { Provider } from 'react-redux';
import { render, unmountComponentAtNode } from 'react-dom';
import 'mapbox-gl/dist/mapbox-gl.css';
import { Embeddable } from 'ui/embeddable';
import { I18nContext } from 'ui/i18n';
import { GisMap } from '../components/gis_map';
import { createMapStore } from '../store/store';
import { getInitialLayers } from '../angular/get_initial_layers';
import {
setGotoWithCenter,
replaceLayerList,
setQuery,
setRefreshConfig,
} from '../actions/store_actions';
import { setReadOnly } from '../store/ui';
import { getInspectorAdapters } from '../store/non_serializable_instances';
export class MapEmbeddable extends Embeddable {
constructor({ savedMap, editUrl, indexPatterns = [] }) {
super({ title: savedMap.title, editUrl, indexPatterns });
this._savedMap = savedMap;
this._store = createMapStore();
}
getInspectorAdapters() {
return getInspectorAdapters(this._store.getState());
}
onContainerStateChanged(containerState) {
if (!_.isEqual(containerState.timeRange, this._prevTimeRange) ||
!_.isEqual(containerState.query, this._prevQuery) ||
!_.isEqual(containerState.filters, this._prevFilters)) {
this._dispatchSetQuery(containerState);
}
if (!_.isEqual(containerState.refreshConfig, this._prevRefreshConfig)) {
this._dispatchSetRefreshConfig(containerState);
}
}
_dispatchSetQuery({ query, timeRange, filters }) {
this._prevTimeRange = timeRange;
this._prevQuery = query;
this._prevFilters = filters;
this._store.dispatch(setQuery({
filters: filters.filter(filter => !filter.meta.disabled),
query,
timeFilters: timeRange,
}));
}
_dispatchSetRefreshConfig({ refreshConfig }) {
this._prevRefreshConfig = refreshConfig;
this._store.dispatch(setRefreshConfig(refreshConfig));
}
/**
*
* @param {HTMLElement} domNode
* @param {ContainerState} containerState
*/
render(domNode, containerState) {
this._store.dispatch(setReadOnly(true));
// todo get center and zoom from embeddable UI state
if (this._savedMap.mapStateJSON) {
const mapState = JSON.parse(this._savedMap.mapStateJSON);
this._store.dispatch(setGotoWithCenter({
lat: mapState.center.lat,
lon: mapState.center.lon,
zoom: mapState.zoom,
}));
}
const layerList = getInitialLayers(this._savedMap.layerListJSON);
this._store.dispatch(replaceLayerList(layerList));
this._dispatchSetQuery(containerState);
this._dispatchSetRefreshConfig(containerState);
render(
<Provider store={this._store}>
<I18nContext>
<GisMap/>
</I18nContext>
</Provider>,
domNode
);
}
destroy() {
this._savedMap.destroy();
if (this._domNode) {
unmountComponentAtNode(this._domNode);
}
}
reload() {
this._dispatchSetQuery({
query: this._prevQuery,
timeRange: this._prevTimeRange,
filters: this._prevFilters
});
}
}

View file

@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import _ from 'lodash';
import chrome from 'ui/chrome';
import { EmbeddableFactory } from 'ui/embeddable';
import { MapEmbeddable } from './map_embeddable';
import { indexPatternService } from '../kibana_services';
export class MapEmbeddableFactory extends EmbeddableFactory {
constructor(gisMapSavedObjectLoader) {
super({ name: 'map' });
this._savedObjectLoader = gisMapSavedObjectLoader;
}
async _getIndexPatterns(indexPatternIds = []) {
const promises = indexPatternIds.map(async (indexPatternId) => {
try {
return await indexPatternService.get(indexPatternId);
} catch (error) {
// Unable to load index pattern, better to not throw error so map embeddable can render
// Error will be surfaced by map embeddable since it too will be unable to locate the index pattern
return null;
}
});
const indexPatterns = await Promise.all(promises);
return _.compact(indexPatterns);
}
async create(panelMetadata, onEmbeddableStateChanged) {
const savedMap = await this._savedObjectLoader.get(panelMetadata.id);
const indexPatterns = await this._getIndexPatterns(savedMap.indexPatternIds);
return new MapEmbeddable({
onEmbeddableStateChanged,
savedMap,
editUrl: chrome.addBasePath(`/app/maps#/map/${panelMetadata.id}`),
indexPatterns,
});
}
}

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { EmbeddableFactoriesRegistryProvider } from 'ui/embeddable/embeddable_factories_registry';
import { MapEmbeddableFactory } from './map_embeddable_factory';
import '../angular/services/gis_map_saved_object_loader';
function mapEmbeddableFactoryProvider(gisMapSavedObjectLoader) {
return new MapEmbeddableFactory(gisMapSavedObjectLoader);
}
EmbeddableFactoriesRegistryProvider.register(mapEmbeddableFactoryProvider);

View file

@ -14,6 +14,7 @@ import 'uiExports/autocompleteProviders';
import 'uiExports/fieldFormats';
import 'uiExports/inspectorViews';
import 'uiExports/search';
import 'uiExports/embeddableFactories';
import 'ui/agg_types';
import chrome from 'ui/chrome';

View file

@ -105,7 +105,19 @@ export const getTimeFilters = ({ map }) => map.mapState.timeFilters ?
export const getQuery = ({ map }) => map.mapState.query;
export const getRefreshConfig = ({ map }) => map.mapState.refreshConfig;
export const getFilters = ({ map }) => map.mapState.filters;
export const getRefreshConfig = ({ map }) => {
if (map.mapState.refreshConfig) {
return map.mapState.refreshConfig;
}
const refreshInterval = timefilter.getRefreshInterval();
return {
isPaused: refreshInterval.pause,
interval: refreshInterval.value,
};
};
export const getRefreshTimerLastTriggeredAt = ({ map }) => map.mapState.refreshTimerLastTriggeredAt;
@ -116,7 +128,8 @@ export const getDataFilters = createSelector(
getTimeFilters,
getRefreshTimerLastTriggeredAt,
getQuery,
(mapExtent, mapBuffer, mapZoom, timeFilters, refreshTimerLastTriggeredAt, query) => {
getFilters,
(mapExtent, mapBuffer, mapZoom, timeFilters, refreshTimerLastTriggeredAt, query, filters) => {
return {
extent: mapExtent,
buffer: mapBuffer,
@ -124,6 +137,7 @@ export const getDataFilters = createSelector(
timeFilters,
refreshTimerLastTriggeredAt,
query,
filters,
};
}
);

View file

@ -112,54 +112,52 @@ export class HeatmapLayer extends AbstractLayer {
}
const sourceDataRequest = this.getSourceDataRequest();
const dataMeta = sourceDataRequest ? sourceDataRequest.getMeta() : {};
const meta = sourceDataRequest ? sourceDataRequest.getMeta() : {};
const geogridPrecision = this._source.getGeoGridPrecision(dataFilters.zoom);
const isSamePrecision = dataMeta.geogridPrecision === geogridPrecision;
const isSamePrecision = meta.geogridPrecision === geogridPrecision;
const isSameTime = _.isEqual(dataMeta.timeFilters, dataFilters.timeFilters);
const isSameTime = _.isEqual(meta.timeFilters, dataFilters.timeFilters);
const updateDueToRefreshTimer = dataFilters.refreshTimerLastTriggeredAt
&& !_.isEqual(dataMeta.refreshTimerLastTriggeredAt, dataFilters.refreshTimerLastTriggeredAt);
&& !_.isEqual(meta.refreshTimerLastTriggeredAt, dataFilters.refreshTimerLastTriggeredAt);
const updateDueToExtent = this.updateDueToExtent(this._source, dataMeta, dataFilters);
const updateDueToExtent = this.updateDueToExtent(this._source, meta, dataFilters);
const updateDueToQuery = dataFilters.query
&& !_.isEqual(dataMeta.query, dataFilters.query);
&& !_.isEqual(meta.query, dataFilters.query);
const updateDueToFilters = dataFilters.filters
&& !_.isEqual(meta.filters, dataFilters.filters);
const metricPropertyKey = this._getPropKeyOfSelectedMetric();
const updateDueToMetricChange = !_.isEqual(dataMeta.metric, metricPropertyKey);
const updateDueToMetricChange = !_.isEqual(meta.metric, metricPropertyKey);
if (isSamePrecision
&& isSameTime
&& !updateDueToExtent
&& !updateDueToRefreshTimer
&& !updateDueToQuery
&& !updateDueToFilters
&& !updateDueToMetricChange
) {
return;
}
const newDataMeta = {
const searchFilters = {
...dataFilters,
geogridPrecision,
metric: metricPropertyKey
};
await this._fetchNewData({ startLoading, stopLoading, onLoadError, dataMeta: newDataMeta });
await this._fetchNewData({ startLoading, stopLoading, onLoadError, searchFilters });
}
async _fetchNewData({ startLoading, stopLoading, onLoadError, dataMeta }) {
const { geogridPrecision, timeFilters, buffer, query } = dataMeta;
async _fetchNewData({ startLoading, stopLoading, onLoadError, searchFilters }) {
const requestToken = Symbol(`layer-source-refresh: this.getId()`);
startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, dataMeta);
startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, searchFilters);
try {
const layerName = await this.getDisplayName();
const data = await this._source.getGeoJsonPoints({ layerName }, {
geogridPrecision,
buffer,
timeFilters,
query,
});
const data = await this._source.getGeoJsonPoints(layerName, searchFilters);
stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, data);
} catch (error) {
onLoadError(SOURCE_DATA_ID_ORIGIN, requestToken, error.message);

View file

@ -148,14 +148,9 @@ export class ESGeoGridSource extends AbstractESSource {
throw new Error(`Grid resolution param not recognized: ${this._descriptor.resolution}`);
}
async getGeoJsonWithMeta({ layerName }, searchFilters) {
async getGeoJsonWithMeta(layerName, searchFilters) {
const featureCollection = await this.getGeoJsonPoints({ layerName }, {
geogridPrecision: searchFilters.geogridPrecision,
buffer: searchFilters.buffer,
timeFilters: searchFilters.timeFilters,
query: searchFilters.query,
});
const featureCollection = await this.getGeoJsonPoints(layerName, searchFilters);
return {
data: featureCollection,
@ -171,11 +166,11 @@ export class ESGeoGridSource extends AbstractESSource {
});
}
async getGeoJsonPoints({ layerName }, { geogridPrecision, buffer, timeFilters, query }) {
async getGeoJsonPoints(layerName, searchFilters) {
const indexPattern = await this._getIndexPattern();
const searchSource = await this._makeSearchSource({ buffer, timeFilters, query }, 0);
const aggConfigs = new AggConfigs(indexPattern, this._makeAggConfigs(geogridPrecision), aggSchemas.all);
const searchSource = await this._makeSearchSource(searchFilters, 0);
const aggConfigs = new AggConfigs(indexPattern, this._makeAggConfigs(searchFilters.geogridPrecision), aggSchemas.all);
searchSource.setField('aggs', aggConfigs.toDsl());
const esResponse = await this._runEsQuery(layerName, searchSource, 'Elasticsearch geohash_grid aggregation request');

View file

@ -103,7 +103,7 @@ export class ESSearchSource extends AbstractESSource {
];
}
async getGeoJsonWithMeta({ layerName }, searchFilters) {
async getGeoJsonWithMeta(layerName, searchFilters) {
const searchSource = await this._makeSearchSource(searchFilters, this._descriptor.limit);
// Setting "fields" instead of "source: { includes: []}"
// because SearchSource automatically adds the following by default

View file

@ -91,7 +91,7 @@ export class AbstractESSource extends AbstractVectorSource {
}
}
async _makeSearchSource({ buffer, query, timeFilters }, limit) {
async _makeSearchSource({ buffer, query, timeFilters, filters }, limit) {
const indexPattern = await this._getIndexPattern();
const geoField = await this._getGeoField();
const isTimeAware = await this.isTimeAware();
@ -99,22 +99,22 @@ export class AbstractESSource extends AbstractVectorSource {
searchSource.setField('index', indexPattern);
searchSource.setField('size', limit);
searchSource.setField('filter', () => {
const filters = [];
const allFilters = [...filters];
if (this.isFilterByMapBounds() && buffer) {//buffer can be empty
filters.push(createExtentFilter(buffer, geoField.name, geoField.type));
allFilters.push(createExtentFilter(buffer, geoField.name, geoField.type));
}
if (isTimeAware) {
filters.push(timefilter.createFilter(indexPattern, timeFilters));
allFilters.push(timefilter.createFilter(indexPattern, timeFilters));
}
return filters;
return allFilters;
});
searchSource.setField('query', query);
return searchSource;
}
async getBoundsForFilters({ query, timeFilters }) {
async getBoundsForFilters({ query, timeFilters, filters }) {
const searchSource = await this._makeSearchSource({ query, timeFilters }, 0);
const searchSource = await this._makeSearchSource({ query, timeFilters, filters }, 0);
const geoField = await this._getGeoField();
const indexPattern = await this._getIndexPattern();

View file

@ -202,8 +202,10 @@ export class VectorLayer extends AbstractLayer {
}
let updateDueToQuery = false;
let updateDueToFilters = false;
if (isQueryAware) {
updateDueToQuery = !_.isEqual(meta.query, searchFilters.query);
updateDueToFilters = !_.isEqual(meta.filters, searchFilters.filters);
}
let updateDueToPrecisionChange = false;
@ -218,6 +220,7 @@ export class VectorLayer extends AbstractLayer {
&& !updateDueToExtentChange
&& !updateDueToFields
&& !updateDueToQuery
&& !updateDueToFilters
&& !updateDueToPrecisionChange;
}
@ -297,9 +300,7 @@ export class VectorLayer extends AbstractLayer {
try {
startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, searchFilters);
const layerName = await this.getDisplayName();
const { data, meta } = await this._source.getGeoJsonWithMeta({
layerName,
}, searchFilters);
const { data, meta } = await this._source.getGeoJsonWithMeta(layerName, searchFilters);
stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, data, meta);
return {
refreshed: true,

View file

@ -94,6 +94,7 @@ const INITIAL_STATE = {
mouseCoordinates: null,
timeFilters: null,
query: null,
filters: [],
refreshConfig: null,
refreshTimerLastTriggeredAt: null,
},
@ -196,13 +197,14 @@ export function map(state = INITIAL_STATE, action) {
};
return { ...state, mapState: { ...state.mapState, ...newMapState } };
case SET_QUERY:
const { query, timeFilters } = action;
const { query, timeFilters, filters } = action;
return {
...state,
mapState: {
...state.mapState,
query,
timeFilters,
filters,
}
};
case SET_REFRESH_CONFIG:

View file

@ -19,12 +19,14 @@ function createInspectorAdapters() {
return inspectorAdapters;
}
const INITIAL_STATE = {
inspectorAdapters: createInspectorAdapters(),
};
// Reducer
export function nonSerializableInstances(state = INITIAL_STATE) {
export function nonSerializableInstances(state) {
if (!state) {
return {
inspectorAdapters: createInspectorAdapters(),
};
}
// state is read only and provides access to non-serializeable object instances
return state;
}

View file

@ -69,6 +69,12 @@ export function enableFullScreen() {
isFullScreen: true
};
}
export function setReadOnly(isReadOnly) {
return {
type: SET_READ_ONLY,
isReadOnly
};
}
// Selectors
export const getFlyoutDisplay = ({ ui }) => ui && ui.flyoutDisplay

View file

@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import expect from 'expect.js';
export default function ({ getPageObjects, getService }) {
const PageObjects = getPageObjects(['common', 'dashboard', 'maps']);
const kibanaServer = getService('kibanaServer');
const filterBar = getService('filterBar');
const dashboardPanelActions = getService('dashboardPanelActions');
const inspector = getService('inspector');
describe('embed in dashboard', () => {
before(async () => {
await kibanaServer.uiSettings.replace({
'defaultIndex': 'c698b940-e149-11e8-a35a-370a8516603a'
});
await PageObjects.common.navigateToApp('dashboard');
await PageObjects.dashboard.loadSavedDashboard('map embeddable example');
});
async function getRequestTimestamp() {
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const requestTimestamp = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Request timestamp');
await inspector.close();
return requestTimestamp;
}
it('should pass index patterns to container', async () => {
const indexPatterns = await filterBar.getIndexPatterns();
expect(indexPatterns).to.equal('geo_shapes*,meta_for_geo_shapes*,logstash-*');
});
it('should populate inspector with requests for map embeddable', async () => {
await dashboardPanelActions.openInspectorByTitle('join example');
const joinExampleRequestNames = await inspector.getRequestNames();
await inspector.close();
expect(joinExampleRequestNames).to.equal('geo_shapes*,meta_for_geo_shapes*.shape_name');
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
const gridExampleRequestNames = await inspector.getRequestNames();
await inspector.close();
expect(gridExampleRequestNames).to.equal('logstash-*');
});
it('should apply container state (time, query, filters) to embeddable when loaded', async () => {
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
const requestStats = await inspector.getTableData();
const totalHits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)');
await inspector.close();
expect(totalHits).to.equal('6');
});
it('should apply new container state (time, query, filters) to embeddable', async () => {
await filterBar.selectIndexPattern('logstash-*');
await filterBar.addFilter('machine.os', 'is', 'win 8');
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
const requestStats = await inspector.getTableData();
const totalHits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)');
await inspector.close();
expect(totalHits).to.equal('1');
});
it('should re-fetch query when "refresh" is clicked', async () => {
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
const beforeQueryRefreshTimestamp = await getRequestTimestamp();
await PageObjects.maps.refreshQuery();
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
const afterQueryRefreshTimestamp = await getRequestTimestamp();
expect(beforeQueryRefreshTimestamp).not.to.equal(afterQueryRefreshTimestamp);
});
it('should re-fetch documents with refresh timer', async () => {
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
const beforeRefreshTimerTimestamp = await getRequestTimestamp();
expect(beforeRefreshTimerTimestamp.length).to.be(24);
await PageObjects.maps.triggerSingleRefresh(1000);
await dashboardPanelActions.openInspectorByTitle('geo grid vector grid example');
const afterRefreshTimerTimestamp = await getRequestTimestamp();
expect(beforeRefreshTimerTimestamp).not.to.equal(afterRefreshTimerTimestamp);
});
});
}

View file

@ -20,7 +20,8 @@ export default function ({ getPageObjects, getService }) {
const DATA_CENTER_LAT = 38;
async function getRequestTimestamp() {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const requestTimestamp = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Request timestamp');
await inspector.close();
@ -122,7 +123,8 @@ export default function ({ getPageObjects, getService }) {
});
it('should apply query to geotile_grid aggregation request', async () => {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)');
await inspector.close();
@ -136,7 +138,8 @@ export default function ({ getPageObjects, getService }) {
});
it('should contain geotile_grid aggregation elasticsearch request', async () => {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const totalHits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)');
expect(totalHits).to.equal('6');
@ -195,7 +198,8 @@ export default function ({ getPageObjects, getService }) {
});
it('should apply query to geotile_grid aggregation request', async () => {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)');
await inspector.close();
@ -209,7 +213,8 @@ export default function ({ getPageObjects, getService }) {
});
it('should contain geotile_grid aggregation elasticsearch request', async () => {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const totalHits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits (total)');
expect(totalHits).to.equal('6');

View file

@ -16,7 +16,8 @@ export default function ({ getPageObjects, getService }) {
});
async function getRequestTimestamp() {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const requestTimestamp = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Request timestamp');
await inspector.close();
@ -24,7 +25,8 @@ export default function ({ getPageObjects, getService }) {
}
async function getHits() {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits');
await inspector.close();
@ -56,7 +58,8 @@ export default function ({ getPageObjects, getService }) {
});
it('should apply query to search request', async () => {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits');
await inspector.close();

View file

@ -40,6 +40,7 @@ export default function ({ loadTestFile, getService }) {
loadTestFile(require.resolve('./joins'));
loadTestFile(require.resolve('./add_layer_panel'));
loadTestFile(require.resolve('./layer_errors'));
loadTestFile(require.resolve('./embeddable/dashboard'));
});
});
}

View file

@ -66,7 +66,8 @@ export default function ({ getPageObjects, getService }) {
});
it('should apply query stored with map', async () => {
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits');
await inspector.close();
@ -85,7 +86,8 @@ export default function ({ getPageObjects, getService }) {
const query = await queryBar.getQueryString();
expect(query).to.equal('machine.os.raw : "win 8"');
await PageObjects.maps.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
const requestStats = await inspector.getTableData();
await inspector.close();
const hits = PageObjects.maps.getInspectorStatRowHit(requestStats, 'Hits');

View file

@ -392,3 +392,33 @@
}
}
}
{
"type": "doc",
"value": {
"id": "dashboard:19906970-2e40-11e9-85cb-6965aae20f13",
"index": ".kibana",
"source": {
"dashboard": {
"description": "",
"hits": 0,
"kibanaSavedObjectMeta": {
"searchSourceJSON": "{\"query\":{\"query\":\"\",\"language\":\"lucene\"}}"
},
"optionsJSON": "{\"darkTheme\":false,\"useMargins\":true,\"hidePanelTitles\":false}",
"panelsJSON": "[{\"embeddableConfig\":{},\"gridData\":{\"x\":0,\"y\":0,\"w\":24,\"h\":20,\"i\":\"1\"},\"id\":\"1649cc70-f736-11e8-8ce0-9723965e01e3\",\"panelIndex\":\"1\",\"type\":\"map\",\"version\":\"7.0.0-alpha1\"},{\"embeddableConfig\":{},\"gridData\":{\"x\":24,\"y\":0,\"w\":24,\"h\":20,\"i\":\"2\"},\"id\":\"88845ec0-03ae-11e9-83d4-5b39b6575325\",\"panelIndex\":\"2\",\"type\":\"map\",\"version\":\"7.0.0-alpha1\"}]",
"refreshInterval": {
"pause": true,
"value": 1000
},
"timeFrom": "2015-09-20T00:00:00.000Z",
"timeRestore": true,
"timeTo": "2015-09-20T01:00:00.000Z",
"title": "map embeddable example",
"version": 1
},
"type": "dashboard",
"updated_at": "2018-04-11T21:57:52.253Z"
}
}
}

View file

@ -258,37 +258,29 @@ export function GisPageProvider({ getService, getPageObjects }) {
return await testSubjects.getVisibleText(`layerErrorMessage`);
}
async openInspectorView(viewId) {
await inspector.open();
log.debug(`Open Inspector view ${viewId}`);
await testSubjects.click('inspectorViewChooser');
await testSubjects.click(viewId);
}
async openInspectorMapView() {
await this.openInspectorView('inspectorViewChooserMap');
}
async openInspectorRequestsView() {
await this.openInspectorView('inspectorViewChooserRequests');
await inspector.openInspectorView('inspectorViewChooserMap');
}
// Method should only be used when multiple requests are expected
// RequestSelector will only display inspectorRequestChooser when there is more than one request
async openInspectorRequest(requestName) {
await this.openInspectorView('inspectorViewChooserRequests');
await inspector.open();
await inspector.openInspectorRequestsView();
log.debug(`Open Inspector request ${requestName}`);
await testSubjects.click('inspectorRequestChooser');
await testSubjects.click(`inspectorRequestChooser${requestName}`);
}
async doesInspectorHaveRequests() {
await this.openInspectorRequestsView();
await inspector.open();
await inspector.openInspectorRequestsView();
return await testSubjects.exists('inspectorNoRequestsMessage');
}
async getMapboxStyle() {
log.debug('getMapboxStyle');
await inspector.open();
await this.openInspectorMapView();
await testSubjects.click('mapboxStyleTab');
const mapboxStyleContainer = await testSubjects.find('mapboxStyleContainer');