diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts
index 8fb6140d55e31..bf185f78941de 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/angular/context_state.ts
@@ -24,9 +24,9 @@ import {
syncStates,
BaseStateContainer,
} from '../../../../../../../plugins/kibana_utils/public';
-import { esFilters, FilterManager, Filter } from '../../../../../../../plugins/data/public';
+import { esFilters, FilterManager, Filter, Query } from '../../../../../../../plugins/data/public';
-interface AppState {
+export interface AppState {
/**
* Columns displayed in the table, cannot be changed by UI, just in discover's main app
*/
@@ -47,6 +47,7 @@ interface AppState {
* Number of records to be fetched after the anchor records (older records)
*/
successorCount: number;
+ query?: Query;
}
interface GlobalState {
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js
index a175a1aebebdf..df970ab5f2584 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/field_chooser.js
@@ -24,7 +24,11 @@ import './discover_field';
import './discover_field_search_directive';
import './discover_index_pattern_directive';
import fieldChooserTemplate from './field_chooser.html';
-import { IndexPatternFieldList } from '../../../../../../../../plugins/data/public';
+import {
+ IndexPatternFieldList,
+ KBN_FIELD_TYPES,
+} from '../../../../../../../../plugins/data/public';
+import { getMapsAppUrl, isFieldVisualizable, isMapsAppRegistered } from './lib/visualize_url_utils';
export function createFieldChooserDirective($location, config, $route) {
return {
@@ -186,8 +190,15 @@ export function createFieldChooserDirective($location, config, $route) {
return '';
}
+ if (
+ (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
+ isMapsAppRegistered()
+ ) {
+ return getMapsAppUrl(field, $scope.indexPattern, $scope.state, $scope.columns);
+ }
+
let agg = {};
- const isGeoPoint = field.type === 'geo_point';
+ const isGeoPoint = field.type === KBN_FIELD_TYPES.GEO_POINT;
const type = isGeoPoint ? 'tile_map' : 'histogram';
// If we're visualizing a date field, and our index is time based (and thus has a time filter),
// then run a date histogram
@@ -243,7 +254,7 @@ export function createFieldChooserDirective($location, config, $route) {
$scope.computeDetails = function(field, recompute) {
if (_.isUndefined(field.details) || recompute) {
field.details = {
- visualizeUrl: field.visualizable ? getVisualizeUrl(field) : null,
+ visualizeUrl: isFieldVisualizable(field) ? getVisualizeUrl(field) : null,
...fieldCalculator.getFieldValueCounts({
hits: $scope.hits,
field: field,
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html
index 5d134911fc91b..333dc472e956d 100644
--- a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/detail_views/string.html
@@ -79,7 +79,7 @@
@@ -87,7 +87,7 @@
diff --git a/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts
new file mode 100644
index 0000000000000..8dbf3cd79ccb1
--- /dev/null
+++ b/src/legacy/core_plugins/kibana/public/discover/np_ready/components/field_chooser/lib/visualize_url_utils.ts
@@ -0,0 +1,108 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import uuid from 'uuid/v4';
+// @ts-ignore
+import rison from 'rison-node';
+import {
+ IFieldType,
+ IIndexPattern,
+ KBN_FIELD_TYPES,
+} from '../../../../../../../../../plugins/data/public';
+import { AppState } from '../../../angular/context_state';
+import { getServices } from '../../../../kibana_services';
+
+function getMapsAppBaseUrl() {
+ const mapsAppVisAlias = getServices()
+ .visualizations.types.getAliases()
+ .find(({ name }) => {
+ return name === 'maps';
+ });
+ return mapsAppVisAlias ? mapsAppVisAlias.aliasUrl : null;
+}
+
+export function isMapsAppRegistered() {
+ return getServices()
+ .visualizations.types.getAliases()
+ .some(({ name }) => {
+ return name === 'maps';
+ });
+}
+
+export function isFieldVisualizable(field: IFieldType) {
+ if (
+ (field.type === KBN_FIELD_TYPES.GEO_POINT || field.type === KBN_FIELD_TYPES.GEO_SHAPE) &&
+ isMapsAppRegistered()
+ ) {
+ return true;
+ }
+ return field.visualizable;
+}
+
+export function getMapsAppUrl(
+ field: IFieldType,
+ indexPattern: IIndexPattern,
+ appState: AppState,
+ columns: string[]
+) {
+ const mapAppParams = new URLSearchParams();
+
+ // Copy global state
+ const locationSplit = window.location.href.split('discover?');
+ if (locationSplit.length > 1) {
+ const discoverParams = new URLSearchParams(locationSplit[1]);
+ const globalStateUrlValue = discoverParams.get('_g');
+ if (globalStateUrlValue) {
+ mapAppParams.set('_g', globalStateUrlValue);
+ }
+ }
+
+ // Copy filters and query in app state
+ const mapsAppState: any = {
+ filters: appState.filters || [],
+ };
+ if (appState.query) {
+ mapsAppState.query = appState.query;
+ }
+ // @ts-ignore
+ mapAppParams.set('_a', rison.encode(mapsAppState));
+
+ // create initial layer descriptor
+ const hasColumns = columns && columns.length && columns[0] !== '_source';
+ mapAppParams.set(
+ 'initialLayers',
+ // @ts-ignore
+ rison.encode_array([
+ {
+ id: uuid(),
+ label: indexPattern.title,
+ sourceDescriptor: {
+ id: uuid(),
+ type: 'ES_SEARCH',
+ geoField: field.name,
+ tooltipProperties: hasColumns ? columns : [],
+ indexPatternId: indexPattern.id,
+ },
+ visible: true,
+ type: 'VECTOR',
+ },
+ ])
+ );
+
+ return getServices().addBasePath(`${getMapsAppBaseUrl()}?${mapAppParams.toString()}`);
+}
diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js
index 5ccc5625849d2..080b8c8ee753f 100644
--- a/test/functional/page_objects/discover_page.js
+++ b/test/functional/page_objects/discover_page.js
@@ -206,12 +206,6 @@ export function DiscoverPageProvider({ getService, getPageObjects }) {
return await testSubjects.getVisibleText('discoverQueryHits');
}
- async query(queryString) {
- await find.setValue('input[aria-label="Search input"]', queryString);
- await find.clickByCssSelector('button[aria-label="Search"]');
- await PageObjects.header.waitUntilLoadingHasFinished();
- }
-
async getDocHeader() {
const header = await find.byCssSelector('thead > tr:nth-child(1)');
return await header.getVisibleText();
diff --git a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js
index 45ee441716769..3cae75231d28e 100644
--- a/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js
+++ b/x-pack/legacy/plugins/maps/public/angular/get_initial_layers.js
@@ -9,7 +9,7 @@ import { EMSTMSSource } from '../layers/sources/ems_tms_source';
import chrome from 'ui/chrome';
import { getKibanaTileMap } from '../meta';
-export function getInitialLayers(layerListJSON) {
+export function getInitialLayers(layerListJSON, initialLayers = []) {
if (layerListJSON) {
return JSON.parse(layerListJSON);
}
@@ -19,7 +19,7 @@ export function getInitialLayers(layerListJSON) {
const sourceDescriptor = KibanaTilemapSource.createDescriptor();
const source = new KibanaTilemapSource(sourceDescriptor);
const layer = source.createDefaultLayer();
- return [layer.toLayerDescriptor()];
+ return [layer.toLayerDescriptor(), ...initialLayers];
}
const isEmsEnabled = chrome.getInjected('isEmsEnabled', true);
@@ -27,8 +27,8 @@ export function getInitialLayers(layerListJSON) {
const descriptor = EMSTMSSource.createDescriptor({ isAutoSelect: true });
const source = new EMSTMSSource(descriptor);
const layer = source.createDefaultLayer();
- return [layer.toLayerDescriptor()];
+ return [layer.toLayerDescriptor(), ...initialLayers];
}
- return [];
+ return initialLayers;
}
diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
index 95c8ff975b1d6..a8e9ae46a3b9a 100644
--- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js
+++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js
@@ -6,6 +6,7 @@
import _ from 'lodash';
import chrome from 'ui/chrome';
+import rison from 'rison-node';
import 'ui/directives/listen';
import 'ui/directives/storage';
import React from 'react';
@@ -66,6 +67,32 @@ const REACT_ANCHOR_DOM_ELEMENT_ID = 'react-maps-root';
const app = uiModules.get(MAP_APP_PATH, []);
+function getInitialLayersFromUrlParam() {
+ const locationSplit = window.location.href.split('?');
+ if (locationSplit.length <= 1) {
+ return [];
+ }
+ const mapAppParams = new URLSearchParams(locationSplit[1]);
+ if (!mapAppParams.has('initialLayers')) {
+ return [];
+ }
+
+ try {
+ return rison.decode_array(mapAppParams.get('initialLayers'));
+ } catch (e) {
+ toastNotifications.addWarning({
+ title: i18n.translate('xpack.maps.initialLayers.unableToParseTitle', {
+ defaultMessage: `Inital layers not added to map`,
+ }),
+ text: i18n.translate('xpack.maps.initialLayers.unableToParseMessage', {
+ defaultMessage: `Unable to parse contents of 'initialLayers' parameter. Error: {errorMsg}`,
+ values: { errorMsg: e.message },
+ }),
+ });
+ return [];
+ }
+}
+
app.controller(
'GisMapController',
($scope, $route, kbnUrl, localStorage, AppState, globalState) => {
@@ -333,7 +360,7 @@ app.controller(
store.dispatch(setOpenTOCDetails(_.get(uiState, 'openTOCDetails', [])));
}
- const layerList = getInitialLayers(savedMap.layerListJSON);
+ const layerList = getInitialLayers(savedMap.layerListJSON, getInitialLayersFromUrlParam());
initialLayerListConfig = copyPersistentState(layerList);
store.dispatch(replaceLayerList(layerList));
store.dispatch(setRefreshConfig($scope.refreshConfig));
diff --git a/x-pack/test/functional/apps/maps/discover.js b/x-pack/test/functional/apps/maps/discover.js
new file mode 100644
index 0000000000000..ce33596476755
--- /dev/null
+++ b/x-pack/test/functional/apps/maps/discover.js
@@ -0,0 +1,50 @@
+/*
+ * 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 '@kbn/expect';
+
+export default function({ getService, getPageObjects }) {
+ const queryBar = getService('queryBar');
+ const PageObjects = getPageObjects(['common', 'discover', 'header', 'maps', 'timePicker']);
+
+ describe('discover visualize button', () => {
+ beforeEach(async () => {
+ await PageObjects.common.navigateToApp('discover');
+ });
+
+ it('should link geo_shape fields to Maps application', async () => {
+ await PageObjects.discover.selectIndexPattern('geo_shapes*');
+ await PageObjects.discover.clickFieldListItem('geometry');
+ await PageObjects.discover.clickFieldListItemVisualize('geometry');
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.maps.waitForLayersToLoad();
+ const doesLayerExist = await PageObjects.maps.doesLayerExist('geo_shapes*');
+ expect(doesLayerExist).to.equal(true);
+ const hits = await PageObjects.maps.getHits();
+ expect(hits).to.equal('4');
+ });
+
+ it('should link geo_point fields to Maps application with time and query context', async () => {
+ await PageObjects.discover.selectIndexPattern('logstash-*');
+ await PageObjects.timePicker.setAbsoluteRange(
+ 'Sep 22, 2015 @ 00:00:00.000',
+ 'Sep 22, 2015 @ 04:00:00.000'
+ );
+ await queryBar.setQuery('machine.os.raw : "ios"');
+ await queryBar.submitQuery();
+ await PageObjects.header.waitUntilLoadingHasFinished();
+
+ await PageObjects.discover.clickFieldListItem('geo.coordinates');
+ await PageObjects.discover.clickFieldListItemVisualize('geo.coordinates');
+ await PageObjects.header.waitUntilLoadingHasFinished();
+ await PageObjects.maps.waitForLayersToLoad();
+ const doesLayerExist = await PageObjects.maps.doesLayerExist('logstash-*');
+ expect(doesLayerExist).to.equal(true);
+ const hits = await PageObjects.maps.getHits();
+ expect(hits).to.equal('7');
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/maps/index.js b/x-pack/test/functional/apps/maps/index.js
index 0545fcd1b6453..e8a9d7ba54bc5 100644
--- a/x-pack/test/functional/apps/maps/index.js
+++ b/x-pack/test/functional/apps/maps/index.js
@@ -45,6 +45,7 @@ export default function({ loadTestFile, getService }) {
loadTestFile(require.resolve('./import_geojson'));
loadTestFile(require.resolve('./layer_errors'));
loadTestFile(require.resolve('./embeddable'));
+ loadTestFile(require.resolve('./discover'));
});
});
}