Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Maps] direct Discover "visualize" to open Maps application #58549

Merged
merged 14 commits into from
Mar 2, 2020
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -47,6 +47,7 @@ interface AppState {
* Number of records to be fetched after the anchor records (older records)
*/
successorCount: number;
query?: Query;
}

interface GlobalState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,15 @@
</div>
<string-field-progress-bar
percent="{{bucket.percent}}"
count="{{::bucket.count}}"
count="{{::bucket.count}}"
></string-field-progress-bar>
</div>
</div>
</div>

<a
ng-href="{{field.details.visualizeUrl}}"
ng-show="field.visualizable && canVisualize"
ng-show="field.details.visualizeUrl && canVisualize"
class="kuiButton kuiButton--secondary kuiButton--small kuiVerticalRhythmSmall"
data-test-subj="fieldVisualize-{{::field.name}}"
>
Expand Down
Original file line number Diff line number Diff line change
@@ -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()}`);
}
6 changes: 0 additions & 6 deletions test/functional/page_objects/discover_page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -19,16 +19,16 @@ 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);
if (isEmsEnabled) {
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;
}
29 changes: 28 additions & 1 deletion x-pack/legacy/plugins/maps/public/angular/map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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('?');
kertal marked this conversation as resolved.
Show resolved Hide resolved
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) => {
Expand Down Expand Up @@ -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));
Expand Down
50 changes: 50 additions & 0 deletions x-pack/test/functional/apps/maps/discover.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
}
1 change: 1 addition & 0 deletions x-pack/test/functional/apps/maps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});
});
}