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 @@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
import uuid from 'uuid/v4';
import _ from 'lodash';
import $ from 'jquery';
import rison from 'rison-node';
Expand All @@ -25,6 +26,7 @@ import './discover_field_search_directive';
import './discover_index_pattern_directive';
import fieldChooserTemplate from './field_chooser.html';
import { IndexPatternFieldList } from '../../../../../../../../plugins/data/public';
import { getServices } from '../../../kibana_services';

export function createFieldChooserDirective($location, config, $route) {
return {
Expand Down Expand Up @@ -181,11 +183,73 @@ export function createFieldChooserDirective($location, config, $route) {
$scope.indexPattern.popularizeField(fieldName, 1);
};

function getMapsAppUrl() {
const services = getServices();
const mapsAppVisAlias = services.visualizations.types.getAliases().find(({ name }) => {
return name === 'maps';
});
return mapsAppVisAlias ? mapsAppVisAlias.aliasUrl : null;
}

function isFieldVisualizable(field) {
const mapsAppUrl = getMapsAppUrl();
if (mapsAppUrl && (field.type === 'geo_point' || field.type === 'geo_shape')) {
return true;
}

return field.visualizable;
}

function getVisualizeUrl(field) {
if (!$scope.state) {
return '';
}

const mapsAppUrl = getMapsAppUrl();
if (mapsAppUrl && (field.type === 'geo_point' || field.type === 'geo_shape')) {
nreese marked this conversation as resolved.
Show resolved Hide resolved
const mapAppParams = new URLSearchParams();

// Copy global state
const locationSplit = window.location.href.split('discover?');
if (locationSplit.length > 1) {
const discoverParams = new URLSearchParams(locationSplit[1]);
mapAppParams.set('_g', discoverParams.get('_g'));
kertal marked this conversation as resolved.
Show resolved Hide resolved
}

// Copy filters and query in app state
const mapsAppState = {
filters: $scope.state.filters || [],
};
if ($scope.state.query) {
mapsAppState.query = $scope.state.query;
}
mapAppParams.set('_a', rison.encode(mapsAppState));

// create initial layer descriptor
const hasColumns =
$scope.columns && $scope.columns.length && $scope.columns[0] !== '_source';
mapAppParams.set(
'initialLayers',
rison.encode([
{
id: uuid(),
label: $scope.indexPattern.title,
sourceDescriptor: {
id: uuid(),
type: 'ES_SEARCH',
geoField: field.name,
tooltipProperties: hasColumns ? $scope.columns : [],
indexPatternId: $scope.indexPattern.id,
},
visible: true,
type: 'VECTOR',
},
])
);

return getServices().addBasePath(`${mapsAppUrl}?${mapAppParams.toString()}`);
}

let agg = {};
const isGeoPoint = field.type === 'geo_point';
const type = isGeoPoint ? 'tile_map' : 'histogram';
Expand Down Expand Up @@ -243,7 +307,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
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(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'));
});
});
}