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] Map embeddable #31473

Merged
merged 30 commits into from
Mar 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9c63c4e
[Maps] embeddable
nreese Feb 11, 2019
6cf4765
Merge branch 'master' of github.com:elastic/kibana into map_embeddable
nreese Feb 11, 2019
3dd49cf
fix embeddable registration
nreese Feb 11, 2019
f59ebeb
get embeddable to render
nreese Feb 11, 2019
1543078
Merge branch 'master' of github.com:elastic/kibana into map_embeddable
nreese Feb 12, 2019
d071b83
merge with master
nreese Feb 19, 2019
d7f5725
render map embeddable
nreese Feb 19, 2019
b9bdd63
support filter pills on dashboard
nreese Feb 19, 2019
9e0048d
fix layerName error
nreese Feb 19, 2019
eb1bb15
update getBoundsForFilters to not record bounds request in inspector
nreese Feb 19, 2019
d192030
merge with master
nreese Feb 20, 2019
5e5d0ae
set isReadOnly
nreese Feb 20, 2019
4faa8d4
merge with master
nreese Feb 26, 2019
2173671
fix bug where different states are sharing same inspectorAdapters ins…
nreese Feb 26, 2019
d32242d
populate indexPatterns so container has index patterns for type ahead…
nreese Feb 26, 2019
5953eeb
pass refreshConfig from container to map store
nreese Feb 26, 2019
2909f76
add functional test verifying index patterns passed to container
nreese Feb 26, 2019
00eb86f
remove data.json files commited by mistake
nreese Feb 26, 2019
6b86ce2
add functional test ensuring inspector adaptors are not shared betwee…
nreese Feb 26, 2019
3f8e72b
add functional tests for filters getting passed to embeddable
nreese Feb 26, 2019
51c0f0a
remove embeddable dashboard from web logs sample data saved objects
nreese Feb 26, 2019
c717ba3
Merge branch 'master' of github.com:elastic/kibana into map_embeddable
nreese Feb 28, 2019
0bd4a3a
add slash to edit path
nreese Feb 28, 2019
32842b3
fix typo
nreese Feb 28, 2019
423aba6
Merge branch 'master' of github.com:elastic/kibana into map_embeddable
nreese Mar 1, 2019
27575c7
pass previous query state on reload, add functional tests to verify r…
nreese Mar 1, 2019
de0e371
remove unnecessary check in getFilters
nreese Mar 1, 2019
1963877
review feedback from thomasneirynck
nreese Mar 1, 2019
0f934a9
fix resize bug
nreese Mar 1, 2019
ed3c20e
remove animationFrame from resize callback
nreese Mar 1, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class RequestSelector extends Component {
>
<EuiContextMenuPanel
items={this.props.requests.map(this.renderRequestDropdownItem)}
data-test-subj="inspectorRequestChooserMenuPanel"
/>
</EuiPopover>
);
Expand All @@ -140,7 +141,7 @@ class RequestSelector extends Component {
</EuiFlexItem>
<EuiFlexItem grow={true}>
{requests.length <= 1 &&
<div className="insRequestSelector__singleRequest">
<div className="insRequestSelector__singleRequest" data-test-subj="inspectorRequestName">
{selectedRequest.name}
</div>
}
Expand Down
1 change: 1 addition & 0 deletions src/legacy/ui/public/filter_bar/filter_editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ class FilterEditorUI extends Component<Props, State> {
onChange={this.onIndexPatternChange}
singleSelection={{ asPlainText: true }}
isClearable={false}
data-test-subj="filterIndexPatternsSelect"
/>
</EuiFormRow>
</EuiFlexItem>
Expand Down
5 changes: 5 additions & 0 deletions test/functional/services/dashboard/panel_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }) {
await testSubjects.click(CUSTOMIZE_PANEL_DATA_TEST_SUBJ);
}

async openInspectorByTitle(title) {
const header = await this.getPanelHeading(title);
await this.openInspector(header);
}

async openInspector(parent) {
await this.openContextMenu(parent);
await testSubjects.click(OPEN_INSPECTOR_TEST_SUBJ);
Expand Down
12 changes: 12 additions & 0 deletions test/functional/services/filter_bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ export function FilterBarProvider({ getService, getPageObjects }) {
await testSubjects.click('cancelSaveFilter');
}
}

async getIndexPatterns() {
await testSubjects.click('addFilter');
const indexPatterns = await comboBox.getOptionsList('filterIndexPatternsSelect');
await this.ensureFieldEditorModalIsClosed();
return indexPatterns.trim().split('\n').join(',');
}

async selectIndexPattern(indexPatternTitle) {
await testSubjects.click('addFilter');
await comboBox.set('filterIndexPatternsSelect', indexPatternTitle);
}
}

return new FilterBar();
Expand Down
24 changes: 24 additions & 0 deletions test/functional/services/inspector.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,29 @@ export function InspectorProvider({ getService }) {
});
await renderable.waitForRender();
}

async openInspectorView(viewId) {
log.debug(`Open Inspector view ${viewId}`);
await testSubjects.click('inspectorViewChooser');
await testSubjects.click(viewId);
}

async openInspectorRequestsView() {
await this.openInspectorView('inspectorViewChooserRequests');
}

async getRequestNames() {
await this.openInspectorRequestsView();
const requestChooserExists = await testSubjects.exists('inspectorRequestChooser');
if (requestChooserExists) {
await testSubjects.click('inspectorRequestChooser');
const menu = await testSubjects.find('inspectorRequestChooserMenuPanel');
const requestNames = await menu.getVisibleText();
return requestNames.trim().split('\n').join(',');
}

const singleRequest = await testSubjects.find('inspectorRequestName');
return await singleRequest.getVisibleText();
}
};
}
3 changes: 3 additions & 0 deletions x-pack/plugins/maps/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export function maps(kibana) {
isEmsEnabled: mapConfig.includeElasticMapsService,
};
},
embeddableFactories: [
'plugins/maps/embeddable/map_embeddable_factory_provider'
],
inspectorViews: [
'plugins/maps/inspector/views/register_views',
],
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/maps/public/actions/store_actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ export function removeLayer(id) {
};
}

export function setQuery({ query, timeFilters }) {
export function setQuery({ query, timeFilters, filters = [] }) {
nreese marked this conversation as resolved.
Show resolved Hide resolved
return async (dispatch, getState) => {
dispatch({
type: SET_QUERY,
Expand All @@ -489,6 +489,7 @@ export function setQuery({ query, timeFilters }) {
// ensure query changes to trigger re-fetch even when query is the same because "Refresh" clicked
queryLastTriggeredAt: (new Date()).toISOString(),
},
filters,
});

const dataFilters = getDataFilters(getState());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,9 @@
import './saved_gis_map';
import { uiModules } from 'ui/modules';
import { SavedObjectLoader, SavedObjectsClientProvider } from 'ui/saved_objects';
import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry';

const module = uiModules.get('app/maps');

// Register this service with the saved object registry so it can be
// edited by the object editor.
SavedObjectRegistryProvider.register({
service: 'gisMapSavedObjectLoader',
title: 'gisMaps'
});
nreese marked this conversation as resolved.
Show resolved Hide resolved

// This is the only thing that gets injected into controllers
module.service('gisMapSavedObjectLoader', function (Private, SavedGisMap, kbnIndex, kbnUrl, $http, chrome) {
const savedObjectClient = Private(SavedObjectsClientProvider);
Expand Down
9 changes: 9 additions & 0 deletions x-pack/plugins/maps/public/angular/services/saved_gis_map.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ module.factory('SavedGisMap', function (Private) {
});

savedObject.layerListJSON = attributes.layerListJSON;

const indexPatternIds = references
.filter(reference => {
return reference.type === 'index-pattern';
})
.map(reference => {
return reference.id;
});
savedObject.indexPatternIds = _.uniq(indexPatternIds);
},

// if this is null/undefined then the SavedObject will be assigned the defaults
Expand Down
20 changes: 5 additions & 15 deletions x-pack/plugins/maps/public/components/map/mb/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,7 @@ export class MBMapContainer extends React.Component {
originalMbBoxRemoveLayerFunc.apply(this._mbMap, [id]);
};

this.assignSizeWatch();

this._initResizerChecker();

// moveend callback is debounced to avoid updating map extent state while map extent is still changing
// moveend is fired while the map extent is still changing in the following scenarios
Expand Down Expand Up @@ -149,20 +148,11 @@ export class MBMapContainer extends React.Component {
}
}

assignSizeWatch() {
_initResizerChecker() {
this._checker = new ResizeChecker(this.refs.mapContainer);
this._checker.on('resize', (() => {
let lastWidth = window.innerWidth;
let lastHeight = window.innerHeight;
return () => {
if (lastWidth === window.innerWidth
&& lastHeight === window.innerHeight && this._mbMap) {
this._mbMap.resize();
}
lastWidth = window.innerWidth;
lastHeight = window.innerHeight;
};
})());
this._checker.on('resize', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice, this is more clean too.

I don't think we need to wrap this in an animation-frame callback. It assumes we'd get resize events at a rate exceeding 60fps, and that mapbox would not be optimizing for map.resize() method calls internally. It'd suprise me if both were true. I think we should be fine just calling resize on each event.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thomasneirynck I have removed the animation frame. Looks like EUI has a ResizeObserver that we could use instead of ui/public/resize_checker. The problem is that ResizeObserver was only added in EUI 7.2.0 and Kibana is currently on EUI 7.1.0. I will make a separate PR to clean this up more and replace resize_checker (which is based on angular) with ResizeObserver once Kibana EUI has been upgraded.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, I can just revert the changes to this file and the resize issue can be completely addressed in a separate PR since resize_checker is going to be replaced anyways.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's good to fix it here imho

OK with new PR where we migrate to eui's ResizeObserver later

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was some reason we needed this previously but I'm comfortable with getting rid of it and letting it re-emerge if necessary. Really might not be needed anymore.

this._mbMap.resize();
});
}

_syncMbMapWithMapState = () => {
Expand Down
114 changes: 114 additions & 0 deletions x-pack/plugins/maps/public/embeddable/map_embeddable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* 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 _ from 'lodash';
import React from 'react';
import { Provider } from 'react-redux';
import { render, unmountComponentAtNode } from 'react-dom';
import 'mapbox-gl/dist/mapbox-gl.css';

import { Embeddable } from 'ui/embeddable';
import { I18nContext } from 'ui/i18n';

import { GisMap } from '../components/gis_map';
import { createMapStore } from '../store/store';
import { getInitialLayers } from '../angular/get_initial_layers';
import {
setGotoWithCenter,
replaceLayerList,
setQuery,
setRefreshConfig,
} from '../actions/store_actions';
import { setReadOnly } from '../store/ui';
import { getInspectorAdapters } from '../store/non_serializable_instances';

export class MapEmbeddable extends Embeddable {

constructor({ savedMap, editUrl, indexPatterns = [] }) {
super({ title: savedMap.title, editUrl, indexPatterns });

this._savedMap = savedMap;
this._store = createMapStore();
}

getInspectorAdapters() {
return getInspectorAdapters(this._store.getState());
}

onContainerStateChanged(containerState) {
if (!_.isEqual(containerState.timeRange, this._prevTimeRange) ||
!_.isEqual(containerState.query, this._prevQuery) ||
!_.isEqual(containerState.filters, this._prevFilters)) {
this._dispatchSetQuery(containerState);
}

if (!_.isEqual(containerState.refreshConfig, this._prevRefreshConfig)) {
this._dispatchSetRefreshConfig(containerState);
}
}

_dispatchSetQuery({ query, timeRange, filters }) {
this._prevTimeRange = timeRange;
this._prevQuery = query;
this._prevFilters = filters;
this._store.dispatch(setQuery({
filters: filters.filter(filter => !filter.meta.disabled),
query,
timeFilters: timeRange,
}));
}

_dispatchSetRefreshConfig({ refreshConfig }) {
this._prevRefreshConfig = refreshConfig;
this._store.dispatch(setRefreshConfig(refreshConfig));
}

/**
*
* @param {HTMLElement} domNode
* @param {ContainerState} containerState
*/
render(domNode, containerState) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if we should monitor this domNode for a resize (see other comment).

this._store.dispatch(setReadOnly(true));
// todo get center and zoom from embeddable UI state
if (this._savedMap.mapStateJSON) {
const mapState = JSON.parse(this._savedMap.mapStateJSON);
this._store.dispatch(setGotoWithCenter({
lat: mapState.center.lat,
lon: mapState.center.lon,
zoom: mapState.zoom,
}));
}
const layerList = getInitialLayers(this._savedMap.layerListJSON);
this._store.dispatch(replaceLayerList(layerList));
this._dispatchSetQuery(containerState);
this._dispatchSetRefreshConfig(containerState);

render(
nreese marked this conversation as resolved.
Show resolved Hide resolved
<Provider store={this._store}>
<I18nContext>
<GisMap/>
</I18nContext>
</Provider>,
domNode
);
}

destroy() {
this._savedMap.destroy();
if (this._domNode) {
unmountComponentAtNode(this._domNode);
}
}

reload() {
this._dispatchSetQuery({
query: this._prevQuery,
timeRange: this._prevTimeRange,
filters: this._prevFilters
});
}
}
45 changes: 45 additions & 0 deletions x-pack/plugins/maps/public/embeddable/map_embeddable_factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 _ from 'lodash';
import chrome from 'ui/chrome';
import { EmbeddableFactory } from 'ui/embeddable';
import { MapEmbeddable } from './map_embeddable';
import { indexPatternService } from '../kibana_services';

export class MapEmbeddableFactory extends EmbeddableFactory {

constructor(gisMapSavedObjectLoader) {
super({ name: 'map' });
this._savedObjectLoader = gisMapSavedObjectLoader;
}

async _getIndexPatterns(indexPatternIds = []) {
const promises = indexPatternIds.map(async (indexPatternId) => {
try {
return await indexPatternService.get(indexPatternId);
} catch (error) {
// Unable to load index pattern, better to not throw error so map embeddable can render
// Error will be surfaced by map embeddable since it too will be unable to locate the index pattern
return null;
}
});
const indexPatterns = await Promise.all(promises);
return _.compact(indexPatterns);
}

async create(panelMetadata, onEmbeddableStateChanged) {
const savedMap = await this._savedObjectLoader.get(panelMetadata.id);
const indexPatterns = await this._getIndexPatterns(savedMap.indexPatternIds);

return new MapEmbeddable({
onEmbeddableStateChanged,
savedMap,
editUrl: chrome.addBasePath(`/app/maps#/map/${panelMetadata.id}`),
indexPatterns,
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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 { EmbeddableFactoriesRegistryProvider } from 'ui/embeddable/embeddable_factories_registry';
import { MapEmbeddableFactory } from './map_embeddable_factory';
import '../angular/services/gis_map_saved_object_loader';

function mapEmbeddableFactoryProvider(gisMapSavedObjectLoader) {
return new MapEmbeddableFactory(gisMapSavedObjectLoader);
}

EmbeddableFactoriesRegistryProvider.register(mapEmbeddableFactoryProvider);
1 change: 1 addition & 0 deletions x-pack/plugins/maps/public/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'uiExports/autocompleteProviders';
import 'uiExports/fieldFormats';
import 'uiExports/inspectorViews';
import 'uiExports/search';
import 'uiExports/embeddableFactories';
import 'ui/agg_types';

import chrome from 'ui/chrome';
Expand Down
Loading