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] POC adding experimental vector drawing layer functionality to the Maps app #96365

Closed
wants to merge 26 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
5f8aa18
Placeholder feature edit control in place
Mar 26, 2021
2cbd6b0
Merge remote-tracking branch 'upstream/master' into shape-drawing-ui
Mar 31, 2021
a80038a
Placeholder menu added
Mar 31, 2021
26ec7bf
Redux integrated
Apr 1, 2021
8ca809a
Single shape drawing
Apr 1, 2021
f9eeb95
Merge remote-tracking branch 'upstream/master' into shape-drawing-ui
Apr 1, 2021
b8eea96
Keep draw mode active
Apr 2, 2021
b4f3af0
Trigger editing through add layer wizard
Apr 5, 2021
001c87f
Add index checking utils
Apr 5, 2021
95f8f14
Connect error checking
Apr 5, 2021
2de30ec
Conditionally show edit toolbar
Apr 5, 2021
92e17aa
Connect missing shapes. Some clean up
Apr 5, 2021
143b24a
Track features and index name in store
Apr 5, 2021
e129539
Connect add layer button enablement
Apr 6, 2021
9c5ca16
Connect persistence utils
Apr 6, 2021
423e3aa
Dispatch clearing action
Apr 6, 2021
f238ccb
Full roundtrip w/ settings showing. Moved async calls to component
Apr 6, 2021
d450fd1
Adjust addLayer call for async
Apr 7, 2021
3d7ad76
Merge remote-tracking branch 'upstream/master' into shape-drawing-ui
Apr 7, 2021
b12ad85
Add edit controls
Apr 7, 2021
576b3d3
Fix deleting functionality to update store
Apr 7, 2021
cd211f9
Add toggle color to buttons. Fall back to edit between drawings
Apr 8, 2021
4f79738
Deselect features still selected when entering trash mode
Apr 8, 2021
ceae6d2
Add geometry check to filter out invalid geometries
Apr 8, 2021
bee0fd5
Clear out old drawing data on cancel. Update action names
Apr 8, 2021
3725859
Add circle drawing button
Apr 8, 2021
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
5 changes: 5 additions & 0 deletions x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ export enum SOURCE_TYPES {
GEOJSON_FILE = 'GEOJSON_FILE',
MVT_SINGLE_LAYER = 'MVT_SINGLE_LAYER',
TABLE_SOURCE = 'TABLE_SOURCE',
NEW_VECTOR_LAYER = 'NEW_VECTOR_LAYER',
}

export enum FIELD_ORIGIN {
Expand Down Expand Up @@ -155,6 +156,10 @@ export enum DRAW_TYPE {
BOUNDS = 'BOUNDS',
DISTANCE = 'DISTANCE',
POLYGON = 'POLYGON',
POINT = 'POINT',
LINE = 'LINE',
SIMPLE_SELECT = 'SIMPLE_SELECT',
TRASH = 'TRASH',
}

export const AGG_DELIMITER = '_of_';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,13 @@ export type TableSourceDescriptor = {
term: string;
};

export type NewVectorLayerSourceDescriptor = {
__fields?: InlineFieldDescriptor[];
__featureCollection: FeatureCollection;
areResultsTrimmed: boolean;
tooltipContent: string | null;
name: string;
type: string;
};

export type TermJoinSourceDescriptor = ESTermSourceDescriptor | TableSourceDescriptor;
1 change: 1 addition & 0 deletions x-pack/plugins/maps/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

export interface CreateDocSourceResp {
indexPatternId?: string;
success: boolean;
error?: Error;
}
Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugins/maps/public/actions/layer_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
UPDATE_LAYER_PROP,
UPDATE_LAYER_STYLE,
UPDATE_SOURCE_PROP,
CLEAR_DRAWING_DATA,
} from './map_action_constants';
import { clearDataRequests, syncDataForLayerId, updateStyleMeta } from './data_request_actions';
import { cleanTooltipStateForLayer } from './tooltip_actions';
Expand All @@ -48,6 +49,11 @@ import { IVectorStyle } from '../classes/styles/vector/vector_style';
import { notifyLicensedFeatureUsage } from '../licensed_features';
import { IESAggField } from '../classes/fields/agg';
import { IField } from '../classes/fields/field';
// @ts-ignore
import {
createNewIndexAndPattern,
addFeatureToIndex,
} from '../classes/layers/new_vector_layer_wizard/utils/indexing_service';

export function trackCurrentLayerState(layerId: string) {
return {
Expand Down Expand Up @@ -548,3 +554,9 @@ export function setAreTilesLoaded(layerId: string, areTilesLoaded: boolean) {
newValue: areTilesLoaded,
};
}

export function clearDrawingData() {
return {
type: CLEAR_DRAWING_DATA,
};
}
6 changes: 6 additions & 0 deletions x-pack/plugins/maps/public/actions/map_action_constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ export const ROLLBACK_TO_TRACKED_LAYER_STATE = 'ROLLBACK_TO_TRACKED_LAYER_STATE'
export const REMOVE_TRACKED_LAYER_STATE = 'REMOVE_TRACKED_LAYER_STATE';
export const SET_OPEN_TOOLTIPS = 'SET_OPEN_TOOLTIPS';
export const UPDATE_DRAW_STATE = 'UPDATE_DRAW_STATE';
export const UPDATE_EDIT_MODE = 'UPDATE_EDIT_MODE';
export const UPDATE_DRAW_FEATURE_STATE = 'UPDATE_DRAW_FEATURE_STATE';
export const SET_SCROLL_ZOOM = 'SET_SCROLL_ZOOM';
export const SET_MAP_INIT_ERROR = 'SET_MAP_INIT_ERROR';
export const SET_WAITING_FOR_READY_HIDDEN_LAYERS = 'SET_WAITING_FOR_READY_HIDDEN_LAYERS';
export const SET_MAP_SETTINGS = 'SET_MAP_SETTINGS';
export const ROLLBACK_MAP_SETTINGS = 'ROLLBACK_MAP_SETTINGS';
export const TRACK_MAP_SETTINGS = 'TRACK_MAP_SETTINGS';
export const UPDATE_MAP_SETTING = 'UPDATE_MAP_SETTING';
export const ADD_FEATURES_TO_INDEX_QUEUE = 'ADD_FEATURES_TO_INDEX_QUEUE';
export const REMOVE_FEATURES_FROM_INDEX_QUEUE = 'REMOVE_FEATURES_FROM_INDEX_QUEUE';
export const SET_VECTOR_LAYER_INDEX_NAME = 'SET_VECTOR_LAYER_INDEX_NAME';
export const CLEAR_DRAWING_DATA = 'CLEAR_DRAWING_DATA';
48 changes: 47 additions & 1 deletion x-pack/plugins/maps/public/actions/map_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
*/

import _ from 'lodash';
import { Feature } from 'geojson';
import { AnyAction, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
import turfBboxPolygon from '@turf/bbox-polygon';
import turfBooleanContains from '@turf/boolean-contains';

import { Filter, Query, TimeRange } from 'src/plugins/data/public';
import { DRAW_TYPE } from '../../common/constants';
import { MapStoreState } from '../reducers/store';
import {
getDataFilters,
Expand Down Expand Up @@ -43,7 +44,12 @@ import {
TRACK_MAP_SETTINGS,
TRIGGER_REFRESH_TIMER,
UPDATE_DRAW_STATE,
UPDATE_DRAW_FEATURE_STATE,
UPDATE_EDIT_MODE,
UPDATE_MAP_SETTING,
ADD_FEATURES_TO_INDEX_QUEUE,
SET_VECTOR_LAYER_INDEX_NAME,
REMOVE_FEATURES_FROM_INDEX_QUEUE,
} from './map_action_constants';
import { autoFitToBounds, syncDataForAllLayers } from './data_request_actions';
import { addLayer, addLayerWithoutDataSync } from './layer_actions';
Expand Down Expand Up @@ -323,3 +329,43 @@ export function updateDrawState(drawState: DrawState | null) {
});
};
}

export function updateDrawFeatureState(drawFeatureState: DRAW_TYPE | null) {
return (dispatch: Dispatch) => {
if (drawFeatureState !== null) {
dispatch({ type: SET_OPEN_TOOLTIPS, openTooltips: [] });
}
dispatch({
type: UPDATE_DRAW_FEATURE_STATE,
drawFeatureState,
});
};
}

export function updateEditMode(isActive: boolean) {
return {
type: UPDATE_EDIT_MODE,
isActive,
};
}

export function addFeaturesToIndexQueue(features: Feature[]) {
return {
type: ADD_FEATURES_TO_INDEX_QUEUE,
features,
};
}

export function removeFeaturesFromIndexQueue(featureIds: string[]) {
return {
type: REMOVE_FEATURES_FROM_INDEX_QUEUE,
featureIds,
};
}

export function setVectorLayerIndexName(indexName: string) {
return {
type: SET_VECTOR_LAYER_INDEX_NAME,
indexName,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { mvtVectorSourceWizardConfig } from '../sources/mvt_single_layer_vector_
import { ObservabilityLayerWizardConfig } from './solution_layers/observability';
import { SecurityLayerWizardConfig } from './solution_layers/security';
import { choroplethLayerWizardConfig } from './choropleth_layer_wizard';
import { newVectorLayerWizardConfig } from './new_vector_layer_wizard/config';

let registered = false;
export function registerLayerWizards() {
Expand All @@ -39,6 +40,7 @@ export function registerLayerWizards() {

// Registration order determines display order
registerLayerWizard(uploadLayerWizardConfig);
registerLayerWizard(newVectorLayerWizardConfig);
registerLayerWizard(esDocumentsLayerWizardConfig);
// @ts-ignore
registerLayerWizard(choroplethLayerWizardConfig);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import React from 'react';
import { LayerWizard, RenderWizardArguments } from '../../layers/layer_wizard_registry';
import { NewVectorLayerEditor } from './index';
import { DocumentsLayerIcon } from '../../layers/icons/documents_layer_icon';
import { CREATE_DRAWN_FEATURES_INDEX_STEP_ID, ADD_DRAWN_FEATURES_TO_INDEX_STEP_ID } from './wizard';

export const newVectorLayerWizardConfig: LayerWizard = {
categories: [],
description: i18n.translate('xpack.maps.newVectorLayerWizard.description', {
defaultMessage: 'Draw points & shapes and save in Elasticsearch',
}),
icon: DocumentsLayerIcon,
prerequisiteSteps: [
{
id: CREATE_DRAWN_FEATURES_INDEX_STEP_ID,
label: i18n.translate('xpack.maps.newVectorLayerWizard.indexFeatures', {
defaultMessage: 'Index features',
}),
},
{
id: ADD_DRAWN_FEATURES_TO_INDEX_STEP_ID,
label: i18n.translate('xpack.maps.newVectorLayerWizard.indexingFeatures', {
defaultMessage: 'Indexing features',
}),
},
],
renderWizard: (renderWizardArguments: RenderWizardArguments) => {
return <NewVectorLayerEditor {...renderWizardArguments} />;
},
title: i18n.translate('xpack.maps.newVectorLayerWizard.title', {
defaultMessage: 'Draw new vector layer',
}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { ThunkDispatch } from 'redux-thunk';
import { AnyAction } from 'redux';
import { connect } from 'react-redux';
import { MapStoreState } from '../../../reducers/store';
import { NewVectorLayerEditor } from './wizard';
import {
addLayer,
clearDrawingData,
setSelectedLayer,
setVectorLayerIndexName,
updateEditMode,
updateFlyout,
} from '../../../actions';
import { FLYOUT_STATE } from '../../../reducers/ui';
import { LayerDescriptor } from '../../../../common/descriptor_types';

function mapStateToProps(state: MapStoreState) {
return {
indexName: state.map.mapState.vectorLayerIndexName,
featuresQueued: state.map.mapState.featuresToIndexQueue,
};
}

function mapDispatchToProps(dispatch: ThunkDispatch<MapStoreState, void, AnyAction>) {
return {
setEditModeActive: () => dispatch(updateEditMode(true)),
setEditModeInActive: () => {
dispatch(updateEditMode(false))
dispatch(clearDrawingData());
},
setIndexName: (indexName: string) => dispatch(setVectorLayerIndexName(indexName)),
clearDrawingData: () => {
dispatch(clearDrawingData());
},
addNewLayer: async (layerDescriptor: LayerDescriptor) => {
await dispatch(addLayer(layerDescriptor));
await dispatch(setSelectedLayer(layerDescriptor.id));
dispatch(updateFlyout(FLYOUT_STATE.LAYER_PANEL));
},
};
}

const connected = connect(mapStateToProps, mapDispatchToProps)(NewVectorLayerEditor);
export { connected as NewVectorLayerEditor };
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { i18n } from '@kbn/i18n';
import { getHttp } from '../../../../kibana_services';

export async function http(options) {
if (!(options && options.url)) {
throw i18n.translate('xpack.layers.newVectorLayerWizard.httpService.noUrl', {
defaultMessage: 'No URL provided',
});
}
const url = options.url || '';
const headers = {
'Content-Type': 'application/json',
...options.headers,
};

const allHeaders = options.headers === undefined ? headers : { ...options.headers, ...headers };
const body = options.data === undefined ? null : JSON.stringify(options.data);

const payload = {
method: options.method || 'GET',
headers: allHeaders,
credentials: 'same-origin',
query: options.query,
};

if (body !== null) {
payload.body = body;
}
return await doFetch(url, payload);
}

async function doFetch(url, payload) {
try {
return await getHttp().fetch(url, payload);
} catch (err) {
return {
failures: [
i18n.translate('xpack.layers.newVectorLayerWizard.httpService.fetchError', {
defaultMessage: 'Error performing fetch: {error}',
values: { error: err.message },
}),
],
};
}
}
Original file line number Diff line number Diff line change
@@ -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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { http as httpService } from './http_service';
import { getSavedObjectsClient } from '../../../../kibana_services';

export const getExistingIndexNames = async () => {
const indexes = await httpService({
url: `/api/index_management/indices`,
method: 'GET',
});
return indexes ? indexes.map(({ name }) => name) : [];
};

export const createNewIndexAndPattern = async (indexName) => {
return await httpService({
url: `/api/maps/docSource`,
method: 'POST',
data: {
index: indexName,
// Initially set to static mappings
mappings: {
properties: {
coordinates: {
type: 'geo_shape',
},
},
},
},
});
};

export const addFeatureToIndex = async (indexName, geometry) => {
return await httpService({
url: `/api/maps/feature`,
method: 'POST',
data: {
index: indexName,
data: {
coordinates: geometry,
},
},
});
};

export const getExistingIndexPatternNames = async () => {
const indexPatterns = await getSavedObjectsClient()
.find({
type: 'index-pattern',
fields: ['id', 'title', 'type', 'fields'],
perPage: 10000,
})
.then(({ savedObjects }) => savedObjects.map((savedObject) => savedObject.get('title')));
return indexPatterns ? indexPatterns.map(({ name }) => name) : [];
};

export function checkIndexPatternValid(name) {
const byteLength = encodeURI(name).split(/%(?:u[0-9A-F]{2})?[0-9A-F]{2}|./).length - 1;
const reg = new RegExp('[\\\\/*?"<>|\\s,#]+');
const indexPatternInvalid =
byteLength > 255 || // name can't be greater than 255 bytes
name !== name.toLowerCase() || // name should be lowercase
name === '.' ||
name === '..' || // name can't be . or ..
name.match(/^[-_+]/) !== null || // name can't start with these chars
name.match(reg) !== null; // name can't contain these chars
return !indexPatternInvalid;
}
Loading