Skip to content

Commit

Permalink
[Maps] Add draw wizard (elastic#100278)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomasneirynck authored and ecezalp committed May 26, 2021
1 parent 0b4b8f4 commit 6b3be2c
Show file tree
Hide file tree
Showing 24 changed files with 483 additions and 48 deletions.
25 changes: 7 additions & 18 deletions x-pack/plugins/file_upload/public/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
* 2.0.
*/

import React from 'react';
import { FileUploadComponentProps, lazyLoadModules } from '../lazy_load_bundle';
import { lazyLoadModules } from '../lazy_load_bundle';
import type { IImporter, ImportFactoryOptions } from '../importer';
import { IndexNameFormProps } from '../';
import type { HasImportPermission, FindFileStructureResponse } from '../../common';
import type { getMaxBytes, getMaxBytesFormatted } from '../importer/get_max_bytes';
import { JsonUploadAndParseAsyncWrapper } from './json_upload_and_parse_async_wrapper';
import { IndexNameFormAsyncWrapper } from './index_name_form_async_wrapper';

export interface FileUploadStartApi {
getFileUploadComponent(): ReturnType<typeof getFileUploadComponent>;
getIndexNameFormComponent(): Promise<React.ComponentType<IndexNameFormProps>>;
FileUploadComponent: typeof JsonUploadAndParseAsyncWrapper;
IndexNameFormComponent: typeof IndexNameFormAsyncWrapper;
importerFactory: typeof importerFactory;
getMaxBytes: typeof getMaxBytes;
getMaxBytesFormatted: typeof getMaxBytesFormatted;
Expand All @@ -30,19 +30,8 @@ export interface GetTimeFieldRangeResponse {
end: { epoch: number; string: string };
}

export async function getFileUploadComponent(): Promise<
React.ComponentType<FileUploadComponentProps>
> {
const fileUploadModules = await lazyLoadModules();
return fileUploadModules.JsonUploadAndParse;
}

export async function getIndexNameFormComponent(): Promise<
React.ComponentType<IndexNameFormProps>
> {
const fileUploadModules = await lazyLoadModules();
return fileUploadModules.IndexNameForm;
}
export const FileUploadComponent = JsonUploadAndParseAsyncWrapper;
export const IndexNameFormComponent = IndexNameFormAsyncWrapper;

export async function importerFactory(
format: string,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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 React from 'react';
import { EuiLoadingContent } from '@elastic/eui';
import { lazyLoadModules } from '../lazy_load_bundle';
import { IndexNameFormProps } from '../index';

interface State {
IndexNameForm: React.ComponentType<IndexNameFormProps> | null;
}

export class IndexNameFormAsyncWrapper extends React.Component<IndexNameFormProps, State> {
state: State = {
IndexNameForm: null,
};

private _isMounted = false;

componentWillUnmount(): void {
this._isMounted = false;
}

componentDidMount() {
this._isMounted = true;
lazyLoadModules().then((modules) => {
if (this._isMounted) {
this.setState({
IndexNameForm: modules.IndexNameForm,
});
}
});
}

render() {
const { IndexNameForm } = this.state;
return IndexNameForm ? <IndexNameForm {...this.props} /> : <EuiLoadingContent lines={3} />;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 React from 'react';
import { EuiLoadingContent } from '@elastic/eui';
import { FileUploadComponentProps, lazyLoadModules } from '../lazy_load_bundle';

interface State {
JsonUploadAndParse: React.ComponentType<FileUploadComponentProps> | null;
}

export class JsonUploadAndParseAsyncWrapper extends React.Component<
FileUploadComponentProps,
State
> {
state: State = {
JsonUploadAndParse: null,
};
private _isMounted = false;

componentDidMount() {
this._isMounted = true;
lazyLoadModules().then((modules) => {
if (this._isMounted) {
this.setState({
JsonUploadAndParse: modules.JsonUploadAndParse,
});
}
});
}

componentWillUnmount(): void {
this._isMounted = false;
}

render() {
const { JsonUploadAndParse } = this.state;
return JsonUploadAndParse ? (
<JsonUploadAndParse {...this.props} />
) : (
<EuiLoadingContent lines={3} />
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface FileUploadComponentProps {

let loadModulesPromise: Promise<LazyLoadedFileUploadModules>;

interface LazyLoadedFileUploadModules {
export interface LazyLoadedFileUploadModules {
JsonUploadAndParse: React.ComponentType<FileUploadComponentProps>;
IndexNameForm: React.ComponentType<IndexNameFormProps>;
importerFactory: (format: string, options: ImportFactoryOptions) => IImporter | undefined;
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/file_upload/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
import { CoreStart, Plugin } from '../../../../src/core/public';
import {
FileUploadStartApi,
getFileUploadComponent,
FileUploadComponent,
importerFactory,
hasImportPermission,
getIndexNameFormComponent,
IndexNameFormComponent,
checkIndexExists,
getTimeFieldRange,
analyzeFile,
Expand Down Expand Up @@ -42,8 +42,8 @@ export class FileUploadPlugin
public start(core: CoreStart, plugins: FileUploadStartDependencies): FileUploadStartApi {
setStartServices(core, plugins);
return {
getFileUploadComponent,
getIndexNameFormComponent,
FileUploadComponent,
IndexNameFormComponent,
importerFactory,
getMaxBytes,
getMaxBytesFormatted,
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/maps/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const GIS_API_PATH = `api/${APP_ID}`;
export const INDEX_SETTINGS_API_PATH = `${GIS_API_PATH}/indexSettings`;
export const FONTS_API_PATH = `${GIS_API_PATH}/fonts`;
export const INDEX_SOURCE_API_PATH = `${GIS_API_PATH}/docSource`;
export const INDEX_FEATURE_PATH = `/${GIS_API_PATH}/feature`;
export const API_ROOT_PATH = `/${GIS_API_PATH}`;

export const MVT_GETTILE_API_PATH = 'mvt/getTile';
Expand Down
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
1 change: 0 additions & 1 deletion x-pack/plugins/maps/public/actions/map_actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ 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 { MapStoreState } from '../reducers/store';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ import React, { Component } from 'react';
import { FeatureCollection } from 'geojson';
import { EuiPanel } from '@elastic/eui';
import { DEFAULT_MAX_RESULT_WINDOW, SCALING_TYPES } from '../../../../common/constants';
import { getFileUpload } from '../../../kibana_services';
import { GeoJsonFileSource } from '../../sources/geojson_file_source';
import { VectorLayer } from '../../layers/vector_layer';
import { createDefaultLayerDescriptor } from '../../sources/es_search_source';
import { RenderWizardArguments } from '../../layers/layer_wizard_registry';
import { FileUploadComponentProps, FileUploadGeoResults } from '../../../../../file_upload/public';
import { FileUploadGeoResults } from '../../../../../file_upload/public';
import { ES_FIELD_TYPES } from '../../../../../../../src/plugins/data/public';
import { getFileUploadComponent } from '../../../kibana_services';

export enum UPLOAD_STEPS {
CONFIGURE_UPLOAD = 'CONFIGURE_UPLOAD',
Expand All @@ -34,7 +34,6 @@ enum INDEXING_STAGE {

interface State {
indexingStage: INDEXING_STAGE;
fileUploadComponent: React.ComponentType<FileUploadComponentProps> | null;
results?: FileUploadGeoResults;
}

Expand All @@ -43,12 +42,10 @@ export class ClientFileCreateSourceEditor extends Component<RenderWizardArgument

state: State = {
indexingStage: INDEXING_STAGE.CONFIGURE,
fileUploadComponent: null,
};

componentDidMount() {
this._isMounted = true;
this._loadFileUploadComponent();
}

componentWillUnmount() {
Expand Down Expand Up @@ -91,13 +88,6 @@ export class ClientFileCreateSourceEditor extends Component<RenderWizardArgument
this.props.advanceToNextStep();
});

async _loadFileUploadComponent() {
const fileUploadComponent = await getFileUpload().getFileUploadComponent();
if (this._isMounted) {
this.setState({ fileUploadComponent });
}
}

_onFileSelect = (geojsonFile: FeatureCollection, name: string, previewCoverage: number) => {
if (!this._isMounted) {
return;
Expand Down Expand Up @@ -157,11 +147,8 @@ export class ClientFileCreateSourceEditor extends Component<RenderWizardArgument
};

render() {
if (!this.state.fileUploadComponent) {
return null;
}
const FileUpload = getFileUploadComponent();

const FileUpload = this.state.fileUploadComponent;
return (
<EuiPanel>
<FileUpload
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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 React, { FunctionComponent } from 'react';

export const DrawLayerIcon: FunctionComponent = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="49"
height="25"
fill="none"
viewBox="0 0 49 25"
className="mapLayersWizardIcon"
>
<path
className="mapLayersWizardIcon__background"
d="M12.281 3l-6.625 7.625 1.657 8.938 35.218-.813v-13l-10.625-3.5-9.781 9.5L12.281 3z"
/>
<path
className="mapLayersWizardIcon__highlight"
fillRule="evenodd"
d="M31.775 1.68l11.256 3.708v13.85l-36.133.834-1.777-9.593 7.114-8.189 9.875 8.778 9.665-9.388zm.262 1.14l-9.897 9.612-9.813-8.722-6.135 7.06 1.535 8.283 34.304-.792V6.111L32.037 2.82z"
clipRule="evenodd"
/>
<circle cx="7.281" cy="19.5" r="2.5" className="mapLayersWizardIcon__highlight" />
<circle cx="5.656" cy="10.25" r="2.5" className="mapLayersWizardIcon__highlight" />
<circle cx="12.156" cy="3.625" r="2.5" className="mapLayersWizardIcon__highlight" />
<circle cx="22" cy="11.6" r="2.5" className="mapLayersWizardIcon__highlight" />
<circle cx="31.969" cy="2.5" r="2.5" className="mapLayersWizardIcon__highlight" />
<circle cx="42.344" cy="6.125" r="2.5" className="mapLayersWizardIcon__highlight" />
<circle cx="42.344" cy="19" r="2.5" className="mapLayersWizardIcon__highlight" />
</svg>
);
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ 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';
import { getMapAppConfig } from '../../kibana_services';

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

// Registration order determines display order
registerLayerWizard(uploadLayerWizardConfig);
if (getMapAppConfig().enableDrawingFeature) {
registerLayerWizard(newVectorLayerWizardConfig);
}
registerLayerWizard(esDocumentsLayerWizardConfig);
// @ts-ignore
registerLayerWizard(choroplethLayerWizardConfig);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* 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 './wizard';
import { DrawLayerIcon } from '../../layers/icons/draw_layer_icon';
import { getFileUpload } from '../../../kibana_services';
import { LAYER_WIZARD_CATEGORY } from '../../../../common';

const ADD_VECTOR_DRAWING_LAYER = 'ADD_VECTOR_DRAWING_LAYER';

export const newVectorLayerWizardConfig: LayerWizard = {
categories: [LAYER_WIZARD_CATEGORY.ELASTICSEARCH],
description: i18n.translate('xpack.maps.newVectorLayerWizard.description', {
defaultMessage: 'Creates a new empty layer. Use this to add shapes to the map',
}),
disabledReason: i18n.translate('xpack.maps.newVectorLayerWizard.disabledDesc', {
defaultMessage:
'Unable to draw vector shapes, you are missing the Kibana privilege "Index Pattern Management".',
}),
getIsDisabled: async () => {
const hasImportPermission = await getFileUpload().hasImportPermission({
checkCreateIndexPattern: true,
checkHasManagePipeline: false,
});
return !hasImportPermission;
},
icon: DrawLayerIcon,
prerequisiteSteps: [
{
id: ADD_VECTOR_DRAWING_LAYER,
label: i18n.translate('xpack.maps.newVectorLayerWizard.indexNewLayer', {
defaultMessage: 'Index new layer',
}),
},
],
renderWizard: (renderWizardArguments: RenderWizardArguments) => {
return <NewVectorLayerEditor {...renderWizardArguments} />;
},
title: i18n.translate('xpack.maps.newVectorLayerWizard.title', {
defaultMessage: 'Create new layer',
}),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 { getHttp } from '../../../kibana_services';
import { CreateDocSourceResp, INDEX_SOURCE_API_PATH } from '../../../../common';

export const createNewIndexAndPattern = async (indexName: string) => {
return await getHttp().fetch<CreateDocSourceResp>({
path: `/${INDEX_SOURCE_API_PATH}`,
method: 'POST',
body: JSON.stringify({
index: indexName,
// Initially set to static mappings
mappings: {
properties: {
coordinates: {
type: 'geo_shape',
},
},
},
}),
});
};
Loading

0 comments on commit 6b3be2c

Please sign in to comment.