mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[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:
parent
d89a97fb14
commit
54bbdbaedb
30 changed files with 447 additions and 96 deletions
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -213,6 +213,7 @@ class FilterEditorUI extends Component<Props, State> {
|
|||
onChange={this.onIndexPatternChange}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
isClearable={false}
|
||||
data-test-subj="filterIndexPatternsSelect"
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiFlexItem>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
],
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = () => {
|
||||
|
|
114
x-pack/plugins/maps/public/embeddable/map_embeddable.js
Normal file
114
x-pack/plugins/maps/public/embeddable/map_embeddable.js
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
|
@ -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';
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
87
x-pack/test/functional/apps/maps/embeddable/dashboard.js
Normal file
87
x-pack/test/functional/apps/maps/embeddable/dashboard.js
Normal 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);
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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'));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue