mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Maps] add query bar to map application top nav (#28210)
* display query bar in top nav with index patterns for typeahead support * add query to state and pass query to search source * store query in app state and saved object * functional tests * functional tests for geohashgrid source * functional tests for es_search_source * delete previous join properties when joining results * re-fetch on query refresh * review feedback
This commit is contained in:
parent
7cc7147cd5
commit
519956f892
21 changed files with 448 additions and 41 deletions
|
@ -35,6 +35,7 @@ export const LAYER_DATA_LOAD_ENDED = 'LAYER_DATA_LOAD_ENDED';
|
|||
export const LAYER_DATA_LOAD_ERROR = 'LAYER_DATA_LOAD_ERROR';
|
||||
export const SET_JOINS = 'SET_JOINS';
|
||||
export const SET_TIME_FILTERS = 'SET_TIME_FILTERS';
|
||||
export const SET_QUERY = 'SET_QUERY';
|
||||
export const TRIGGER_REFRESH_TIMER = 'TRIGGER_REFRESH_TIMER';
|
||||
export const UPDATE_LAYER_PROP = 'UPDATE_LAYER_PROP';
|
||||
export const UPDATE_LAYER_STYLE_FOR_SELECTED_LAYER = 'UPDATE_LAYER_STYLE';
|
||||
|
@ -406,6 +407,22 @@ export function setTimeFilters({ from, to }) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setQuery({ query }) {
|
||||
return async (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: SET_QUERY,
|
||||
query: {
|
||||
...query,
|
||||
// ensure query changes to trigger re-fetch even when query is the same because "Refresh" clicked
|
||||
queryLastTriggeredAt: (new Date()).toISOString(),
|
||||
},
|
||||
});
|
||||
|
||||
const dataFilters = getDataFilters(getState());
|
||||
await syncDataForAllLayers(getState, dispatch, dataFilters);
|
||||
};
|
||||
}
|
||||
|
||||
export function setRefreshConfig({ isPaused, interval }) {
|
||||
return async (dispatch) => {
|
||||
dispatch({
|
||||
|
|
|
@ -19,6 +19,17 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search. -->
|
||||
<div ng-show="chrome.getVisible()" class="fullWidth" data-transclude-slot="bottomRow">
|
||||
<query-bar
|
||||
query="query"
|
||||
app-name="'maps'"
|
||||
on-submit="updateQueryAndDispatch"
|
||||
index-patterns="indexPatterns"
|
||||
></query-bar>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</kbn-top-nav>
|
||||
</div>
|
||||
|
|
|
@ -19,20 +19,23 @@ import {
|
|||
setRefreshConfig,
|
||||
setGoto,
|
||||
replaceLayerList,
|
||||
setQuery,
|
||||
} from '../actions/store_actions';
|
||||
import { getIsDarkTheme, updateFlyout, FLYOUT_STATE } from '../store/ui';
|
||||
import { getUniqueIndexPatternIds } from '../selectors/map_selectors';
|
||||
import { Inspector } from 'ui/inspector';
|
||||
import { inspectorAdapters } from '../kibana_services';
|
||||
import { inspectorAdapters, indexPatternService } from '../kibana_services';
|
||||
import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal';
|
||||
import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal';
|
||||
import { showOptionsPopover } from '../components/top_nav/show_options_popover';
|
||||
import { toastNotifications } from 'ui/notify';
|
||||
|
||||
const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-gis-root';
|
||||
const DEFAULT_QUERY_LANGUAGE = 'kuery';
|
||||
|
||||
const app = uiModules.get('app/gis', []);
|
||||
|
||||
app.controller('GisMapController', ($scope, $route, config, kbnUrl) => {
|
||||
app.controller('GisMapController', ($scope, $route, config, kbnUrl, localStorage, AppState) => {
|
||||
|
||||
const savedMap = $scope.map = $route.current.locals.map;
|
||||
let isDarkTheme;
|
||||
|
@ -40,8 +43,31 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl) => {
|
|||
|
||||
inspectorAdapters.requests.reset();
|
||||
|
||||
getStore().then(store => {
|
||||
const $state = new AppState();
|
||||
$scope.$listen($state, 'fetch_with_changes', function (diff) {
|
||||
if (diff.includes('query')) {
|
||||
$scope.updateQueryAndDispatch($state.query);
|
||||
}
|
||||
});
|
||||
$scope.query = {};
|
||||
$scope.indexPatterns = [];
|
||||
$scope.updateQueryAndDispatch = function (newQuery) {
|
||||
$scope.query = newQuery;
|
||||
getStore().then(store => {
|
||||
// ignore outdated query
|
||||
if ($scope.query !== newQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
store.dispatch(setQuery({ query: $scope.query }));
|
||||
|
||||
// update appState
|
||||
$state.query = $scope.query;
|
||||
$state.save();
|
||||
});
|
||||
};
|
||||
|
||||
getStore().then(store => {
|
||||
// clear old UI state
|
||||
store.dispatch(setSelectedLayer(null));
|
||||
store.dispatch(updateFlyout(FLYOUT_STATE.NONE));
|
||||
|
@ -52,8 +78,10 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl) => {
|
|||
});
|
||||
|
||||
// sync store with savedMap mapState
|
||||
let queryFromSavedObject;
|
||||
if (savedMap.mapStateJSON) {
|
||||
const mapState = JSON.parse(savedMap.mapStateJSON);
|
||||
queryFromSavedObject = mapState.query;
|
||||
const timeFilters = mapState.timeFilters ? mapState.timeFilters : timefilter.getTime();
|
||||
store.dispatch(setTimeFilters(timeFilters));
|
||||
store.dispatch(setGoto({
|
||||
|
@ -68,6 +96,18 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl) => {
|
|||
const layerList = savedMap.layerListJSON ? JSON.parse(savedMap.layerListJSON) : [];
|
||||
store.dispatch(replaceLayerList(layerList));
|
||||
|
||||
// Initialize query, syncing appState and store
|
||||
if ($state.query) {
|
||||
$scope.updateQueryAndDispatch($state.query);
|
||||
} else if (queryFromSavedObject) {
|
||||
$scope.updateQueryAndDispatch(queryFromSavedObject);
|
||||
} else {
|
||||
$scope.updateQueryAndDispatch({
|
||||
query: '',
|
||||
language: localStorage.get('kibana.userQueryLanguage') || DEFAULT_QUERY_LANGUAGE
|
||||
});
|
||||
}
|
||||
|
||||
const root = document.getElementById(REACT_ANCHOR_DOM_ELEMENT_ID);
|
||||
render(
|
||||
<Provider store={store}>
|
||||
|
@ -76,12 +116,40 @@ app.controller('GisMapController', ($scope, $route, config, kbnUrl) => {
|
|||
root);
|
||||
});
|
||||
|
||||
let prevIndexPatternIds;
|
||||
async function updateIndexPatterns(nextIndexPatternIds) {
|
||||
const indexPatterns = [];
|
||||
const getIndexPatternPromises = nextIndexPatternIds.map(async (indexPatternId) => {
|
||||
try {
|
||||
const indexPattern = await indexPatternService.get(indexPatternId);
|
||||
indexPatterns.push(indexPattern);
|
||||
} catch(err) {
|
||||
// unable to fetch index pattern
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(getIndexPatternPromises);
|
||||
// ignore outdated results
|
||||
if (prevIndexPatternIds !== nextIndexPatternIds) {
|
||||
return;
|
||||
}
|
||||
$scope.indexPatterns = indexPatterns;
|
||||
}
|
||||
|
||||
function handleStoreChanges(store) {
|
||||
const state = store.getState();
|
||||
|
||||
// theme changes must triggered in digest cycle because top nav is still angular
|
||||
if (isDarkTheme !== getIsDarkTheme(store.getState())) {
|
||||
isDarkTheme = getIsDarkTheme(store.getState());
|
||||
if (isDarkTheme !== getIsDarkTheme(state)) {
|
||||
isDarkTheme = getIsDarkTheme(state);
|
||||
updateTheme();
|
||||
}
|
||||
|
||||
const nextIndexPatternIds = getUniqueIndexPatternIds(state);
|
||||
if (nextIndexPatternIds !== prevIndexPatternIds) {
|
||||
prevIndexPatternIds = nextIndexPatternIds;
|
||||
updateIndexPatterns(nextIndexPatternIds);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.$on('$destroy', () => {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import { uiModules } from 'ui/modules';
|
||||
import { createLegacyClass } from 'ui/utils/legacy_class';
|
||||
import { SavedObjectProvider } from 'ui/courier';
|
||||
|
@ -14,6 +15,7 @@ import {
|
|||
getLayerListRaw,
|
||||
getMapExtent,
|
||||
getRefreshConfig,
|
||||
getQuery,
|
||||
} from '../../selectors/map_selectors';
|
||||
import { getIsDarkTheme } from '../../store/ui';
|
||||
import { TileStyle } from '../../shared/layers/styles/tile_style';
|
||||
|
@ -97,6 +99,7 @@ module.factory('SavedGisMap', function (Private) {
|
|||
center: getMapCenter(state),
|
||||
timeFilters: getTimeFilters(state),
|
||||
refreshConfig: getRefreshConfig(state),
|
||||
query: _.omit(getQuery(state), 'queryLastTriggeredAt'),
|
||||
});
|
||||
|
||||
this.uiStateJSON = JSON.stringify({
|
||||
|
|
|
@ -10,6 +10,7 @@ import './vendor/jquery_ui_sortable.js';
|
|||
import './vendor/jquery_ui_sortable.css';
|
||||
|
||||
// import the uiExports that we want to "use"
|
||||
import 'uiExports/autocompleteProviders';
|
||||
import 'uiExports/fieldFormats';
|
||||
import 'uiExports/inspectorViews';
|
||||
import 'uiExports/search';
|
||||
|
|
|
@ -110,6 +110,8 @@ export const getMapColors = ({ map }) => {
|
|||
export const getTimeFilters = ({ map }) => map.mapState.timeFilters ?
|
||||
map.mapState.timeFilters : timefilter.getTime();
|
||||
|
||||
export const getQuery = ({ map }) => map.mapState.query;
|
||||
|
||||
export const getRefreshConfig = ({ map }) => map.mapState.refreshConfig;
|
||||
|
||||
export const getRefreshTimerLastTriggeredAt = ({ map }) => map.mapState.refreshTimerLastTriggeredAt;
|
||||
|
@ -122,13 +124,15 @@ export const getDataFilters = createSelector(
|
|||
getMapZoom,
|
||||
getTimeFilters,
|
||||
getRefreshTimerLastTriggeredAt,
|
||||
(mapExtent, mapBuffer, mapZoom, timeFilters, refreshTimerLastTriggeredAt) => {
|
||||
getQuery,
|
||||
(mapExtent, mapBuffer, mapZoom, timeFilters, refreshTimerLastTriggeredAt, query) => {
|
||||
return {
|
||||
extent: mapExtent,
|
||||
buffer: mapBuffer,
|
||||
zoom: mapZoom,
|
||||
timeFilters,
|
||||
refreshTimerLastTriggeredAt,
|
||||
query,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -138,8 +142,8 @@ export const getDataSources = createSelector(getMetadata, metadata => metadata ?
|
|||
export const getLayerList = createSelector(
|
||||
getLayerListRaw,
|
||||
getDataSources,
|
||||
(layerList, dataSources) => {
|
||||
return layerList.map(layerDescriptor =>
|
||||
(layerDescriptorList, dataSources) => {
|
||||
return layerDescriptorList.map(layerDescriptor =>
|
||||
createLayerInstance(layerDescriptor, dataSources));
|
||||
});
|
||||
|
||||
|
@ -158,4 +162,15 @@ export const getSelectedLayerJoinDescriptors = createSelector(
|
|||
});
|
||||
});
|
||||
|
||||
export const getUniqueIndexPatternIds = createSelector(
|
||||
getLayerList,
|
||||
(layerList) => {
|
||||
const indexPatternIds = [];
|
||||
layerList.forEach(layer => {
|
||||
indexPatternIds.push(...layer.getIndexPatternIds());
|
||||
});
|
||||
return _.uniq(indexPatternIds);
|
||||
}
|
||||
);
|
||||
|
||||
export const getTemporaryLayers = createSelector(getLayerList, (layerList) => layerList.filter(layer => layer.isTemporary()));
|
||||
|
|
|
@ -35,6 +35,10 @@ export class HeatmapLayer extends ALayer {
|
|||
return [HeatmapStyle];
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
return this._source.getIndexPatternIds();
|
||||
}
|
||||
|
||||
syncLayerWithMB(mbMap) {
|
||||
|
||||
const mbSource = mbMap.getSource(this.getId());
|
||||
|
@ -105,8 +109,14 @@ export class HeatmapLayer extends ALayer {
|
|||
|
||||
const updateDueToExtent = this.updateDueToExtent(this._source, dataMeta, dataFilters);
|
||||
|
||||
const updateDueToQuery = dataFilters.query
|
||||
&& !_.isEqual(dataMeta.query, dataFilters.query);
|
||||
|
||||
if (isSamePrecision && isSameTime && !updateDueToExtent && !updateDueToRefreshTimer) {
|
||||
if (isSamePrecision
|
||||
&& isSameTime
|
||||
&& !updateDueToExtent
|
||||
&& !updateDueToRefreshTimer
|
||||
&& !updateDueToQuery) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -118,7 +128,7 @@ export class HeatmapLayer extends ALayer {
|
|||
}
|
||||
|
||||
async _fetchNewData({ startLoading, stopLoading, onLoadError, dataMeta }) {
|
||||
const { precision, timeFilters, buffer } = dataMeta;
|
||||
const { precision, timeFilters, buffer, query } = dataMeta;
|
||||
const requestToken = Symbol(`layer-source-refresh: this.getId()`);
|
||||
startLoading('source', requestToken, dataMeta);
|
||||
try {
|
||||
|
@ -128,6 +138,7 @@ export class HeatmapLayer extends ALayer {
|
|||
extent: buffer,
|
||||
timeFilters,
|
||||
layerName,
|
||||
query,
|
||||
});
|
||||
stopLoading('source', requestToken, data);
|
||||
} catch (error) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
|
||||
import { ESJoinSource } from '../sources/es_join_source';
|
||||
import { VectorStyle } from '../styles/vector_style';
|
||||
|
||||
export class LeftInnerJoin {
|
||||
|
||||
|
@ -45,7 +46,15 @@ export class LeftInnerJoin {
|
|||
}
|
||||
|
||||
joinPropertiesToFeatureCollection(featureCollection, propertiesMap) {
|
||||
const joinFields = this.getJoinFields();
|
||||
featureCollection.features.forEach(feature => {
|
||||
// Clean up old join property values
|
||||
joinFields.forEach(({ name }) => {
|
||||
delete feature.properties[name];
|
||||
const stylePropertyName = VectorStyle.getComputedFieldName(name);
|
||||
delete feature.properties[stylePropertyName];
|
||||
});
|
||||
|
||||
const joinKey = feature.properties[this._descriptor.leftField];
|
||||
if (propertiesMap.has(joinKey)) {
|
||||
feature.properties = {
|
||||
|
@ -84,5 +93,9 @@ export class LeftInnerJoin {
|
|||
return tooltipProps;
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
return this._rightSource.getIndexPatternIds();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* 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 { LeftInnerJoin } from './left_inner_join';
|
||||
|
||||
jest.mock('ui/vis/editors/default/schemas', () => {
|
||||
class MockSchemas {}
|
||||
return {
|
||||
Schemas: MockSchemas
|
||||
};
|
||||
});
|
||||
jest.mock('../../../kibana_services', () => {});
|
||||
jest.mock('ui/vis/agg_configs', () => {});
|
||||
jest.mock('ui/timefilter/timefilter', () => {});
|
||||
|
||||
|
||||
const leftJoin = new LeftInnerJoin({
|
||||
leftField: "iso2",
|
||||
right: {
|
||||
id: "d3625663-5b34-4d50-a784-0d743f676a0c",
|
||||
indexPatternId: "90943e30-9a47-11e8-b64d-95841ca0b247",
|
||||
indexPatternTitle: "kibana_sample_data_logs",
|
||||
term: "geo.dest",
|
||||
}
|
||||
});
|
||||
|
||||
describe('joinPropertiesToFeatureCollection', () => {
|
||||
const COUNT_PROPERTY_NAME = '__kbnjoin__count_groupby_kibana_sample_data_logs.geo.dest';
|
||||
|
||||
it('Should add join property to features in feature collection', () => {
|
||||
const featureCollection = {
|
||||
features: [
|
||||
{
|
||||
properties: {
|
||||
iso2: "CN",
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const propertiesMap = new Map();
|
||||
propertiesMap.set('CN', { [COUNT_PROPERTY_NAME]: 61 });
|
||||
|
||||
leftJoin.joinPropertiesToFeatureCollection(featureCollection, propertiesMap);
|
||||
expect(featureCollection.features[0].properties).toEqual({
|
||||
iso2: "CN",
|
||||
[COUNT_PROPERTY_NAME]: 61,
|
||||
});
|
||||
});
|
||||
|
||||
it('Should delete previous join property values from features in feature collection', () => {
|
||||
const featureCollection = {
|
||||
features: [
|
||||
{
|
||||
properties: {
|
||||
iso2: "CN",
|
||||
[COUNT_PROPERTY_NAME]: 61,
|
||||
[`__kbn__scaled(${COUNT_PROPERTY_NAME})`]: 1,
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
const propertiesMap = new Map();
|
||||
|
||||
leftJoin.joinPropertiesToFeatureCollection(featureCollection, propertiesMap);
|
||||
expect(featureCollection.features[0].properties).toEqual({
|
||||
iso2: "CN",
|
||||
});
|
||||
});
|
||||
});
|
|
@ -196,5 +196,9 @@ export class ALayer {
|
|||
return this._dataRequests.find(dataRequest => dataRequest.getDataId() === 'source');
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -132,6 +132,7 @@ export class ESGeohashGridSource extends VectorSource {
|
|||
extent: searchFilters.buffer,
|
||||
timeFilters: searchFilters.timeFilters,
|
||||
layerName,
|
||||
query: searchFilters.query,
|
||||
});
|
||||
|
||||
if (this._descriptor.requestType === RENDER_AS.GRID) {
|
||||
|
@ -157,19 +158,27 @@ export class ESGeohashGridSource extends VectorSource {
|
|||
return true;
|
||||
}
|
||||
|
||||
isQueryAware() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getFieldNames() {
|
||||
return this.getMetricFields().map(({ propertyKey }) => {
|
||||
return propertyKey;
|
||||
});
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
return [this._descriptor.indexPatternId];
|
||||
}
|
||||
|
||||
async getNumberFields() {
|
||||
return this.getMetricFields().map(({ propertyKey: name, propertyLabel: label }) => {
|
||||
return { label, name };
|
||||
});
|
||||
}
|
||||
|
||||
async getGeoJsonPointsWithTotalCount({ precision, extent, timeFilters, layerName }) {
|
||||
async getGeoJsonPointsWithTotalCount({ precision, extent, timeFilters, layerName, query }) {
|
||||
|
||||
let indexPattern;
|
||||
try {
|
||||
|
@ -197,6 +206,7 @@ export class ESGeohashGridSource extends VectorSource {
|
|||
filters.push(timeService.createFilter(indexPattern, timeFilters));
|
||||
return filters;
|
||||
});
|
||||
searchSource.setField('query', query);
|
||||
|
||||
resp = await fetchSearchSourceAndRecordWithInspector({
|
||||
searchSource,
|
||||
|
|
|
@ -84,6 +84,10 @@ export class ESJoinSource extends ASource {
|
|||
inspectorAdapters.requests.resetRequest(this._descriptor.id);
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
return [this._descriptor.indexPatternId];
|
||||
}
|
||||
|
||||
async getPropertiesMap(searchFilters, leftSourceName, leftFieldName) {
|
||||
|
||||
if (!this.hasCompleteConfig()) {
|
||||
|
@ -108,6 +112,7 @@ export class ESJoinSource extends ASource {
|
|||
}
|
||||
return filters;
|
||||
});
|
||||
searchSource.setField('query', searchFilters.query);
|
||||
|
||||
const dsl = aggConfigs.toDsl();
|
||||
searchSource.setField('aggs', dsl);
|
||||
|
@ -165,6 +170,10 @@ export class ESJoinSource extends ASource {
|
|||
return true;
|
||||
}
|
||||
|
||||
isQueryAware() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getJoinDescription(leftSourceName, leftFieldName) {
|
||||
const metrics = this._getValidMetrics().map(metric => {
|
||||
return metric.type !== 'count' ? `${metric.type}(${metric.field})` : 'count(*)';
|
||||
|
|
|
@ -98,6 +98,10 @@ export class ESSearchSource extends VectorSource {
|
|||
return true;
|
||||
}
|
||||
|
||||
isQueryAware() {
|
||||
return true;
|
||||
}
|
||||
|
||||
getFieldNames() {
|
||||
return [
|
||||
this._descriptor.geoField,
|
||||
|
@ -105,6 +109,10 @@ export class ESSearchSource extends VectorSource {
|
|||
];
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
return [this._descriptor.indexPatternId];
|
||||
}
|
||||
|
||||
renderDetails() {
|
||||
return (
|
||||
<ESSourceDetails
|
||||
|
@ -159,6 +167,7 @@ export class ESSearchSource extends VectorSource {
|
|||
}
|
||||
return filters;
|
||||
});
|
||||
searchSource.setField('query', searchFilters.query);
|
||||
|
||||
resp = await fetchSearchSourceAndRecordWithInspector({
|
||||
searchSource,
|
||||
|
|
|
@ -51,6 +51,10 @@ export class ASource {
|
|||
return false;
|
||||
}
|
||||
|
||||
isQueryAware() {
|
||||
return false;
|
||||
}
|
||||
|
||||
getFieldNames() {
|
||||
return [];
|
||||
}
|
||||
|
@ -62,6 +66,10 @@ export class ASource {
|
|||
renderSourceSettingsEditor() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -160,6 +160,14 @@ export class VectorLayer extends ALayer {
|
|||
return [...numberFieldOptions, ...joinFields];
|
||||
}
|
||||
|
||||
getIndexPatternIds() {
|
||||
const indexPatternIds = this._source.getIndexPatternIds();
|
||||
this.getValidJoins().forEach(join => {
|
||||
indexPatternIds.push(...join.getIndexPatternIds());
|
||||
});
|
||||
return indexPatternIds;
|
||||
}
|
||||
|
||||
_findDataRequestForSource(sourceDataId) {
|
||||
return this._dataRequests.find(dataRequest => dataRequest.getDataId() === sourceDataId);
|
||||
}
|
||||
|
@ -169,8 +177,9 @@ export class VectorLayer extends ALayer {
|
|||
const refreshTimerAware = await source.isRefreshTimerAware();
|
||||
const extentAware = source.isFilterByMapBounds();
|
||||
const isFieldAware = source.isFieldAware();
|
||||
const isQueryAware = source.isQueryAware();
|
||||
|
||||
if (!timeAware && !refreshTimerAware && !extentAware && !isFieldAware) {
|
||||
if (!timeAware && !refreshTimerAware && !extentAware && !isFieldAware && !isQueryAware) {
|
||||
const sourceDataRequest = this._findDataRequestForSource(sourceDataId);
|
||||
if (sourceDataRequest && sourceDataRequest.hasDataOrRequestInProgress()) {
|
||||
return true;
|
||||
|
@ -203,10 +212,16 @@ export class VectorLayer extends ALayer {
|
|||
updateDueToFields = !_.isEqual(meta.fieldNames, filters.fieldNames);
|
||||
}
|
||||
|
||||
let updateDueToQuery = false;
|
||||
if (isQueryAware) {
|
||||
updateDueToQuery = !_.isEqual(meta.query, filters.query);
|
||||
}
|
||||
|
||||
return !updateDueToTime
|
||||
&& !updateDueToRefreshTimer
|
||||
&& !this.updateDueToExtent(source, meta, filters)
|
||||
&& !updateDueToFields;
|
||||
&& !updateDueToFields
|
||||
&& !updateDueToQuery;
|
||||
}
|
||||
|
||||
async _syncJoin(join, { startLoading, stopLoading, onLoadError, dataFilters }) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import {
|
|||
MAP_READY,
|
||||
MAP_DESTROYED,
|
||||
SET_TIME_FILTERS,
|
||||
SET_QUERY,
|
||||
UPDATE_LAYER_PROP,
|
||||
UPDATE_LAYER_STYLE_FOR_SELECTED_LAYER,
|
||||
PROMOTE_TEMPORARY_STYLES,
|
||||
|
@ -85,6 +86,7 @@ const INITIAL_STATE = {
|
|||
extent: null,
|
||||
mouseCoordinates: null,
|
||||
timeFilters: null,
|
||||
query: null,
|
||||
refreshConfig: null,
|
||||
refreshTimerLastTriggeredAt: null,
|
||||
},
|
||||
|
@ -162,6 +164,9 @@ export function map(state = INITIAL_STATE, action) {
|
|||
case SET_TIME_FILTERS:
|
||||
const { from, to } = action;
|
||||
return { ...state, mapState: { ...state.mapState, timeFilters: { from, to } } };
|
||||
case SET_QUERY:
|
||||
const { query } = action;
|
||||
return { ...state, mapState: { ...state.mapState, query } };
|
||||
case SET_REFRESH_CONFIG:
|
||||
const { isPaused, interval } = action;
|
||||
return {
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
import expect from 'expect.js';
|
||||
|
||||
export default function ({ getPageObjects }) {
|
||||
export default function ({ getPageObjects, getService }) {
|
||||
const PageObjects = getPageObjects(['gis']);
|
||||
const queryBar = getService('queryBar');
|
||||
const DOC_COUNT_PROP_NAME = 'doc_count';
|
||||
|
||||
describe('layer geohashgrid aggregation source', () => {
|
||||
|
@ -25,10 +26,6 @@ export default function ({ getPageObjects }) {
|
|||
await PageObjects.gis.loadSavedMap('geohashgrid heatmap example');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await PageObjects.gis.closeInspector();
|
||||
});
|
||||
|
||||
const LAYER_ID = '3xlvm';
|
||||
const EXPECTED_NUMBER_FEATURES = 6;
|
||||
const HEATMAP_PROP_NAME = '__kbn_heatmap_weight__';
|
||||
|
@ -51,6 +48,26 @@ export default function ({ getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('query bar', () => {
|
||||
before(async () => {
|
||||
await queryBar.setQuery('machine.os.raw : "win 8"');
|
||||
await queryBar.submitQuery();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await queryBar.setQuery('');
|
||||
await queryBar.submitQuery();
|
||||
});
|
||||
|
||||
it('should apply query to geohashgrid aggregation request', async () => {
|
||||
await PageObjects.gis.openInspectorRequestsView();
|
||||
const requestStats = await PageObjects.gis.getInspectorTableData();
|
||||
const hits = PageObjects.gis.getInspectorStatRowHit(requestStats, 'Hits (total)');
|
||||
await PageObjects.gis.closeInspector();
|
||||
expect(hits).to.equal('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspector', () => {
|
||||
afterEach(async () => {
|
||||
await PageObjects.gis.closeInspector();
|
||||
|
@ -80,10 +97,6 @@ export default function ({ getPageObjects }) {
|
|||
await PageObjects.gis.loadSavedMap('geohashgrid vector grid example');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await PageObjects.gis.closeInspector();
|
||||
});
|
||||
|
||||
const LAYER_ID = 'g1xkv';
|
||||
const EXPECTED_NUMBER_FEATURES = 6;
|
||||
const MAX_OF_BYTES_PROP_NAME = 'max_of_bytes';
|
||||
|
@ -106,6 +119,26 @@ export default function ({ getPageObjects }) {
|
|||
});
|
||||
});
|
||||
|
||||
describe('query bar', () => {
|
||||
before(async () => {
|
||||
await queryBar.setQuery('machine.os.raw : "win 8"');
|
||||
await queryBar.submitQuery();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await queryBar.setQuery('');
|
||||
await queryBar.submitQuery();
|
||||
});
|
||||
|
||||
it('should apply query to geohashgrid aggregation request', async () => {
|
||||
await PageObjects.gis.openInspectorRequestsView();
|
||||
const requestStats = await PageObjects.gis.getInspectorTableData();
|
||||
const hits = PageObjects.gis.getInspectorStatRowHit(requestStats, 'Hits (total)');
|
||||
await PageObjects.gis.closeInspector();
|
||||
expect(hits).to.equal('1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspector', () => {
|
||||
afterEach(async () => {
|
||||
await PageObjects.gis.closeInspector();
|
||||
|
|
|
@ -6,26 +6,24 @@
|
|||
|
||||
import expect from 'expect.js';
|
||||
|
||||
export default function ({ getPageObjects }) {
|
||||
export default function ({ getPageObjects, getService }) {
|
||||
const PageObjects = getPageObjects(['gis']);
|
||||
const queryBar = getService('queryBar');
|
||||
|
||||
describe('elasticsearch document layer', () => {
|
||||
before(async () => {
|
||||
await PageObjects.gis.loadSavedMap('document example');
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
async function getRequestTimestamp() {
|
||||
await PageObjects.gis.openInspectorRequestsView();
|
||||
const requestStats = await PageObjects.gis.getInspectorTableData();
|
||||
const requestTimestamp = PageObjects.gis.getInspectorStatRowHit(requestStats, 'Request timestamp');
|
||||
await PageObjects.gis.closeInspector();
|
||||
});
|
||||
return requestTimestamp;
|
||||
}
|
||||
|
||||
it('should re-fetch geohashgrid aggregation with refresh timer', async () => {
|
||||
async function getRequestTimestamp() {
|
||||
await PageObjects.gis.openInspectorRequestsView();
|
||||
const requestStats = await PageObjects.gis.getInspectorTableData();
|
||||
const requestTimestamp = PageObjects.gis.getInspectorStatRowHit(requestStats, 'Request timestamp');
|
||||
await PageObjects.gis.closeInspector();
|
||||
return requestTimestamp;
|
||||
}
|
||||
const beforeRefreshTimerTimestamp = await getRequestTimestamp();
|
||||
expect(beforeRefreshTimerTimestamp.length).to.be(24);
|
||||
await PageObjects.gis.triggerSingleRefresh(1000);
|
||||
|
@ -33,6 +31,33 @@ export default function ({ getPageObjects }) {
|
|||
expect(beforeRefreshTimerTimestamp).not.to.equal(afterRefreshTimerTimestamp);
|
||||
});
|
||||
|
||||
describe('query bar', () => {
|
||||
before(async () => {
|
||||
await queryBar.setQuery('machine.os.raw : "win 8"');
|
||||
await queryBar.submitQuery();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await queryBar.setQuery('');
|
||||
await queryBar.submitQuery();
|
||||
});
|
||||
|
||||
it('should apply query to search request', async () => {
|
||||
await PageObjects.gis.openInspectorRequestsView();
|
||||
const requestStats = await PageObjects.gis.getInspectorTableData();
|
||||
const hits = PageObjects.gis.getInspectorStatRowHit(requestStats, 'Hits');
|
||||
await PageObjects.gis.closeInspector();
|
||||
expect(hits).to.equal('1');
|
||||
});
|
||||
|
||||
it('should re-fetch query when "refresh" is clicked', async () => {
|
||||
const beforeQueryRefreshTimestamp = await getRequestTimestamp();
|
||||
await queryBar.submitQuery();
|
||||
const afterQueryRefreshTimestamp = await getRequestTimestamp();
|
||||
expect(beforeQueryRefreshTimestamp).not.to.equal(afterQueryRefreshTimestamp);
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspector', () => {
|
||||
it('should register elasticsearch request in inspector', async () => {
|
||||
await PageObjects.gis.openInspectorRequestsView();
|
||||
|
|
|
@ -6,9 +6,11 @@
|
|||
|
||||
import expect from 'expect.js';
|
||||
|
||||
export default function ({ getPageObjects }) {
|
||||
export default function ({ getPageObjects, getService }) {
|
||||
|
||||
const PageObjects = getPageObjects(['gis', 'header']);
|
||||
const queryBar = getService('queryBar');
|
||||
const browser = getService('browser');
|
||||
|
||||
describe('gis-map saved object management', () => {
|
||||
|
||||
|
@ -42,6 +44,49 @@ export default function ({ getPageObjects }) {
|
|||
const layerExists = await PageObjects.gis.doesLayerExist('geo_shapes*');
|
||||
expect(layerExists).to.equal(true);
|
||||
});
|
||||
|
||||
describe('mapState contains query', () => {
|
||||
before(async () => {
|
||||
await PageObjects.gis.loadSavedMap('document example with query');
|
||||
});
|
||||
|
||||
it('should update query bar with query stored with map', async () => {
|
||||
const query = await queryBar.getQueryString();
|
||||
expect(query).to.equal('machine.os.raw : "ios"');
|
||||
});
|
||||
|
||||
it('should update app state with query stored with map', async () => {
|
||||
const currentUrl = await browser.getCurrentUrl();
|
||||
const appState = currentUrl.substring(currentUrl.indexOf('_a='));
|
||||
expect(appState).to.equal('_a=(query:(language:kuery,query:%27machine.os.raw%20:%20%22ios%22%27))');
|
||||
});
|
||||
|
||||
it('should apply query stored with map', async () => {
|
||||
await PageObjects.gis.openInspectorRequestsView();
|
||||
const requestStats = await PageObjects.gis.getInspectorTableData();
|
||||
const hits = PageObjects.gis.getInspectorStatRowHit(requestStats, 'Hits');
|
||||
await PageObjects.gis.closeInspector();
|
||||
expect(hits).to.equal('2');
|
||||
});
|
||||
|
||||
it('should override query stored with map when query is provided in app state', async () => {
|
||||
const currentUrl = await browser.getCurrentUrl();
|
||||
const kibanaBaseUrl = currentUrl.substring(0, currentUrl.indexOf('#'));
|
||||
const appState = `_a=(query:(language:kuery,query:'machine.os.raw%20:%20"win%208"'))`;
|
||||
const urlWithQueryInAppState = `${kibanaBaseUrl}#/map/8eabdab0-144f-11e9-809f-ad25bb78262c?${appState}`;
|
||||
|
||||
await browser.get(urlWithQueryInAppState, true);
|
||||
|
||||
const query = await queryBar.getQueryString();
|
||||
expect(query).to.equal('machine.os.raw : "win 8"');
|
||||
|
||||
await PageObjects.gis.openInspectorRequestsView();
|
||||
const requestStats = await PageObjects.gis.getInspectorTableData();
|
||||
await PageObjects.gis.closeInspector();
|
||||
const hits = PageObjects.gis.getInspectorStatRowHit(requestStats, 'Hits');
|
||||
expect(hits).to.equal('1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('create', () => {
|
||||
|
|
|
@ -81,12 +81,44 @@
|
|||
"type" : "envelope",
|
||||
"coordinates" : [
|
||||
[
|
||||
-131.611171875002,
|
||||
39.17729080254418
|
||||
-130.50168,
|
||||
45.55574
|
||||
],
|
||||
[
|
||||
-69.20882812500054,
|
||||
25.973294086525087
|
||||
-70.72014,
|
||||
18.91275
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"type": "doc",
|
||||
"value": {
|
||||
"index": ".kibana",
|
||||
"type": "doc",
|
||||
"id": "gis-map:8eabdab0-144f-11e9-809f-ad25bb78262c",
|
||||
"source": {
|
||||
"type" : "gis-map",
|
||||
"gis-map" : {
|
||||
"title" : "document example with query",
|
||||
"description" : "",
|
||||
"mapStateJSON" : "{\"zoom\":4.1,\"center\":{\"lon\":-100.61091,\"lat\":33.23887},\"timeFilters\":{\"from\":\"2015-09-20T00:00:00.000Z\",\"to\":\"2015-09-20T01:00:00.000Z\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":1000},\"query\":{\"query\":\"machine.os.raw : \\\"ios\\\"\",\"language\":\"kuery\"}}",
|
||||
"layerListJSON" : "[{\"id\":\"0hmz5\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"id\":\"road_map\"},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"TILE\",\"properties\":{\"alphaValue\":1}},\"type\":\"TILE\",\"minZoom\":0,\"maxZoom\":24},{\"id\":\"z52lq\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"sourceDescriptor\":{\"id\":\"e1a5e1a6-676c-4a89-8ea9-0d91d64b73c6\",\"type\":\"ES_SEARCH\",\"indexPatternId\":\"c698b940-e149-11e8-a35a-370a8516603a\",\"geoField\":\"geo.coordinates\",\"limit\":2048,\"filterByMapBounds\":true,\"showTooltip\":true,\"tooltipProperties\":[\"machine.os\"]},\"visible\":true,\"temporary\":false,\"style\":{\"type\":\"VECTOR\",\"properties\":{\"fillColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#e6194b\"}},\"lineColor\":{\"type\":\"STATIC\",\"options\":{\"color\":\"#FFFFFF\"}},\"lineWidth\":{\"type\":\"STATIC\",\"options\":{\"size\":1}},\"iconSize\":{\"type\":\"STATIC\",\"options\":{\"size\":10}},\"alphaValue\":1},\"previousStyle\":null},\"type\":\"VECTOR\"}]",
|
||||
"uiStateJSON" : "{\"isDarkMode\":false}",
|
||||
"bounds" : {
|
||||
"type" : "envelope",
|
||||
"coordinates" : [
|
||||
[
|
||||
-130.50168,
|
||||
45.55574
|
||||
],
|
||||
[
|
||||
-70.72014,
|
||||
18.91275
|
||||
]
|
||||
]
|
||||
}
|
||||
|
@ -113,12 +145,12 @@
|
|||
"type" : "envelope",
|
||||
"coordinates" : [
|
||||
[
|
||||
-131.611171875002,
|
||||
39.17729080254418
|
||||
14.1441,
|
||||
31.99653
|
||||
],
|
||||
[
|
||||
-69.20882812500054,
|
||||
25.973294086525087
|
||||
140.52442,
|
||||
-32.07532
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
|
@ -207,6 +207,7 @@ export function GisPageProvider({ getService, getPageObjects }) {
|
|||
await testSubjects.click('mapboxStyleTab');
|
||||
const mapboxStyleContainer = await testSubjects.find('mapboxStyleContainer');
|
||||
const mapboxStyleJson = await mapboxStyleContainer.getVisibleText();
|
||||
await this.closeInspector();
|
||||
let mapboxStyle;
|
||||
try {
|
||||
mapboxStyle = JSON.parse(mapboxStyleJson);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue