[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:
Nathan Reese 2019-01-10 12:53:04 -07:00 committed by GitHub
parent 7cc7147cd5
commit 519956f892
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 448 additions and 41 deletions

View file

@ -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({

View file

@ -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>

View file

@ -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', () => {

View file

@ -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({

View file

@ -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';

View file

@ -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()));

View file

@ -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) {

View file

@ -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();
}
}

View file

@ -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",
});
});
});

View file

@ -196,5 +196,9 @@ export class ALayer {
return this._dataRequests.find(dataRequest => dataRequest.getDataId() === 'source');
}
getIndexPatternIds() {
return [];
}
}

View file

@ -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,

View file

@ -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(*)';

View file

@ -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,

View file

@ -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 [];
}
}

View file

@ -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 }) {

View file

@ -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 {

View file

@ -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();

View file

@ -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();

View file

@ -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', () => {

View file

@ -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
]
]
}

View file

@ -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);