diff --git a/x-pack/legacy/plugins/console_extensions/index.js b/x-pack/legacy/plugins/console_extensions/index.js deleted file mode 100644 index fd1b48f0fd6b1..0000000000000 --- a/x-pack/legacy/plugins/console_extensions/index.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { join } from 'path'; -import { processors } from './spec/ingest'; - -export function consoleExtensions(kibana) { - return new kibana.Plugin({ - id: 'console_extensions', - require: ['kibana', 'console'], - isEnabled(config) { - return ( - config.get('console_extensions.enabled') && - config.has('console.enabled') && - config.get('console.enabled') - ); - }, - - config(Joi) { - return Joi.object({ - enabled: Joi.boolean().default(true), - }).default(); - }, - - init: server => { - if ( - server.plugins.console && - server.plugins.console.addExtensionSpecFilePath && - server.plugins.console.addProcessorDefinition - ) { - const { addExtensionSpecFilePath, addProcessorDefinition } = server.plugins.console; - - addExtensionSpecFilePath(join(__dirname, 'spec/')); - - processors.forEach(processor => addProcessorDefinition(processor)); - } else { - console.warn( - 'Missing server.plugins.console.addExtensionSpecFilePath extension point.', - 'Cannot add xpack APIs to autocomplete.' - ); - } - }, - }); -} diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.ts index b8b67a0f36bd2..dcee956130a29 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/__jest__/client_integration/helpers/mappings_editor.helpers.ts @@ -13,4 +13,4 @@ export const setup = (props: any) => wrapComponent: false, }, defaultProps: props, - }); + })(); diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx index 9e390e785c7d5..723c105d403b8 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/__jest__/client_integration/mappings_editor.test.tsx @@ -28,7 +28,7 @@ describe('', () => { }, }, }; - const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue })(); + const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue }); const { exists } = testBed; expect(exists('mappingsEditor')).toBe(true); @@ -44,7 +44,7 @@ describe('', () => { }, }, }; - const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue })(); + const testBed = await setup({ onUpdate: mockOnUpdate, defaultValue }); const { exists } = testBed; expect(exists('mappingsEditor')).toBe(true); diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx new file mode 100644 index 0000000000000..a9433d3a7530f --- /dev/null +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.test.tsx @@ -0,0 +1,77 @@ +/* + * 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 React from 'react'; +import { act } from 'react-dom/test-utils'; + +jest.mock('@elastic/eui', () => ({ + ...jest.requireActual('@elastic/eui'), + // Mocking EuiCodeEditor, which uses React Ace under the hood + EuiCodeEditor: (props: any) => ( + { + props.onChange(syntheticEvent.jsonString); + }} + /> + ), +})); + +import { registerTestBed, nextTick, TestBed } from '../../../../../../../../../test_utils'; +import { LoadMappingsProvider } from './load_mappings_provider'; + +const ComponentToTest = ({ onJson }: { onJson: () => void }) => ( + + {openModal => ( + + )} + +); + +const setup = (props: any) => + registerTestBed(ComponentToTest, { + memoryRouter: { wrapComponent: false }, + defaultProps: props, + })(); + +const openModalWithJsonContent = ({ find, component }: TestBed) => async (json: any) => { + find('load-json-button').simulate('click'); + component.update(); + + // Set the mappings to load + // @ts-ignore + await act(async () => { + find('mockCodeEditor').simulate('change', { + jsonString: JSON.stringify(json), + }); + await nextTick(300); // There is a debounce in the JsonEditor that we need to wait for + }); +}; + +describe('', () => { + test('it should forward valid mapping definition', async () => { + const mappingsToLoad = { + properties: { + title: { + type: 'text', + }, + }, + }; + + const onJson = jest.fn(); + const testBed = await setup({ onJson }); + + // Open the modal and add the JSON + await openModalWithJsonContent(testBed)(mappingsToLoad); + + // Confirm + testBed.find('confirmModalConfirmButton').simulate('click'); + + const [jsonReturned] = onJson.mock.calls[0]; + expect(jsonReturned).toEqual({ ...mappingsToLoad, dynamic_templates: [] }); + }); +}); diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx index a55bd96dce3d0..6bc360a1ec70e 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/components/load_mappings/load_mappings_provider.tsx @@ -25,7 +25,7 @@ type OpenJsonModalFunc = () => void; interface Props { onJson(json: { [key: string]: any }): void; - children: (deleteProperty: OpenJsonModalFunc) => React.ReactNode; + children: (openModal: OpenJsonModalFunc) => React.ReactNode; } interface State { diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/extract_mappings_definition.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/extract_mappings_definition.ts index eae3c5b15759c..817b0f4a4d3d0 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/extract_mappings_definition.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/extract_mappings_definition.ts @@ -6,15 +6,10 @@ import { isPlainObject } from 'lodash'; import { GenericObject } from '../types'; -import { - validateMappingsConfiguration, - mappingsConfigurationSchemaKeys, -} from './mappings_validator'; - -const ALLOWED_PARAMETERS = [...mappingsConfigurationSchemaKeys, 'dynamic_templates', 'properties']; +import { validateMappingsConfiguration, VALID_MAPPINGS_PARAMETERS } from './mappings_validator'; const isMappingDefinition = (obj: GenericObject): boolean => { - const areAllKeysValid = Object.keys(obj).every(key => ALLOWED_PARAMETERS.includes(key)); + const areAllKeysValid = Object.keys(obj).every(key => VALID_MAPPINGS_PARAMETERS.includes(key)); if (!areAllKeysValid) { return false; diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts index f1e6efb06c649..d67c267dda6ae 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.test.ts @@ -18,6 +18,24 @@ describe('Mappings configuration validator', () => { }); }); + it('should detect valid mappings configuration', () => { + const mappings = { + _source: { + includes: [], + excludes: [], + enabled: true, + }, + _meta: {}, + _routing: { + required: false, + }, + dynamic: true, + }; + + const { errors } = validateMappings(mappings); + expect(errors).toBe(undefined); + }); + it('should strip out unknown configuration', () => { const mappings = { dynamic: true, @@ -30,6 +48,7 @@ describe('Mappings configuration validator', () => { excludes: ['abc'], }, properties: { title: { type: 'text' } }, + dynamic_templates: [], unknown: 123, }; @@ -37,7 +56,7 @@ describe('Mappings configuration validator', () => { const { unknown, ...expected } = mappings; expect(value).toEqual(expected); - expect(errors).toBe(undefined); + expect(errors).toEqual([{ code: 'ERR_CONFIG', configName: 'unknown' }]); }); it('should strip out invalid configuration and returns the errors for each of them', () => { @@ -47,9 +66,8 @@ describe('Mappings configuration validator', () => { dynamic_date_formats: false, // wrong format _source: { enabled: true, - includes: 'abc', + unknownProp: 'abc', // invalid excludes: ['abc'], - wrong: 123, // parameter not allowed }, properties: 'abc', }; @@ -59,10 +77,10 @@ describe('Mappings configuration validator', () => { expect(value).toEqual({ dynamic: true, properties: {}, + dynamic_templates: [], }); expect(errors).not.toBe(undefined); - expect(errors!.length).toBe(3); expect(errors!).toEqual([ { code: 'ERR_CONFIG', configName: '_source' }, { code: 'ERR_CONFIG', configName: 'dynamic_date_formats' }, diff --git a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.ts b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.ts index 6ccbfeb50dcf4..78d638e398593 100644 --- a/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.ts +++ b/x-pack/legacy/plugins/index_management/public/app/components/mappings_editor/lib/mappings_validator.ts @@ -196,23 +196,30 @@ export const validateProperties = (properties = {}): PropertiesValidatorResponse * Single source of truth to validate the *configuration* of the mappings. * Whenever a user loads a JSON object it will be validate against this Joi schema. */ -export const mappingsConfigurationSchema = t.partial({ - dynamic: t.union([t.literal(true), t.literal(false), t.literal('strict')]), - date_detection: t.boolean, - numeric_detection: t.boolean, - dynamic_date_formats: t.array(t.string), - _source: t.partial({ - enabled: t.boolean, - includes: t.array(t.string), - excludes: t.array(t.string), - }), - _meta: t.UnknownRecord, - _routing: t.partial({ - required: t.boolean, - }), -}); - -export const mappingsConfigurationSchemaKeys = Object.keys(mappingsConfigurationSchema.props); +export const mappingsConfigurationSchema = t.exact( + t.partial({ + dynamic: t.union([t.literal(true), t.literal(false), t.literal('strict')]), + date_detection: t.boolean, + numeric_detection: t.boolean, + dynamic_date_formats: t.array(t.string), + _source: t.exact( + t.partial({ + enabled: t.boolean, + includes: t.array(t.string), + excludes: t.array(t.string), + }) + ), + _meta: t.UnknownRecord, + _routing: t.interface({ + required: t.boolean, + }), + }) +); + +const mappingsConfigurationSchemaKeys = Object.keys(mappingsConfigurationSchema.type.props); +const sourceConfigurationSchemaKeys = Object.keys( + mappingsConfigurationSchema.type.props._source.type.props +); export const validateMappingsConfiguration = ( mappingsConfiguration: any @@ -222,8 +229,20 @@ export const validateMappingsConfiguration = ( let copyOfMappingsConfig = { ...mappingsConfiguration }; const result = mappingsConfigurationSchema.decode(mappingsConfiguration); + const isSchemaInvalid = isLeft(result); - if (isLeft(result)) { + const unknownConfigurationParameters = Object.keys(mappingsConfiguration).filter( + key => mappingsConfigurationSchemaKeys.includes(key) === false + ); + + const unknownSourceConfigurationParameters = + mappingsConfiguration._source !== undefined + ? Object.keys(mappingsConfiguration._source).filter( + key => sourceConfigurationSchemaKeys.includes(key) === false + ) + : []; + + if (isSchemaInvalid) { /** * To keep the logic simple we will strip out the parameters that contain errors */ @@ -235,6 +254,15 @@ export const validateMappingsConfiguration = ( }); } + if (unknownConfigurationParameters.length > 0) { + unknownConfigurationParameters.forEach(configName => configurationRemoved.add(configName)); + } + + if (unknownSourceConfigurationParameters.length > 0) { + configurationRemoved.add('_source'); + delete copyOfMappingsConfig._source; + } + copyOfMappingsConfig = pick(copyOfMappingsConfig, mappingsConfigurationSchemaKeys); const errors: MappingsValidationError[] = toArray(ordString)(configurationRemoved) @@ -252,7 +280,7 @@ export const validateMappings = (mappings: any = {}): MappingsValidatorResponse return { value: {} }; } - const { properties, dynamic_templates, ...mappingsConfiguration } = mappings; + const { properties, dynamic_templates: dynamicTemplates, ...mappingsConfiguration } = mappings; const { value: parsedConfiguration, errors: configurationErrors } = validateMappingsConfiguration( mappingsConfiguration @@ -265,8 +293,14 @@ export const validateMappings = (mappings: any = {}): MappingsValidatorResponse value: { ...parsedConfiguration, properties: parsedProperties, - dynamic_templates, + dynamic_templates: dynamicTemplates ?? [], }, errors: errors.length ? errors : undefined, }; }; + +export const VALID_MAPPINGS_PARAMETERS = [ + ...mappingsConfigurationSchemaKeys, + 'dynamic_templates', + 'properties', +]; diff --git a/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts b/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts index f1d0577b4cb19..25ee8eb09d1e1 100644 --- a/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts +++ b/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts @@ -46,7 +46,7 @@ export const DOCKER_ALLOWED_LIST = [ ]; export const AWS_S3_ALLOWED_LIST = ['aws.s3']; -export const AWS_METRICS_ALLOWED_LIST = ['aws.cloudwatch']; +export const AWS_METRICS_ALLOWED_LIST = ['aws.dimensions']; export const getAllowedListForPrefix = memoize((prefix: string) => { const firstPart = first(prefix.split(/\./)); @@ -60,11 +60,9 @@ export const getAllowedListForPrefix = memoize((prefix: string) => { return [...defaultAllowedList, ...K8S_ALLOWED_LIST]; case 'aws': if (prefix === 'aws.s3_daily_storage') { - return [...defaultAllowedList, ...AWS_S3_ALLOWED_LIST]; - } - if (prefix === 'aws.metrics') { - return [...defaultAllowedList, ...AWS_METRICS_ALLOWED_LIST]; + return [...defaultAllowedList, ...AWS_S3_ALLOWED_LIST, ...AWS_METRICS_ALLOWED_LIST]; } + return [...defaultAllowedList, ...AWS_METRICS_ALLOWED_LIST]; default: return defaultAllowedList; } diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/index.ts b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/index.ts index c7a49a90a7886..7f2982f221a3c 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/index.ts +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/index.ts @@ -9,5 +9,7 @@ export * from './setup_page'; export * from './initial_configuration_step'; export * from './process_step'; +export * from './missing_results_privileges_prompt'; +export * from './missing_setup_privileges_prompt'; export * from './ml_unavailable_prompt'; export * from './setup_status_unknown_prompt'; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/missing_results_privileges_prompt.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/missing_results_privileges_prompt.tsx new file mode 100644 index 0000000000000..0c3393b0e15e3 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/missing_results_privileges_prompt.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +import euiStyled from '../../../../../../common/eui_styled_components'; +import { UserManagementLink } from './user_management_link'; + +export const MissingResultsPrivilegesPrompt: React.FunctionComponent = () => ( + + + + } + body={ +

+ machine_learning_user, + }} + /> +

+ } + actions={} + /> +); + +const EmptyPrompt = euiStyled(EuiEmptyPrompt)` + align-self: center; +`; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/missing_setup_privileges_prompt.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/missing_setup_privileges_prompt.tsx new file mode 100644 index 0000000000000..1549ab9120777 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/missing_setup_privileges_prompt.tsx @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiEmptyPrompt, EuiCode } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +import euiStyled from '../../../../../../common/eui_styled_components'; +import { UserManagementLink } from './user_management_link'; + +export const MissingSetupPrivilegesPrompt: React.FunctionComponent = () => ( + + + + } + body={ +

+ machine_learning_admin, + }} + /> +

+ } + actions={} + /> +); + +const EmptyPrompt = euiStyled(EuiEmptyPrompt)` + align-self: center; +`; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/user_management_link.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/user_management_link.tsx new file mode 100644 index 0000000000000..9a2bbd3dabffc --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_setup/user_management_link.tsx @@ -0,0 +1,18 @@ +/* + * 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 { EuiButton, EuiButtonProps } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +export const UserManagementLink: React.FunctionComponent = props => ( + + + +); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index 84bcfa911125a..15d3c83ffebe9 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -51,6 +51,7 @@ interface ScrollableLogTextStreamViewProps { fromScroll: boolean; }) => any; loadNewerItems: () => void; + reloadItems: () => void; setFlyoutItem: (id: string) => void; setFlyoutVisibility: (visible: boolean) => void; highlightedItem: string | null; @@ -269,10 +270,10 @@ export class ScrollableLogTextStreamView extends React.PureComponent< }; private handleReload = () => { - const { jumpToTarget, target } = this.props; + const { reloadItems } = this.props; - if (target) { - jumpToTarget(target); + if (reloadItems) { + reloadItems(); } }; diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx index 6153ebce5e0da..f66ae867eef5a 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx @@ -86,7 +86,7 @@ export const MetricsExplorerChart = ({ - + {title} @@ -159,7 +159,7 @@ export const MetricsExplorerChart = ({ }; const ChartTitle = euiStyled.div` - width: 100% + width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx index bd8be6df8ea69..3c10ba805fad5 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/log_analysis_capabilities.tsx @@ -48,13 +48,22 @@ export const useLogAnalysisCapabilities = () => { fetchMlCapabilitiesRequest.state, ]); + const hasLogAnalysisSetupCapabilities = mlCapabilities.capabilities.canCreateJob; + const hasLogAnalysisReadCapabilities = mlCapabilities.capabilities.canGetJobs; + const hasLogAnalysisCapabilites = + mlCapabilities.isPlatinumOrTrialLicense && mlCapabilities.mlFeatureEnabledInSpace; + return { - hasLogAnalysisCapabilites: mlCapabilities.capabilities.canCreateJob, + hasLogAnalysisCapabilites, + hasLogAnalysisReadCapabilities, + hasLogAnalysisSetupCapabilities, isLoading, }; }; -export const LogAnalysisCapabilities = createContainer(useLogAnalysisCapabilities); +export const [LogAnalysisCapabilitiesProvider, useLogAnalysisCapabilitiesContext] = createContainer( + useLogAnalysisCapabilities +); const initialMlCapabilities = { capabilities: { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts index ed82c0854cdea..04412f5fdd871 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts @@ -67,6 +67,7 @@ export type LogEntriesStateParams = { export interface LogEntriesCallbacks { fetchNewerEntries: () => Promise; + checkForNewEntries: () => Promise; } export const logEntriesInitialCallbacks = { fetchNewerEntries: async () => {}, @@ -231,7 +232,7 @@ const useFetchEntriesEffect = ( useEffect(fetchMoreEntriesEffect, fetchMoreEntriesEffectDependencies); useEffect(streamEntriesEffect, streamEntriesEffectDependencies); - return { fetchNewerEntries }; + return { fetchNewerEntries, checkForNewEntries: runFetchNewEntriesRequest }; }; export const useLogEntriesState: ( @@ -239,8 +240,8 @@ export const useLogEntriesState: ( ) => [LogEntriesStateParams, LogEntriesCallbacks] = props => { const [state, dispatch] = useReducer(logEntriesStateReducer, logEntriesInitialState); - const { fetchNewerEntries } = useFetchEntriesEffect(state, dispatch, props); - const callbacks = { fetchNewerEntries }; + const { fetchNewerEntries, checkForNewEntries } = useFetchEntriesEffect(state, dispatch, props); + const callbacks = { fetchNewerEntries, checkForNewEntries }; return [state, callbacks]; }; diff --git a/x-pack/legacy/plugins/infra/public/index.scss b/x-pack/legacy/plugins/infra/public/index.scss index 4cef6d6baa915..afee4ab8b0389 100644 --- a/x-pack/legacy/plugins/infra/public/index.scss +++ b/x-pack/legacy/plugins/infra/public/index.scss @@ -36,6 +36,12 @@ .infrastructureChart .echTooltip__label { overflow-x: hidden; - white-space: no-wrap; + white-space: nowrap; text-overflow: ellipsis; } + +.metricsExplorerTitleAnchor { + white-space: nowrap; + text-overflow: ellipsis; + display: inline; +} diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index 505878f0239dc..224217e860e94 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -4,132 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { Route, RouteComponentProps, Switch } from 'react-router-dom'; - -import { DocumentTitle } from '../../components/document_title'; -import { HelpCenterContent } from '../../components/help_center_content'; -import { Header } from '../../components/header'; -import { RoutedTabs } from '../../components/navigation/routed_tabs'; -import { ColumnarPage } from '../../components/page'; -import { SourceLoadingPage } from '../../components/source_loading_page'; -import { SourceErrorPage } from '../../components/source_error_page'; -import { Source, useSource } from '../../containers/source'; -import { StreamPage } from './stream'; -import { LogsSettingsPage } from './settings'; -import { AppNavigation } from '../../components/navigation/app_navigation'; -import { - useLogAnalysisCapabilities, - LogAnalysisCapabilities, -} from '../../containers/logs/log_analysis'; -import { useSourceId } from '../../containers/source_id'; -import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params'; -import { useKibana } from '../../../../../../..//src/plugins/kibana_react/public'; -import { LogEntryCategoriesPage } from './log_entry_categories'; -import { LogEntryRatePage } from './log_entry_rate'; - -export const LogsPage = ({ match }: RouteComponentProps) => { - const uiCapabilities = useKibana().services.application?.capabilities; - const [sourceId] = useSourceId(); - const source = useSource({ sourceId }); - const logAnalysisCapabilities = useLogAnalysisCapabilities(); - - const streamTab = { - title: streamTabTitle, - path: `${match.path}/stream`, - }; - - const logRateTab = { - title: logRateTabTitle, - path: `${match.path}/log-rate`, - }; - - const logCategoriesTab = { - title: logCategoriesTabTitle, - path: `${match.path}/log-categories`, - }; - - const settingsTab = { - title: settingsTabTitle, - path: `${match.path}/settings`, - }; - - return ( - - - - - - - -
- {source.isLoadingSource || - (!source.isLoadingSource && - !source.hasFailedLoadingSource && - source.source === undefined) ? ( - - ) : source.hasFailedLoadingSource ? ( - - ) : ( - <> - - - - - - - - - - - - - )} - - - - ); -}; - -const pageTitle = i18n.translate('xpack.infra.header.logsTitle', { - defaultMessage: 'Logs', -}); - -const streamTabTitle = i18n.translate('xpack.infra.logs.index.streamTabTitle', { - defaultMessage: 'Stream', -}); - -const logRateTabTitle = i18n.translate('xpack.infra.logs.index.logRateBetaBadgeTitle', { - defaultMessage: 'Log Rate', -}); - -const logCategoriesTabTitle = i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', { - defaultMessage: 'Categories', -}); - -const settingsTabTitle = i18n.translate('xpack.infra.logs.index.settingsTabTitle', { - defaultMessage: 'Settings', -}); - -const feedbackLinkUrl = 'https://discuss.elastic.co/c/logs'; +export * from './page'; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx index cc59d73055796..b6975ffc54691 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx @@ -5,21 +5,27 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useContext, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { isSetupStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisSetupStatusUnknownPrompt, + MissingResultsPrivilegesPrompt, + MissingSetupPrivilegesPrompt, MlUnavailablePrompt, } from '../../../components/logging/log_analysis_setup'; -import { LogAnalysisCapabilities } from '../../../containers/logs/log_analysis'; +import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; import { LogEntryCategoriesResultsContent } from './page_results_content'; import { LogEntryCategoriesSetupContent } from './page_setup_content'; import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module'; export const LogEntryCategoriesPageContent = () => { - const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context); + const { + hasLogAnalysisCapabilites, + hasLogAnalysisReadCapabilities, + hasLogAnalysisSetupCapabilities, + } = useLogAnalysisCapabilitiesContext(); const { fetchJobStatus, @@ -28,12 +34,16 @@ export const LogEntryCategoriesPageContent = () => { } = useLogEntryCategoriesModuleContext(); useEffect(() => { - fetchModuleDefinition(); - fetchJobStatus(); - }, [fetchJobStatus, fetchModuleDefinition]); + if (hasLogAnalysisReadCapabilities) { + fetchModuleDefinition(); + fetchJobStatus(); + } + }, [fetchJobStatus, fetchModuleDefinition, hasLogAnalysisReadCapabilities]); if (!hasLogAnalysisCapabilites) { return ; + } else if (!hasLogAnalysisReadCapabilities) { + return ; } else if (setupStatus === 'initializing') { return ( { return ; } else if (isSetupStatusWithResults(setupStatus)) { return ; + } else if (!hasLogAnalysisSetupCapabilities) { + return ; } else { return ; } diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx index a80464ed42cb2..8d2b4e1fd0ff4 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_content.tsx @@ -5,31 +5,41 @@ */ import { i18n } from '@kbn/i18n'; -import React, { useContext, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { isSetupStatusWithResults } from '../../../../common/log_analysis'; import { LoadingPage } from '../../../components/loading_page'; import { LogAnalysisSetupStatusUnknownPrompt, + MissingResultsPrivilegesPrompt, + MissingSetupPrivilegesPrompt, MlUnavailablePrompt, } from '../../../components/logging/log_analysis_setup'; -import { LogAnalysisCapabilities } from '../../../containers/logs/log_analysis'; +import { useLogAnalysisCapabilitiesContext } from '../../../containers/logs/log_analysis'; import { LogEntryRateResultsContent } from './page_results_content'; import { LogEntryRateSetupContent } from './page_setup_content'; import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; export const LogEntryRatePageContent = () => { - const { hasLogAnalysisCapabilites } = useContext(LogAnalysisCapabilities.Context); + const { + hasLogAnalysisCapabilites, + hasLogAnalysisReadCapabilities, + hasLogAnalysisSetupCapabilities, + } = useLogAnalysisCapabilitiesContext(); const { fetchJobStatus, fetchModuleDefinition, setupStatus } = useLogEntryRateModuleContext(); useEffect(() => { - fetchModuleDefinition(); - fetchJobStatus(); - }, [fetchJobStatus, fetchModuleDefinition]); + if (hasLogAnalysisReadCapabilities) { + fetchModuleDefinition(); + fetchJobStatus(); + } + }, [fetchJobStatus, fetchModuleDefinition, hasLogAnalysisReadCapabilities]); if (!hasLogAnalysisCapabilites) { return ; + } else if (!hasLogAnalysisReadCapabilities) { + return ; } else if (setupStatus === 'initializing') { return ( { return ; } else if (isSetupStatusWithResults(setupStatus)) { return ; + } else if (!hasLogAnalysisSetupCapabilities) { + return ; } else { return ; } diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/page.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/page.tsx new file mode 100644 index 0000000000000..72826b156d7b4 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/page.tsx @@ -0,0 +1,19 @@ +/* + * 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 React from 'react'; +import { RouteComponentProps } from 'react-router-dom'; + +import { LogsPageContent } from './page_content'; +import { LogsPageProviders } from './page_providers'; + +export const LogsPage: React.FunctionComponent = ({ match }) => { + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/page_content.tsx new file mode 100644 index 0000000000000..41ef9987d1ad0 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/page_content.tsx @@ -0,0 +1,122 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import React from 'react'; +import { Route, Switch } from 'react-router-dom'; + +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { DocumentTitle } from '../../components/document_title'; +import { Header } from '../../components/header'; +import { HelpCenterContent } from '../../components/help_center_content'; +import { AppNavigation } from '../../components/navigation/app_navigation'; +import { RoutedTabs } from '../../components/navigation/routed_tabs'; +import { ColumnarPage } from '../../components/page'; +import { SourceErrorPage } from '../../components/source_error_page'; +import { SourceLoadingPage } from '../../components/source_loading_page'; +import { useLogAnalysisCapabilitiesContext } from '../../containers/logs/log_analysis'; +import { useSourceContext } from '../../containers/source'; +import { RedirectWithQueryParams } from '../../utils/redirect_with_query_params'; +import { LogEntryCategoriesPage } from './log_entry_categories'; +import { LogEntryRatePage } from './log_entry_rate'; +import { LogsSettingsPage } from './settings'; +import { StreamPage } from './stream'; + +export const LogsPageContent: React.FunctionComponent<{ + logsPagePath: string; +}> = ({ logsPagePath }) => { + const uiCapabilities = useKibana().services.application?.capabilities; + const source = useSourceContext(); + const logAnalysisCapabilities = useLogAnalysisCapabilitiesContext(); + + const streamTab = { + title: streamTabTitle, + path: `${logsPagePath}/stream`, + }; + + const logRateTab = { + title: logRateTabTitle, + path: `${logsPagePath}/log-rate`, + }; + + const logCategoriesTab = { + title: logCategoriesTabTitle, + path: `${logsPagePath}/log-categories`, + }; + + const settingsTab = { + title: settingsTabTitle, + path: `${logsPagePath}/settings`, + }; + + return ( + + + + + +
+ {source.isLoadingSource || + (!source.isLoadingSource && !source.hasFailedLoadingSource && source.source === undefined) ? ( + + ) : source.hasFailedLoadingSource ? ( + + ) : ( + <> + + + + + + + + + + + + + )} + + ); +}; + +const pageTitle = i18n.translate('xpack.infra.header.logsTitle', { + defaultMessage: 'Logs', +}); + +const streamTabTitle = i18n.translate('xpack.infra.logs.index.streamTabTitle', { + defaultMessage: 'Stream', +}); + +const logRateTabTitle = i18n.translate('xpack.infra.logs.index.logRateBetaBadgeTitle', { + defaultMessage: 'Log Rate', +}); + +const logCategoriesTabTitle = i18n.translate('xpack.infra.logs.index.logCategoriesBetaBadgeTitle', { + defaultMessage: 'Categories', +}); + +const settingsTabTitle = i18n.translate('xpack.infra.logs.index.settingsTabTitle', { + defaultMessage: 'Settings', +}); + +const feedbackLinkUrl = 'https://discuss.elastic.co/c/logs'; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/page_providers.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/page_providers.tsx new file mode 100644 index 0000000000000..24c1598787a20 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/page_providers.tsx @@ -0,0 +1,20 @@ +/* + * 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 React from 'react'; +import { LogAnalysisCapabilitiesProvider } from '../../containers/logs/log_analysis'; +import { SourceProvider } from '../../containers/source'; +import { useSourceId } from '../../containers/source_id'; + +export const LogsPageProviders: React.FunctionComponent = ({ children }) => { + const [sourceId] = useSourceId(); + + return ( + + {children} + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index f789c16b67d9c..4d2b99da9b412 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -82,6 +82,7 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { items, lastLoadedTime, fetchNewerEntries, + checkForNewEntries, }) => ( { jumpToTarget={jumpToTargetPosition} lastLoadedTime={lastLoadedTime} loadNewerItems={fetchNewerEntries} + reloadItems={checkForNewEntries} reportVisibleInterval={reportVisiblePositions} scale={textScale} target={targetPosition} diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx index bc61c6ae34ce5..1f0620c43f7f7 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/embeddable.test.tsx @@ -9,7 +9,7 @@ import { ExpressionRendererProps } from 'src/plugins/expressions/public'; import { Query, TimeRange, esFilters } from 'src/plugins/data/public'; import { Document } from '../../persistence'; -jest.mock('../../../../../../../src/legacy/ui/public/inspector', () => ({ +jest.mock('../../../../../../../src/plugins/inspector/public/', () => ({ isAvailable: false, open: false, })); diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.js index eef621e6a2cd6..2570341aa5756 100644 --- a/x-pack/legacy/plugins/maps/common/constants.js +++ b/x-pack/legacy/plugins/maps/common/constants.js @@ -151,3 +151,10 @@ export const COLOR_MAP_TYPE = { export const COLOR_PALETTE_MAX_SIZE = 10; export const CATEGORICAL_DATA_TYPES = ['string', 'ip', 'boolean']; + +export const SYMBOLIZE_AS_TYPES = { + CIRCLE: 'circle', + ICON: 'icon', +}; + +export const DEFAULT_ICON = 'airfield'; diff --git a/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.js b/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.js new file mode 100644 index 0000000000000..0ae20ffd142c5 --- /dev/null +++ b/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.js @@ -0,0 +1,46 @@ +/* + * 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 { DEFAULT_ICON, LAYER_TYPE, STYLE_TYPE, SYMBOLIZE_AS_TYPES } from '../constants'; + +function isVectorLayer(layerDescriptor) { + const layerType = _.get(layerDescriptor, 'type'); + return layerType === LAYER_TYPE.VECTOR; +} + +export function migrateSymbolStyleDescriptor({ attributes }) { + if (!attributes.layerListJSON) { + return attributes; + } + + const layerList = JSON.parse(attributes.layerListJSON); + layerList.forEach(layerDescriptor => { + if (!isVectorLayer(layerDescriptor) || !_.has(layerDescriptor, 'style.properties')) { + return; + } + + const symbolizeAs = _.get( + layerDescriptor, + 'style.properties.symbol.options.symbolizeAs', + SYMBOLIZE_AS_TYPES.CIRCLE + ); + layerDescriptor.style.properties.symbolizeAs = { + options: { value: symbolizeAs }, + }; + const iconId = _.get(layerDescriptor, 'style.properties.symbol.options.symbolId', DEFAULT_ICON); + layerDescriptor.style.properties.icon = { + type: STYLE_TYPE.STATIC, + options: { value: iconId }, + }; + delete layerDescriptor.style.properties.symbol; + }); + + return { + ...attributes, + layerListJSON: JSON.stringify(layerList), + }; +} diff --git a/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.test.js b/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.test.js new file mode 100644 index 0000000000000..2811b83f46d8f --- /dev/null +++ b/x-pack/legacy/plugins/maps/common/migrations/migrate_symbol_style_descriptor.test.js @@ -0,0 +1,131 @@ +/* + * 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 { migrateSymbolStyleDescriptor } from './migrate_symbol_style_descriptor'; +import { LAYER_TYPE, STYLE_TYPE } from '../constants'; + +describe('migrateSymbolStyleDescriptor', () => { + test('Should handle missing layerListJSON attribute', () => { + const attributes = { + title: 'my map', + }; + expect(migrateSymbolStyleDescriptor({ attributes })).toEqual({ + title: 'my map', + }); + }); + + test('Should ignore non-vector layers', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.HEATMAP, + style: { + type: 'HEATMAP', + colorRampName: 'Greens', + }, + }, + ]); + const attributes = { + title: 'my map', + layerListJSON, + }; + expect(migrateSymbolStyleDescriptor({ attributes })).toEqual({ + title: 'my map', + layerListJSON, + }); + }); + + test('Should migrate "symbol" style descriptor', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + properties: { + fillColor: { + type: STYLE_TYPE.STATIC, + options: { color: '#54B399' }, + }, + symbol: { + options: { + symbolizeAs: 'icon', + symbolId: 'square', + }, + }, + }, + }, + }, + ]); + const attributes = { + title: 'my map', + layerListJSON, + }; + expect(migrateSymbolStyleDescriptor({ attributes })).toEqual({ + title: 'my map', + layerListJSON: JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + properties: { + fillColor: { + type: STYLE_TYPE.STATIC, + options: { color: '#54B399' }, + }, + symbolizeAs: { + options: { value: 'icon' }, + }, + icon: { + type: STYLE_TYPE.STATIC, + options: { value: 'square' }, + }, + }, + }, + }, + ]), + }); + }); + + test('Should migrate style descriptor without "symbol"', () => { + const layerListJSON = JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + properties: { + fillColor: { + type: STYLE_TYPE.STATIC, + options: { color: '#54B399' }, + }, + }, + }, + }, + ]); + const attributes = { + title: 'my map', + layerListJSON, + }; + expect(migrateSymbolStyleDescriptor({ attributes })).toEqual({ + title: 'my map', + layerListJSON: JSON.stringify([ + { + type: LAYER_TYPE.VECTOR, + style: { + properties: { + fillColor: { + type: STYLE_TYPE.STATIC, + options: { color: '#54B399' }, + }, + symbolizeAs: { + options: { value: 'circle' }, + }, + icon: { + type: STYLE_TYPE.STATIC, + options: { value: 'airfield' }, + }, + }, + }, + }, + ]), + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/migrations.js b/x-pack/legacy/plugins/maps/migrations.js index 023695335ec28..9622f6ba63fac 100644 --- a/x-pack/legacy/plugins/maps/migrations.js +++ b/x-pack/legacy/plugins/maps/migrations.js @@ -9,6 +9,7 @@ import { emsRasterTileToEmsVectorTile } from './common/migrations/ems_raster_til import { topHitsTimeToSort } from './common/migrations/top_hits_time_to_sort'; import { moveApplyGlobalQueryToSources } from './common/migrations/move_apply_global_query'; import { addFieldMetaOptions } from './common/migrations/add_field_meta_options'; +import { migrateSymbolStyleDescriptor } from './common/migrations/migrate_symbol_style_descriptor'; export const migrations = { map: { @@ -46,5 +47,13 @@ export const migrations = { attributes: attributesPhase2, }; }, + '7.7.0': doc => { + const attributes = migrateSymbolStyleDescriptor(doc); + + return { + ...doc, + attributes, + }; + }, }, }; diff --git a/x-pack/legacy/plugins/maps/public/angular/map_controller.js b/x-pack/legacy/plugins/maps/public/angular/map_controller.js index ece775f5a7e25..5f058e2ba7806 100644 --- a/x-pack/legacy/plugins/maps/public/angular/map_controller.js +++ b/x-pack/legacy/plugins/maps/public/angular/map_controller.js @@ -43,9 +43,8 @@ import { getLayerListRaw, } from '../selectors/map_selectors'; import { getInspectorAdapters } from '../reducers/non_serializable_instances'; -import { Inspector } from 'ui/inspector'; import { docTitle } from 'ui/doc_title'; -import { indexPatternService } from '../kibana_services'; +import { indexPatternService, getInspector } from '../kibana_services'; import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { toastNotifications } from 'ui/notify'; @@ -510,7 +509,7 @@ app.controller( testId: 'openInspectorButton', run() { const inspectorAdapters = getInspectorAdapters(store.getState()); - Inspector.open(inspectorAdapters, {}); + getInspector().open(inspectorAdapters, {}); }, }, ...(capabilities.get().maps.save diff --git a/x-pack/legacy/plugins/maps/public/components/single_field_select.js b/x-pack/legacy/plugins/maps/public/components/single_field_select.js index ba9ef1f22c54c..7351ce7691a82 100644 --- a/x-pack/legacy/plugins/maps/public/components/single_field_select.js +++ b/x-pack/legacy/plugins/maps/public/components/single_field_select.js @@ -8,63 +8,50 @@ import _ from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; -import { EuiComboBox } from '@elastic/eui'; +import { EuiComboBox, EuiHighlight } from '@elastic/eui'; +import { FieldIcon } from '../../../../../../src/plugins/kibana_react/public'; -const sortByLabel = (a, b) => { - if (a.label < b.label) return -1; - if (a.label > b.label) return 1; - return 0; -}; - -// Creates grouped options by grouping fields by field type -export const getGroupedFieldOptions = (fields, filterField) => { +function fieldsToOptions(fields) { if (!fields) { - return; + return []; } - const fieldsByTypeMap = new Map(); - - fields.filter(filterField).forEach(field => { - const fieldLabel = 'label' in field ? field.label : field.name; - if (fieldsByTypeMap.has(field.type)) { - const fieldsList = fieldsByTypeMap.get(field.type); - fieldsList.push({ value: field.name, label: fieldLabel }); - fieldsByTypeMap.set(field.type, fieldsList); - } else { - fieldsByTypeMap.set(field.type, [{ value: field.name, label: fieldLabel }]); - } - }); - - const groupedFieldOptions = []; - fieldsByTypeMap.forEach((fieldsList, fieldType) => { - const sortedOptions = fieldsList.sort(sortByLabel).map(({ value, label }) => { - return { value: value, label: label }; + return fields + .map(field => { + return { + value: field, + label: 'label' in field ? field.label : field.name, + }; + }) + .sort((a, b) => { + return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); }); +} - groupedFieldOptions.push({ - label: fieldType, - options: sortedOptions, - }); - }); - - groupedFieldOptions.sort(sortByLabel); - - return groupedFieldOptions; -}; +function renderOption(option, searchValue, contentClassName) { + return ( + + +   + {option.label} + + ); +} -export function SingleFieldSelect({ fields, filterField, onChange, value, placeholder, ...rest }) { +export function SingleFieldSelect({ fields, onChange, value, placeholder, ...rest }) { const onSelection = selectedOptions => { - onChange(_.get(selectedOptions, '0.value')); + onChange(_.get(selectedOptions, '0.value.name')); }; return ( ); @@ -75,11 +62,4 @@ SingleFieldSelect.propTypes = { fields: PropTypes.array, onChange: PropTypes.func.isRequired, value: PropTypes.string, // fieldName - filterField: PropTypes.func, -}; - -SingleFieldSelect.defaultProps = { - filterField: () => { - return true; - }, }; diff --git a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js index 76827e71df9ec..777c8ae0923fe 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/layer_panel/join_editor/resources/join_expression.js @@ -23,7 +23,6 @@ import { getTermsFields } from '../../../../index_pattern_util'; import { indexPatternService } from '../../../../kibana_services'; import { npStart } from 'ui/new_platform'; -import { isNestedField } from '../../../../../../../../../src/plugins/data/public'; const { IndexPatternSelect } = npStart.plugins.data.ui; export class JoinExpression extends Component { @@ -134,10 +133,6 @@ export class JoinExpression extends Component { return null; } - const filterStringOrNumberFields = field => { - return (field.type === 'string' && !isNestedField(field)) || field.type === 'number'; - }; - return ( diff --git a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js index cf6085e0c398c..1e44c7225a564 100644 --- a/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js +++ b/x-pack/legacy/plugins/maps/public/connected_components/map/mb/view.js @@ -107,7 +107,6 @@ export class MBMapContainer extends React.Component { } async _createMbMapInstance() { - const initialView = this.props.goto ? this.props.goto.center : null; return new Promise(resolve => { const mbStyle = { version: 8, @@ -127,12 +126,15 @@ export class MBMapContainer extends React.Component { preserveDrawingBuffer: chrome.getInjected('preserveDrawingBuffer', false), interactive: !this.props.disableInteractive, }; + const initialView = _.get(this.props.goto, 'center'); if (initialView) { options.zoom = initialView.zoom; options.center = { lng: initialView.lon, lat: initialView.lat, }; + } else { + options.bounds = [-170, -60, 170, 75]; } const mbMap = new mapboxgl.Map(options); mbMap.dragRotate.disable(); diff --git a/x-pack/legacy/plugins/maps/public/index_pattern_util.js b/x-pack/legacy/plugins/maps/public/index_pattern_util.js index 10837bc2f0d0c..96d4a4b19fbfa 100644 --- a/x-pack/legacy/plugins/maps/public/index_pattern_util.js +++ b/x-pack/legacy/plugins/maps/public/index_pattern_util.js @@ -6,6 +6,7 @@ import { indexPatternService } from './kibana_services'; import { isNestedField } from '../../../../../src/plugins/data/public'; +import { ES_GEO_FIELD_TYPE } from '../common/constants'; export async function getIndexPatternsFromIds(indexPatternIds = []) { const promises = []; @@ -29,6 +30,18 @@ export function getTermsFields(fields) { }); } +export const AGGREGATABLE_GEO_FIELD_TYPES = [ES_GEO_FIELD_TYPE.GEO_POINT]; + +export function getAggregatableGeoFields(fields) { + return fields.filter(field => { + return ( + field.aggregatable && + !isNestedField(field) && + AGGREGATABLE_GEO_FIELD_TYPES.includes(field.type) + ); + }); +} + // Returns filtered fields list containing only fields that exist in _source. export function getSourceFields(fields) { return fields.filter(field => { diff --git a/x-pack/legacy/plugins/maps/public/inspector/views/register_views.js b/x-pack/legacy/plugins/maps/public/inspector/views/register_views.ts similarity index 72% rename from x-pack/legacy/plugins/maps/public/inspector/views/register_views.js rename to x-pack/legacy/plugins/maps/public/inspector/views/register_views.ts index 6cca73f899cfd..59c0595668300 100644 --- a/x-pack/legacy/plugins/maps/public/inspector/views/register_views.js +++ b/x-pack/legacy/plugins/maps/public/inspector/views/register_views.ts @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MapView } from './map_view'; +import { npSetup } from 'ui/new_platform'; -import { viewRegistry } from 'ui/inspector'; +// @ts-ignore +import { MapView } from './map_view'; -viewRegistry.register(MapView); +npSetup.plugins.inspector.registerView(MapView); diff --git a/x-pack/legacy/plugins/maps/public/kibana_services.js b/x-pack/legacy/plugins/maps/public/kibana_services.js index dadae7a3fdca9..60fda398b4f3e 100644 --- a/x-pack/legacy/plugins/maps/public/kibana_services.js +++ b/x-pack/legacy/plugins/maps/public/kibana_services.js @@ -21,6 +21,12 @@ export const getLicenseId = () => { return licenseId; }; +let inspector; +export const setInspector = newInspector => (inspector = newInspector); +export const getInspector = () => { + return inspector; +}; + export async function fetchSearchSourceAndRecordWithInspector({ searchSource, requestId, diff --git a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js index 452f669a113cd..05b177b361449 100644 --- a/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/joins/inner_join.test.js @@ -6,14 +6,13 @@ import { InnerJoin } from './inner_join'; -jest.mock('ui/new_platform'); -jest.mock('ui/vis/editors/default/schemas', () => { +jest.mock('../../kibana_services', () => {}); +jest.mock('ui/agg_types', () => { class MockSchemas {} return { Schemas: MockSchemas, }; }); -jest.mock('ui/agg_types', () => {}); jest.mock('ui/timefilter', () => {}); jest.mock('../vector_layer', () => {}); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js index c32b857b49171..bd074386edb3f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/create_source_editor.js @@ -15,16 +15,14 @@ import { NoIndexPatternCallout } from '../../../components/no_index_pattern_call import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiComboBox, EuiSpacer } from '@elastic/eui'; -import { ES_GEO_FIELD_TYPE } from '../../../../common/constants'; -import { isNestedField } from '../../../../../../../../src/plugins/data/public'; +import { + AGGREGATABLE_GEO_FIELD_TYPES, + getAggregatableGeoFields, +} from '../../../index_pattern_util'; import { npStart } from 'ui/new_platform'; const { IndexPatternSelect } = npStart.plugins.data.ui; -function filterGeoField({ type }) { - return [ES_GEO_FIELD_TYPE.GEO_POINT].includes(type); -} - const requestTypeOptions = [ { label: i18n.translate('xpack.maps.source.esGeoGrid.gridRectangleDropdownOption', { @@ -116,9 +114,7 @@ export class CreateSourceEditor extends Component { }); //make default selection - const geoFields = indexPattern.fields - .filter(field => !isNestedField(field)) - .filter(filterGeoField); + const geoFields = getAggregatableGeoFields(indexPattern.fields); if (geoFields[0]) { this._onGeoFieldSelect(geoFields[0].name); } @@ -173,10 +169,9 @@ export class CreateSourceEditor extends Component { })} value={this.state.geoField} onChange={this._onGeoFieldSelect} - filterField={filterGeoField} fields={ this.state.indexPattern - ? this.state.indexPattern.fields.filter(field => !isNestedField(field)) + ? getAggregatableGeoFields(this.state.indexPattern.fields) : undefined } /> @@ -223,7 +218,7 @@ export class CreateSourceEditor extends Component { placeholder={i18n.translate('xpack.maps.source.esGeoGrid.indexPatternPlaceholder', { defaultMessage: 'Select index pattern', })} - fieldTypes={[ES_GEO_FIELD_TYPE.GEO_POINT]} + fieldTypes={AGGREGATABLE_GEO_FIELD_TYPES} onNoIndexPatterns={this._onNoIndexPatterns} /> diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js index 6adb5dd568e23..cb8b43a6c312b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_geo_grid_source/es_geo_grid_source.js @@ -10,8 +10,7 @@ import uuid from 'uuid/v4'; import { VECTOR_SHAPE_TYPES } from '../vector_feature_types'; import { HeatmapLayer } from '../../heatmap_layer'; import { VectorLayer } from '../../vector_layer'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggConfigs } from 'ui/agg_types'; +import { AggConfigs, Schemas } from 'ui/agg_types'; import { tabifyAggResponse } from 'ui/agg_response/tabify'; import { convertToGeoJson } from './convert_to_geojson'; import { VectorStyle } from '../../styles/vector/vector_style'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js index 85d63c9da8a31..5e4727cd7ab0c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/create_source_editor.js @@ -14,16 +14,13 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFormRow, EuiCallOut } from '@elastic/eui'; -import { ES_GEO_FIELD_TYPE } from '../../../../common/constants'; -import { isNestedField } from '../../../../../../../../src/plugins/data/public'; +import { + AGGREGATABLE_GEO_FIELD_TYPES, + getAggregatableGeoFields, +} from '../../../index_pattern_util'; import { npStart } from 'ui/new_platform'; const { IndexPatternSelect } = npStart.plugins.data.ui; -const GEO_FIELD_TYPES = [ES_GEO_FIELD_TYPE.GEO_POINT]; - -function filterGeoField({ type }) { - return GEO_FIELD_TYPES.includes(type); -} export class CreateSourceEditor extends Component { static propTypes = { @@ -92,10 +89,7 @@ export class CreateSourceEditor extends Component { return; } - const geoFields = indexPattern.fields - .filter(field => !isNestedField(field)) - .filter(filterGeoField); - + const geoFields = getAggregatableGeoFields(indexPattern.fields); this.setState({ isLoadingIndexPattern: false, indexPattern: indexPattern, @@ -136,6 +130,9 @@ export class CreateSourceEditor extends Component { return null; } + const fields = this.state.indexPattern + ? getAggregatableGeoFields(this.state.indexPattern.fields) + : undefined; return ( @@ -165,12 +161,7 @@ export class CreateSourceEditor extends Component { })} value={this.state.destGeoField} onChange={this._onDestGeoSelect} - filterField={filterGeoField} - fields={ - this.state.indexPattern - ? this.state.indexPattern.fields.filter(field => !isNestedField(field)) - : undefined - } + fields={fields} /> @@ -190,7 +181,7 @@ export class CreateSourceEditor extends Component { placeholder={i18n.translate('xpack.maps.source.pewPew.indexPatternPlaceholder', { defaultMessage: 'Select index pattern', })} - fieldTypes={GEO_FIELD_TYPES} + fieldTypes={AGGREGATABLE_GEO_FIELD_TYPES} /> ); diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js index e6faf4146435d..5d571967d53e8 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_pew_pew_source/es_pew_pew_source.js @@ -20,8 +20,7 @@ import { i18n } from '@kbn/i18n'; import { SOURCE_DATA_ID_ORIGIN, ES_PEW_PEW, COUNT_PROP_NAME } from '../../../../common/constants'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { convertToLines } from './convert_to_lines'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggConfigs } from 'ui/agg_types'; +import { AggConfigs, Schemas } from 'ui/agg_types'; import { AbstractESAggSource } from '../es_agg_source'; import { DynamicStyleProperty } from '../../styles/vector/properties/dynamic_style_property'; import { COLOR_GRADIENTS } from '../../styles/color_utils'; diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap index 9afe22a5f4550..80368fd5d5e3e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/__snapshots__/update_source_editor.test.js.snap @@ -53,7 +53,6 @@ exports[`should enable sort order select when sort field provided 1`] = ` @@ -230,7 +228,6 @@ exports[`should render top hits form when useTopHits is true 1`] = ` diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js index 69e4c09eed118..ad55a279f9cd7 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_search_source/create_source_editor.js @@ -26,8 +26,13 @@ import { isNestedField } from '../../../../../../../../src/plugins/data/public'; import { npStart } from 'ui/new_platform'; const { IndexPatternSelect } = npStart.plugins.data.ui; -function filterGeoField(field) { - return [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE].includes(field.type); +function getGeoFields(fields) { + return fields.filter(field => { + return ( + !isNestedField(field) && + [ES_GEO_FIELD_TYPE.GEO_POINT, ES_GEO_FIELD_TYPE.GEO_SHAPE].includes(field.type) + ); + }); } const RESET_INDEX_PATTERN_STATE = { indexPattern: undefined, @@ -125,9 +130,7 @@ export class CreateSourceEditor extends Component { }); //make default selection - const geoFields = indexPattern.fields - .filter(field => !isNestedField(field)) - .filter(filterGeoField); + const geoFields = getGeoFields(indexPattern.fields); if (geoFields[0]) { this.onGeoFieldSelect(geoFields[0].name); } @@ -180,11 +183,8 @@ export class CreateSourceEditor extends Component { })} value={this.state.geoField} onChange={this.onGeoFieldSelect} - filterField={filterGeoField} fields={ - this.state.indexPattern - ? this.state.indexPattern.fields.filter(field => !isNestedField(field)) - : undefined + this.state.indexPattern ? getGeoFields(this.state.indexPattern.fields) : undefined } /> diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js index 443668984b0b4..7d7a2e159d128 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.js @@ -6,8 +6,7 @@ import _ from 'lodash'; -import { Schemas } from 'ui/vis/editors/default/schemas'; -import { AggConfigs } from 'ui/agg_types'; +import { AggConfigs, Schemas } from 'ui/agg_types'; import { i18n } from '@kbn/i18n'; import { COUNT_PROP_LABEL, diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js index e63a322eafe01..ffaaf2d705b5c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/es_term_source.test.js @@ -8,10 +8,9 @@ import { ESTermSource, extractPropertiesMap } from './es_term_source'; jest.mock('ui/new_platform'); jest.mock('../vector_layer', () => {}); -jest.mock('ui/vis/editors/default/schemas', () => ({ +jest.mock('ui/agg_types', () => ({ Schemas: function() {}, })); -jest.mock('ui/agg_types', () => {}); jest.mock('ui/timefilter', () => {}); const indexPatternTitle = 'myIndex'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss b/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss index 9060a4a98a4bc..6d332ee878d95 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss +++ b/x-pack/legacy/plugins/maps/public/layers/styles/_index.scss @@ -1,3 +1,4 @@ @import './components/color_gradient'; @import './vector/components/static_dynamic_style_row'; @import './vector/components/color/color_stops'; +@import './vector/components/symbol/icon_select'; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/__snapshots__/vector_style_symbol_editor.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/__snapshots__/vector_style_symbol_editor.test.js.snap deleted file mode 100644 index 43ba2108242d5..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/__snapshots__/vector_style_symbol_editor.test.js.snap +++ /dev/null @@ -1,98 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Should render icon select when symbolized as Icon 1`] = ` - - - - - - - -`; - -exports[`Should render symbol select when symbolized as Circle 1`] = ` - - - - - -`; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js index d5948d5539bae..d52c3dbcfa1df 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops_categorical.js @@ -11,12 +11,12 @@ import { EuiFieldText } from '@elastic/eui'; import { addCategoricalRow, isCategoricalStopsInvalid, - getOtherCategoryLabel, DEFAULT_CUSTOM_COLOR, DEFAULT_NEXT_COLOR, } from './color_stops_utils'; import { i18n } from '@kbn/i18n'; import { ColorStops } from './color_stops'; +import { getOtherCategoryLabel } from '../../style_util'; export const ColorStopsCategorical = ({ colorStops = [ diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js index 16cfd34c95ab3..c5fdda70c4eb4 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js @@ -22,6 +22,10 @@ export function getVectorStyleLabel(styleName) { return i18n.translate('xpack.maps.styles.vector.borderWidthLabel', { defaultMessage: 'Border width', }); + case VECTOR_STYLES.ICON: + return i18n.translate('xpack.maps.styles.vector.iconLabel', { + defaultMessage: 'Icon', + }); case VECTOR_STYLES.ICON_SIZE: return i18n.translate('xpack.maps.styles.vector.symbolSizeLabel', { defaultMessage: 'Symbol size', diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js new file mode 100644 index 0000000000000..cf65a807ae83e --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/category.js @@ -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; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { VECTOR_STYLES } from '../../vector_style_defaults'; +import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { VectorIcon } from './vector_icon'; + +export function Category({ styleName, label, color, isLinesOnly, isPointsOnly, symbolId }) { + function renderIcon() { + if (styleName === VECTOR_STYLES.LABEL_COLOR) { + return ( + + Tx + + ); + } + + return ( + + ); + } + + return ( + + + + {label} + + {renderIcon()} + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js index 8ba4d69c35ec6..301d64e676703 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/symbol_icon.js @@ -76,7 +76,23 @@ export class SymbolIcon extends Component { return null; } - return {this.props.symbolId}; + const { + symbolId, // eslint-disable-line no-unused-vars + fill, // eslint-disable-line no-unused-vars + stroke, // eslint-disable-line no-unused-vars + strokeWidth, // eslint-disable-line no-unused-vars + ...rest + } = this.props; + + return ( + {this.props.symbolId} + ); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js new file mode 100644 index 0000000000000..28d5454afa4ba --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_map_select.js @@ -0,0 +1,92 @@ +/* + * 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 React, { Component, Fragment } from 'react'; + +import { EuiSuperSelect, EuiSpacer } from '@elastic/eui'; + +const CUSTOM_MAP = 'CUSTOM_MAP'; + +export class StyleMapSelect extends Component { + state = {}; + + static getDerivedStateFromProps(nextProps, prevState) { + if (nextProps.customMapStops === prevState.prevPropsCustomMapStops) { + return null; + } + + return { + prevPropsCustomMapStops: nextProps.customMapStops, // reset tracker to latest value + customMapStops: nextProps.customMapStops, // reset customMapStops to latest value + }; + } + + _onMapSelect = selectedValue => { + const useCustomMap = selectedValue === CUSTOM_MAP; + this.props.onChange({ + selectedMapId: useCustomMap ? null : selectedValue, + useCustomMap, + }); + }; + + _onCustomMapChange = ({ customMapStops, isInvalid }) => { + // Manage invalid custom map in local state + if (isInvalid) { + this.setState({ customMapStops }); + return; + } + + this.props.onChange({ + useCustomMap: true, + customMapStops, + }); + }; + + _renderCustomStopsInput() { + if (!this.props.useCustomMap) { + return null; + } + + return ( + + + {this.props.renderCustomStopsInput(this._onCustomMapChange)} + + ); + } + + render() { + const mapOptionsWithCustom = [ + { + value: CUSTOM_MAP, + inputDisplay: this.props.customOptionLabel, + }, + ...this.props.options, + ]; + + let valueOfSelected; + if (this.props.useCustomMap) { + valueOfSelected = CUSTOM_MAP; + } else { + valueOfSelected = this.props.options.find(option => option.value === this.props.selectedMapId) + ? this.props.selectedMapId + : ''; + } + + return ( + + + {this._renderCustomStopsInput()} + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap new file mode 100644 index 0000000000000..b4b7a3fcf28fa --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/__snapshots__/icon_select.test.js.snap @@ -0,0 +1,81 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should render icon select 1`] = ` + + + } + readOnly={true} + value="symbol1" + /> + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" +> + + , + "value": "symbol1", + }, + Object { + "label": "symbol2", + "prepend": , + "value": "symbol2", + }, + ] + } + searchable={true} + singleSelection={false} + > + + + + +`; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss new file mode 100644 index 0000000000000..5e69d97131095 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/_icon_select.scss @@ -0,0 +1,3 @@ +.mapIconSelectSymbol__inputButton { + margin-left: $euiSizeS; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js new file mode 100644 index 0000000000000..9a0d73cef616c --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/dynamic_icon_form.js @@ -0,0 +1,70 @@ +/* + * 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, { Fragment } from 'react'; +import { FieldSelect } from '../field_select'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { IconMapSelect } from './icon_map_select'; + +export function DynamicIconForm({ + fields, + isDarkMode, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, + symbolOptions, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + const { name, origin } = field; + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field: { name, origin }, + }); + }; + + const onIconMapChange = newOptions => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + ...newOptions, + }); + }; + + function renderIconMapSelect() { + if (!styleOptions.field || !styleOptions.field.name) { + return null; + } + + return ( + + ); + } + + return ( + + + {staticDynamicSelect} + + + + + + {renderIconMapSelect()} + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js new file mode 100644 index 0000000000000..a8bb94d1d9ce4 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_map_select.js @@ -0,0 +1,54 @@ +/* + * 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 React from 'react'; + +import { StyleMapSelect } from '../style_map_select'; +import { i18n } from '@kbn/i18n'; +import { getIconPaletteOptions } from '../../symbol_utils'; +import { IconStops } from './icon_stops'; + +export function IconMapSelect({ + customIconStops, + iconPaletteId, + isDarkMode, + onChange, + symbolOptions, + useCustomIconMap, +}) { + function onMapSelectChange({ customMapStops, selectedMapId, useCustomMap }) { + onChange({ + customIconStops: customMapStops, + iconPaletteId: selectedMapId, + useCustomIconMap: useCustomMap, + }); + } + + function renderCustomIconStopsInput(onCustomMapChange) { + return ( + + ); + } + + return ( + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js new file mode 100644 index 0000000000000..03cd1ac14a013 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.js @@ -0,0 +1,138 @@ +/* + * 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 React, { Component } from 'react'; +import { + EuiFormControlLayout, + EuiFieldText, + EuiPopover, + EuiPopoverTitle, + EuiFocusTrap, + keyCodes, + EuiSelectable, +} from '@elastic/eui'; +import { SymbolIcon } from '../legend/symbol_icon'; + +function isKeyboardEvent(event) { + return typeof event === 'object' && 'keyCode' in event; +} + +export class IconSelect extends Component { + state = { + isPopoverOpen: false, + }; + + _closePopover = () => { + this.setState({ isPopoverOpen: false }); + }; + + _openPopover = () => { + this.setState({ isPopoverOpen: true }); + }; + + _togglePopover = () => { + this.setState(prevState => ({ + isPopoverOpen: !prevState.isPopoverOpen, + })); + }; + + _handleKeyboardActivity = e => { + if (isKeyboardEvent(e)) { + if (e.keyCode === keyCodes.ENTER) { + e.preventDefault(); + this._togglePopover(); + } else if (e.keyCode === keyCodes.DOWN) { + this._openPopover(); + } + } + }; + + _onIconSelect = options => { + const selectedOption = options.find(option => { + return option.checked === 'on'; + }); + + if (selectedOption) { + this.props.onChange(selectedOption.value); + } + this._closePopover(); + }; + + _renderPopoverButton() { + const { isDarkMode, value } = this.props; + return ( + + + } + /> + + ); + } + + _renderIconSelectable() { + const { isDarkMode } = this.props; + const options = this.props.symbolOptions.map(({ value, label }) => { + return { + value, + label, + prepend: ( + + ), + }; + }); + + return ( + + {(list, search) => ( +
+ {search} + {list} +
+ )} +
+ ); + } + + render() { + return ( + + {this._renderIconSelectable()} + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js new file mode 100644 index 0000000000000..56dce6fad8386 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_select.test.js @@ -0,0 +1,28 @@ +/* + * 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 React from 'react'; +import { shallow } from 'enzyme'; + +import { IconSelect } from './icon_select'; + +const symbolOptions = [ + { value: 'symbol1', label: 'symbol1' }, + { value: 'symbol2', label: 'symbol2' }, +]; + +test('Should render icon select', () => { + const component = shallow( + {}} + symbolOptions={symbolOptions} + isDarkMode={false} + /> + ); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js new file mode 100644 index 0000000000000..a655a4434ddaa --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/icon_stops.js @@ -0,0 +1,132 @@ +/* + * 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 React from 'react'; +import { DEFAULT_ICON } from '../../../../../../common/constants'; +import { i18n } from '@kbn/i18n'; +import { getOtherCategoryLabel } from '../../style_util'; +import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldText } from '@elastic/eui'; +import { IconSelect } from './icon_select'; + +function isDuplicateStop(targetStop, iconStops) { + const stops = iconStops.filter(({ stop }) => { + return targetStop === stop; + }); + return stops.length > 1; +} + +const DEFAULT_ICON_STOPS = [ + { stop: null, icon: DEFAULT_ICON }, //first stop is the "other" color + { stop: '', icon: DEFAULT_ICON }, +]; + +export function IconStops({ iconStops = DEFAULT_ICON_STOPS, isDarkMode, onChange, symbolOptions }) { + return iconStops.map(({ stop, icon }, index) => { + const onIconSelect = selectedIconId => { + const newIconStops = [...iconStops]; + newIconStops[index] = { + ...iconStops[index], + icon: selectedIconId, + }; + onChange({ customMapStops: newIconStops }); + }; + const onStopChange = e => { + const newStopValue = e.target.value; + const newIconStops = [...iconStops]; + newIconStops[index] = { + ...iconStops[index], + stop: newStopValue, + }; + onChange({ + customMapStops: newIconStops, + isInvalid: isDuplicateStop(newStopValue, iconStops), + }); + }; + const onAdd = () => { + onChange({ + customMapStops: [ + ...iconStops.slice(0, index + 1), + { + stop: '', + icon: DEFAULT_ICON, + }, + ...iconStops.slice(index + 1), + ], + }); + }; + const onRemove = () => { + onChange({ + iconStops: [...iconStops.slice(0, index), ...iconStops.slice(index + 1)], + }); + }; + + let deleteButton; + if (index > 0) { + deleteButton = ( + + ); + } + + const errors = []; + // TODO check for duplicate values and add error messages here + + const isOtherCategoryRow = index === 0; + return ( + +
+ + + + + + + + +
+ {deleteButton} + +
+
+
+ ); + }); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js new file mode 100644 index 0000000000000..b20d8f2eba162 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/static_icon_form.js @@ -0,0 +1,35 @@ +/* + * 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 React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { IconSelect } from './icon_select'; + +export function StaticIconForm({ + isDarkMode, + onStaticStyleChange, + staticDynamicSelect, + styleProperty, + symbolOptions, +}) { + const onChange = selectedIconId => { + onStaticStyleChange(styleProperty.getStyleName(), { value: selectedIconId }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js new file mode 100644 index 0000000000000..d5ec09f515954 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_icon_editor.js @@ -0,0 +1,31 @@ +/* + * 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 React from 'react'; + +import chrome from 'ui/chrome'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicIconForm } from './dynamic_icon_form'; +import { StaticIconForm } from './static_icon_form'; +import { SYMBOL_OPTIONS } from '../../symbol_utils'; + +export function VectorStyleIconEditor(props) { + const iconForm = props.styleProperty.isDynamic() ? ( + + ) : ( + + ); + + return {iconForm}; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js new file mode 100644 index 0000000000000..9394e5c068569 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/symbol/vector_style_symbolize_as_editor.js @@ -0,0 +1,61 @@ +/* + * 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 React from 'react'; + +import { EuiFormRow, EuiButtonGroup } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; +import { SYMBOLIZE_AS_TYPES } from '../../../../../../common/constants'; +import { VECTOR_STYLES } from '../../vector_style_defaults'; + +const SYMBOLIZE_AS_OPTIONS = [ + { + id: SYMBOLIZE_AS_TYPES.CIRCLE, + label: i18n.translate('xpack.maps.vector.symbolAs.circleLabel', { + defaultMessage: 'marker', + }), + }, + { + id: SYMBOLIZE_AS_TYPES.ICON, + label: i18n.translate('xpack.maps.vector.symbolAs.IconLabel', { + defaultMessage: 'icon', + }), + }, +]; + +export function VectorStyleSymbolizeAsEditor({ styleProperty, handlePropertyChange }) { + const styleOptions = styleProperty.getOptions(); + const selectedOption = SYMBOLIZE_AS_OPTIONS.find(({ id }) => { + return id === styleOptions.value; + }); + + const onSymbolizeAsChange = optionId => { + const styleDescriptor = { + options: { + value: optionId, + }, + }; + handlePropertyChange(VECTOR_STYLES.SYMBOLIZE_AS, styleDescriptor); + }; + + return ( + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 0784e2a8cc355..9299022b7895b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -7,10 +7,10 @@ import _ from 'lodash'; import React, { Component, Fragment } from 'react'; -import chrome from 'ui/chrome'; import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; -import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; +import { VectorStyleSymbolizeAsEditor } from './symbol/vector_style_symbolize_as_editor'; +import { VectorStyleIconEditor } from './symbol/vector_style_icon_editor'; import { VectorStyleLabelEditor } from './label/vector_style_label_editor'; import { VectorStyleLabelBorderSizeEditor } from './label/vector_style_label_border_size_editor'; import { VectorStyle } from '../vector_style'; @@ -22,9 +22,7 @@ import { } from '../vector_style_defaults'; import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_utils'; import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types'; -import { SYMBOLIZE_AS_ICON } from '../vector_constants'; import { i18n } from '@kbn/i18n'; -import { SYMBOL_OPTIONS } from '../symbol_utils'; import { EuiSpacer, EuiButtonGroup, EuiFormRow, EuiSwitch } from '@elastic/eui'; @@ -288,34 +286,55 @@ export class VectorStyleEditor extends Component { } _renderPointProperties() { - let iconOrientation; - if (this.props.symbolDescriptor.options.symbolizeAs === SYMBOLIZE_AS_ICON) { - iconOrientation = ( - + let iconOrientationEditor; + let iconEditor; + if (this.props.styleProperties[VECTOR_STYLES.SYMBOLIZE_AS].isSymbolizedAsIcon()) { + iconOrientationEditor = ( + + + + + ); + iconEditor = ( + + + + ); } return ( - + {iconEditor} + {this._renderFillColor()} @@ -325,8 +344,7 @@ export class VectorStyleEditor extends Component { {this._renderLineWidth()} - {iconOrientation} - + {iconOrientationEditor} {this._renderSymbolSize()} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.js deleted file mode 100644 index 29be736b432f9..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.js +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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 React, { Fragment } from 'react'; - -import { - EuiFlexGroup, - EuiFlexItem, - EuiFormRow, - EuiButtonGroup, - EuiSpacer, - EuiComboBox, -} from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; -import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from '../vector_constants'; -import { SymbolIcon } from './legend/symbol_icon'; - -const SYMBOLIZE_AS_OPTIONS = [ - { - id: SYMBOLIZE_AS_CIRCLE, - label: i18n.translate('xpack.maps.vector.symbolAs.circleLabel', { - defaultMessage: 'circle marker', - }), - }, - { - id: SYMBOLIZE_AS_ICON, - label: i18n.translate('xpack.maps.vector.symbolAs.IconLabel', { - defaultMessage: 'icon', - }), - }, -]; - -export function VectorStyleSymbolEditor({ - styleOptions, - handlePropertyChange, - symbolOptions, - isDarkMode, -}) { - const renderSymbolizeAsSelect = () => { - const selectedOption = SYMBOLIZE_AS_OPTIONS.find(({ id }) => { - return id === styleOptions.symbolizeAs; - }); - - const onSymbolizeAsChange = optionId => { - const styleDescriptor = { - options: { - ...styleOptions, - symbolizeAs: optionId, - }, - }; - handlePropertyChange('symbol', styleDescriptor); - }; - - return ( - - ); - }; - - const renderSymbolSelect = () => { - const selectedOption = symbolOptions.find(({ value }) => { - return value === styleOptions.symbolId; - }); - - const onSymbolChange = selectedOptions => { - if (!selectedOptions || selectedOptions.length === 0) { - return; - } - - const styleDescriptor = { - options: { - ...styleOptions, - symbolId: selectedOptions[0].value, - }, - }; - handlePropertyChange('symbol', styleDescriptor); - }; - - const renderOption = ({ value, label }) => { - return ( - - - - - {label} - - ); - }; - - return ( - - ); - }; - - return ( - - - {renderSymbolizeAsSelect()} - - - {styleOptions.symbolizeAs !== SYMBOLIZE_AS_CIRCLE && ( - - - {renderSymbolSelect()} - - )} - - ); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.test.js deleted file mode 100644 index 4e5c8e8a2563b..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_symbol_editor.test.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 React from 'react'; -import { shallow } from 'enzyme'; - -import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from '../vector_constants'; -import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; - -const symbolOptions = [ - { value: 'symbol1', label: 'symbol1' }, - { value: 'symbol2', label: 'symbol2' }, -]; - -const defaultProps = { - styleOptions: { - symbolizeAs: SYMBOLIZE_AS_CIRCLE, - symbolId: symbolOptions[0].value, - }, - handlePropertyChange: () => {}, - symbolOptions, - isDarkMode: false, -}; - -test('Should render symbol select when symbolized as Circle', () => { - const component = shallow(); - - expect(component).toMatchSnapshot(); -}); - -test('Should render icon select when symbolized as Icon', () => { - const component = shallow( - - ); - - expect(component).toMatchSnapshot(); -}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap index 97acffae15a85..ab47e88bb3143 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap @@ -11,82 +11,36 @@ exports[`Should render categorical legend with breaks from default 1`] = ` direction="column" gutterSize="none" > - - - - - US_format - - - - - - - - - - - - CN_format - - - - - - - - - - - - - Other - - - - - - - - + + + + Other + + } + styleName="lineColor" + /> - - - - - 0_format - - - - - - - - - - - - 10_format - - - - - - - + label="0_format" + styleName="lineColor" + /> + - Tx - - ); - } - - const fillColor = this.getStyleName() === VECTOR_STYLES.FILL_COLOR ? color : 'none'; - return ( - - ); - } - _getColorRampStops() { return this._options.useCustomColorRamp && this._options.customColorRamp ? this._options.customColorRamp @@ -289,48 +263,42 @@ export class DynamicColorProperty extends DynamicStyleProperty { } } - _renderColorbreaks({ isLinesOnly, isPointsOnly, symbolId }) { + renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly, symbolId }) { + const categories = []; const { stops, defaultColor } = this._getColorStops(); - const colorAndLabels = stops.map(config => { - return { - label: this.formatField(config.stop), - color: config.color, - }; + stops.map(({ stop, color }) => { + categories.push( + + ); }); if (defaultColor) { - colorAndLabels.push({ - label: {getOtherCategoryLabel()}, - color: defaultColor, - }); - } - - return colorAndLabels.map((config, index) => { - return ( - - - - {config.label} - - - {this._renderStopIcon(config.color, isLinesOnly, isPointsOnly, symbolId)} - - - + categories.push( + {getOtherCategoryLabel()}} + color={defaultColor} + isLinesOnly={isLinesOnly} + isPointsOnly={isPointsOnly} + symbolId={symbolId} + /> ); - }); - } + } - renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly, symbolId }) { return (
- - {this._renderColorbreaks({ - isPointsOnly, - isLinesOnly, - symbolId, - })} + + {categories} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js new file mode 100644 index 0000000000000..c0e56f962db74 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_icon_property.js @@ -0,0 +1,159 @@ +/* + * 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 { getOtherCategoryLabel, assignCategoriesToPalette } from '../style_util'; +import { DynamicStyleProperty } from './dynamic_style_property'; +import { getIconPalette, getMakiIconId, getMakiSymbolAnchor } from '../symbol_utils'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiText, + EuiToolTip, + EuiTextColor, +} from '@elastic/eui'; +import { Category } from '../components/legend/category'; + +export class DynamicIconProperty extends DynamicStyleProperty { + isOrdinal() { + return false; + } + + isCategorical() { + return true; + } + + syncIconWithMb(symbolLayerId, mbMap, iconPixelSize) { + if (this._isIconDynamicConfigComplete()) { + mbMap.setLayoutProperty( + symbolLayerId, + 'icon-image', + this._getMbIconImageExpression(iconPixelSize) + ); + mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', this._getMbIconAnchorExpression()); + } else { + mbMap.setLayoutProperty(symbolLayerId, 'icon-image', null); + mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', null); + } + } + + _getPaletteStops() { + if (this._options.useCustomIconMap && this._options.customIconStops) { + const stops = []; + for (let i = 1; i < this._options.customIconStops.length; i++) { + const { stop, icon } = this._options.customIconStops[i]; + stops.push({ + stop, + style: icon, + }); + } + + return { + fallback: + this._options.customIconStops.length > 0 ? this._options.customIconStops[0].icon : null, + stops, + }; + } + + return assignCategoriesToPalette({ + categories: _.get(this.getFieldMeta(), 'categories', []), + paletteValues: getIconPalette(this._options.iconPaletteId), + }); + } + + _getMbIconImageExpression(iconPixelSize) { + const { stops, fallback } = this._getPaletteStops(); + + if (stops.length < 1 || !fallback) { + //occurs when no data + return null; + } + + const mbStops = []; + stops.forEach(({ stop, style }) => { + mbStops.push(`${stop}`); + mbStops.push(getMakiIconId(style, iconPixelSize)); + }); + mbStops.push(getMakiIconId(fallback, iconPixelSize)); //last item is fallback style for anything that does not match provided stops + return ['match', ['to-string', ['get', this._options.field.name]], ...mbStops]; + } + + _getMbIconAnchorExpression() { + const { stops, fallback } = this._getPaletteStops(); + + if (stops.length < 1 || !fallback) { + //occurs when no data + return null; + } + + const mbStops = []; + stops.forEach(({ stop, style }) => { + mbStops.push(`${stop}`); + mbStops.push(getMakiSymbolAnchor(style)); + }); + mbStops.push(getMakiSymbolAnchor(fallback)); //last item is fallback style for anything that does not match provided stops + return ['match', ['to-string', ['get', this._options.field.name]], ...mbStops]; + } + + _isIconDynamicConfigComplete() { + return this._field && this._field.isValid(); + } + + renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly }) { + const categories = []; + const { stops, fallback } = this._getPaletteStops(); + stops.map(({ stop, style }) => { + categories.push( + + ); + }); + + if (fallback) { + categories.push( + {getOtherCategoryLabel()}} + color="grey" + isLinesOnly={isLinesOnly} + isPointsOnly={isPointsOnly} + symbolId={fallback} + /> + ); + } + + return ( +
+ + + {categories} + + + + + + + {fieldLabel} + + + + + +
+ ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index 5a4da1a80c918..dfc5c530cc90f 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -67,15 +67,15 @@ export class DynamicSizeProperty extends DynamicStyleProperty { mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', haloWidth); } - syncIconImageAndSizeWithMb(symbolLayerId, mbMap, symbolId) { - if (this._isSizeDynamicConfigComplete(this._options)) { - const iconPixels = - this._options.maxSize >= HALF_LARGE_MAKI_ICON_SIZE - ? LARGE_MAKI_ICON_SIZE - : SMALL_MAKI_ICON_SIZE; - mbMap.setLayoutProperty(symbolLayerId, 'icon-image', `${symbolId}-${iconPixels}`); + getIconPixelSize() { + return this._options.maxSize >= HALF_LARGE_MAKI_ICON_SIZE + ? LARGE_MAKI_ICON_SIZE + : SMALL_MAKI_ICON_SIZE; + } - const halfIconPixels = iconPixels / 2; + syncIconSizeWithMb(symbolLayerId, mbMap) { + if (this._isSizeDynamicConfigComplete(this._options)) { + const halfIconPixels = this.getIconPixelSize() / 2; const targetName = this.getComputedFieldName(); // Using property state instead of feature-state because layout properties do not support feature-state mbMap.setLayoutProperty(symbolLayerId, 'icon-size', [ @@ -88,7 +88,6 @@ export class DynamicSizeProperty extends DynamicStyleProperty { this._options.maxSize / halfIconPixels, ]); } else { - mbMap.setLayoutProperty(symbolLayerId, 'icon-image', null); mbMap.setLayoutProperty(symbolLayerId, 'icon-size', null); } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js new file mode 100644 index 0000000000000..3b5be083dd3c9 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_icon_property.js @@ -0,0 +1,16 @@ +/* + * 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 { StaticStyleProperty } from './static_style_property'; +import { getMakiSymbolAnchor, getMakiIconId } from '../symbol_utils'; + +export class StaticIconProperty extends StaticStyleProperty { + syncIconWithMb(symbolLayerId, mbMap, iconPixelSize) { + const symbolId = this._options.value; + mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', getMakiSymbolAnchor(symbolId)); + mbMap.setLayoutProperty(symbolLayerId, 'icon-image', getMakiIconId(symbolId, iconPixelSize)); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js index 024b446369851..2383a5932cb9b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_size_property.js @@ -24,12 +24,14 @@ export class StaticSizeProperty extends StaticStyleProperty { mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', this._options.size); } - syncIconImageAndSizeWithMb(symbolLayerId, mbMap, symbolId) { - const iconPixels = - this._options.size >= HALF_LARGE_MAKI_ICON_SIZE ? LARGE_MAKI_ICON_SIZE : SMALL_MAKI_ICON_SIZE; + getIconPixelSize() { + return this._options.size >= HALF_LARGE_MAKI_ICON_SIZE + ? LARGE_MAKI_ICON_SIZE + : SMALL_MAKI_ICON_SIZE; + } - mbMap.setLayoutProperty(symbolLayerId, 'icon-image', `${symbolId}-${iconPixels}`); - const halfIconPixels = iconPixels / 2; + syncIconSizeWithMb(symbolLayerId, mbMap) { + const halfIconPixels = this.getIconPixelSize() / 2; mbMap.setLayoutProperty(symbolLayerId, 'icon-size', this._options.size / halfIconPixels); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js new file mode 100644 index 0000000000000..9ae1ef5054e30 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/symbolize_as_property.js @@ -0,0 +1,18 @@ +/* + * 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 { AbstractStyleProperty } from './style_property'; +import { SYMBOLIZE_AS_TYPES } from '../../../../../common/constants'; + +export class SymbolizeAsProperty extends AbstractStyleProperty { + constructor(options, styleName) { + super(options, styleName); + } + + isSymbolizedAsIcon = () => { + return this.getOptions().value === SYMBOLIZE_AS_TYPES.ICON; + }; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js index 7bd60ea6502bc..2859b8c0e5a56 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js @@ -4,6 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; + +export function getOtherCategoryLabel() { + return i18n.translate('xpack.maps.styles.categorical.otherCategoryLabel', { + defaultMessage: 'Other', + }); +} + export function getComputedFieldName(styleName, fieldName) { return `${getComputedFieldNamePrefix(fieldName)}__${styleName}`; } @@ -41,3 +49,24 @@ export function scaleValue(value, range) { return (value - range.min) / range.delta; } + +export function assignCategoriesToPalette({ categories, paletteValues }) { + const stops = []; + let fallback = null; + + if (categories && categories.length && paletteValues && paletteValues.length) { + const maxLength = Math.min(paletteValues.length, categories.length + 1); + fallback = paletteValues[maxLength - 1]; + for (let i = 0; i < maxLength - 1; i++) { + stops.push({ + stop: categories[i].key, + style: paletteValues[i], + }); + } + } + + return { + stops, + fallback, + }; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js index e4df2f2bc0f58..2be31c0107193 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.test.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isOnlySingleFeatureType, scaleValue } from './style_util'; +import { isOnlySingleFeatureType, scaleValue, assignCategoriesToPalette } from './style_util'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; describe('isOnlySingleFeatureType', () => { @@ -87,3 +87,42 @@ describe('scaleValue', () => { expect(scaleValue(5, undefined)).toBe(-1); }); }); + +describe('assignCategoriesToPalette', () => { + test('Categories and palette values have same length', () => { + const categories = [{ key: 'alpah' }, { key: 'bravo' }, { key: 'charlie' }, { key: 'delta' }]; + const paletteValues = ['red', 'orange', 'yellow', 'green']; + expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({ + stops: [ + { stop: 'alpah', style: 'red' }, + { stop: 'bravo', style: 'orange' }, + { stop: 'charlie', style: 'yellow' }, + ], + fallback: 'green', + }); + }); + + test('Should More categories than palette values', () => { + const categories = [{ key: 'alpah' }, { key: 'bravo' }, { key: 'charlie' }, { key: 'delta' }]; + const paletteValues = ['red', 'orange', 'yellow']; + expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({ + stops: [ + { stop: 'alpah', style: 'red' }, + { stop: 'bravo', style: 'orange' }, + ], + fallback: 'yellow', + }); + }); + + test('Less categories than palette values', () => { + const categories = [{ key: 'alpah' }, { key: 'bravo' }]; + const paletteValues = ['red', 'orange', 'yellow', 'green', 'blue']; + expect(assignCategoriesToPalette({ categories, paletteValues })).toEqual({ + stops: [ + { stop: 'alpah', style: 'red' }, + { stop: 'bravo', style: 'orange' }, + ], + fallback: 'yellow', + }); + }); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js index 162b22319e6ef..b577d4080b879 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/symbol_utils.js @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; import maki from '@elastic/maki'; import xml2js from 'xml2js'; import { parseXmlString } from '../../../../common/parse_xml_string'; +import { SymbolIcon } from './components/legend/symbol_icon'; export const LARGE_MAKI_ICON_SIZE = 15; const LARGE_MAKI_ICON_SIZE_AS_STRING = LARGE_MAKI_ICON_SIZE.toString(); @@ -55,6 +57,12 @@ export function getMakiSymbolAnchor(symbolId) { } } +// Style descriptor stores symbolId, for example 'aircraft' +// Icons are registered in Mapbox with full maki ids, for example 'aircraft-11' +export function getMakiIconId(symbolId, iconPixelSize) { + return `${symbolId}-${iconPixelSize}`; +} + export function buildSrcUrl(svgString) { const domUrl = window.URL || window.webkitURL || window; const svg = new Blob([svgString], { type: 'image/svg+xml' }); @@ -77,3 +85,54 @@ export async function styleSvg(svgString, fill, stroke, strokeWidth) { const builder = new xml2js.Builder(); return builder.buildObject(svgXml); } + +const ICON_PALETTES = [ + { + id: 'filledShapes', + icons: ['circle', 'marker', 'square', 'star', 'triangle', 'hospital'], + }, + { + id: 'hollowShapes', + icons: [ + 'circle-stroked', + 'marker-stroked', + 'square-stroked', + 'star-stroked', + 'triangle-stroked', + ], + }, +]; + +export function getIconPaletteOptions(isDarkMode) { + return ICON_PALETTES.map(({ id, icons }) => { + const iconsDisplay = icons.map(iconId => { + const style = { + width: '10%', + position: 'relative', + height: '100%', + display: 'inline-block', + paddingTop: '4px', + }; + return ( +
+ +
+ ); + }); + return { + value: id, + inputDisplay:
{iconsDisplay}
, + }; + }); +} + +export function getIconPalette(paletteId) { + const palette = ICON_PALETTES.find(({ id }) => id === paletteId); + return palette ? [...palette.icons] : null; +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index 558df73f74595..97259a908f1e4 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -21,12 +21,11 @@ import { SOURCE_META_ID_ORIGIN, SOURCE_FORMATTERS_ID_ORIGIN, LAYER_STYLE_TYPE, + DEFAULT_ICON, } from '../../../../common/constants'; import { VectorIcon } from './components/legend/vector_icon'; import { VectorStyleLegend } from './components/legend/vector_style_legend'; import { VECTOR_SHAPE_TYPES } from '../../sources/vector_feature_types'; -import { SYMBOLIZE_AS_CIRCLE, SYMBOLIZE_AS_ICON } from './vector_constants'; -import { getMakiSymbolAnchor } from './symbol_utils'; import { getComputedFieldName, isOnlySingleFeatureType } from './style_util'; import { StaticStyleProperty } from './properties/static_style_property'; import { DynamicStyleProperty } from './properties/dynamic_style_property'; @@ -40,6 +39,9 @@ import { StaticTextProperty } from './properties/static_text_property'; import { DynamicTextProperty } from './properties/dynamic_text_property'; import { LabelBorderSizeProperty } from './properties/label_border_size_property'; import { extractColorFromStyleProperty } from './components/legend/extract_color_from_style_property'; +import { SymbolizeAsProperty } from './properties/symbolize_as_property'; +import { StaticIconProperty } from './properties/static_icon_property'; +import { DynamicIconProperty } from './properties/dynamic_icon_property'; const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT]; const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING]; @@ -69,6 +71,10 @@ export class VectorStyle extends AbstractStyle { ...VectorStyle.createDescriptor(descriptor.properties, descriptor.isTimeAware), }; + this._symbolizeAsStyleProperty = new SymbolizeAsProperty( + this._descriptor.properties[VECTOR_STYLES.SYMBOLIZE_AS].options, + VECTOR_STYLES.SYMBOLIZE_AS + ); this._lineColorStyleProperty = this._makeColorProperty( this._descriptor.properties[VECTOR_STYLES.LINE_COLOR], VECTOR_STYLES.LINE_COLOR @@ -81,10 +87,13 @@ export class VectorStyle extends AbstractStyle { this._descriptor.properties[VECTOR_STYLES.LINE_WIDTH], VECTOR_STYLES.LINE_WIDTH ); + this._iconStyleProperty = this._makeIconProperty( + this._descriptor.properties[VECTOR_STYLES.ICON] + ); this._iconSizeStyleProperty = this._makeSizeProperty( this._descriptor.properties[VECTOR_STYLES.ICON_SIZE], VECTOR_STYLES.ICON_SIZE, - this._descriptor.properties[VECTOR_STYLES.SYMBOL].options.symbolizeAs === SYMBOLIZE_AS_ICON + this._symbolizeAsStyleProperty.isSymbolizedAsIcon() ); this._iconOrientationProperty = this._makeOrientationProperty( this._descriptor.properties[VECTOR_STYLES.ICON_ORIENTATION], @@ -114,6 +123,8 @@ export class VectorStyle extends AbstractStyle { _getAllStyleProperties() { return [ + this._symbolizeAsStyleProperty, + this._iconStyleProperty, this._lineColorStyleProperty, this._fillColorStyleProperty, this._lineWidthStyleProperty, @@ -153,7 +164,6 @@ export class VectorStyle extends AbstractStyle { { @@ -527,7 +537,7 @@ export class VectorStyle extends AbstractStyle { } arePointsSymbolizedAsCircles() { - return this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_CIRCLE; + return !this._symbolizeAsStyleProperty.isSymbolizedAsIcon(); } setMBPaintProperties({ alpha, mbMap, fillLayerId, lineLayerId }) { @@ -554,23 +564,22 @@ export class VectorStyle extends AbstractStyle { } setMBSymbolPropertiesForPoints({ mbMap, symbolLayerId, alpha }) { - const symbolId = this._descriptor.properties.symbol.options.symbolId; mbMap.setLayoutProperty(symbolLayerId, 'icon-ignore-placement', true); - mbMap.setLayoutProperty(symbolLayerId, 'icon-anchor', getMakiSymbolAnchor(symbolId)); mbMap.setPaintProperty(symbolLayerId, 'icon-opacity', alpha); + this._iconStyleProperty.syncIconWithMb( + symbolLayerId, + mbMap, + this._iconSizeStyleProperty.getIconPixelSize() + ); // icon-color is only supported on SDF icons. this._fillColorStyleProperty.syncIconColorWithMb(symbolLayerId, mbMap); this._lineColorStyleProperty.syncHaloBorderColorWithMb(symbolLayerId, mbMap); this._lineWidthStyleProperty.syncHaloWidthWithMb(symbolLayerId, mbMap); - this._iconSizeStyleProperty.syncIconImageAndSizeWithMb(symbolLayerId, mbMap, symbolId); + this._iconSizeStyleProperty.syncIconSizeWithMb(symbolLayerId, mbMap); this._iconOrientationProperty.syncIconRotationWithMb(symbolLayerId, mbMap); } - arePointsSymbolizedAsCircles() { - return this._descriptor.properties.symbol.options.symbolizeAs === SYMBOLIZE_AS_CIRCLE; - } - _makeField(fieldDescriptor) { if (!fieldDescriptor || !fieldDescriptor.name) { return null; @@ -660,4 +669,23 @@ export class VectorStyle extends AbstractStyle { throw new Error(`${descriptor} not implemented`); } } + + _makeIconProperty(descriptor) { + if (!descriptor || !descriptor.options) { + return new StaticIconProperty({ value: DEFAULT_ICON }, VECTOR_STYLES.ICON); + } else if (descriptor.type === StaticStyleProperty.type) { + return new StaticIconProperty(descriptor.options, VECTOR_STYLES.ICON); + } else if (descriptor.type === DynamicStyleProperty.type) { + const field = this._makeField(descriptor.options.field); + return new DynamicIconProperty( + descriptor.options, + VECTOR_STYLES.ICON, + field, + this._getFieldMeta, + this._getFieldFormatter + ); + } else { + throw new Error(`${descriptor} not implemented`); + } + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js index 84e539794b150..cc52d44aed8d3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js @@ -87,6 +87,12 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => { options: {}, type: 'STATIC', }, + icon: { + options: { + value: 'airfield', + }, + type: 'STATIC', + }, iconOrientation: { options: { orientation: 0, @@ -138,10 +144,9 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => { }, type: 'STATIC', }, - symbol: { + symbolizeAs: { options: { - symbolId: 'airfield', - symbolizeAs: 'circle', + value: 'circle', }, }, }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js index 54af55b61ab2e..952f8766a6156 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js @@ -5,7 +5,7 @@ */ import { VectorStyle } from './vector_style'; -import { SYMBOLIZE_AS_CIRCLE, DEFAULT_ICON_SIZE } from './vector_constants'; +import { DEFAULT_ICON, SYMBOLIZE_AS_TYPES } from '../../../../common/constants'; import { COLOR_GRADIENTS, COLOR_PALETTES, @@ -14,14 +14,13 @@ import { } from '../color_utils'; import chrome from 'ui/chrome'; -const DEFAULT_ICON = 'airfield'; - export const MIN_SIZE = 1; export const MAX_SIZE = 64; export const DEFAULT_MIN_SIZE = 4; export const DEFAULT_MAX_SIZE = 32; export const DEFAULT_SIGMA = 3; export const DEFAULT_LABEL_SIZE = 14; +export const DEFAULT_ICON_SIZE = 6; export const LABEL_BORDER_SIZES = { NONE: 'NONE', @@ -31,10 +30,11 @@ export const LABEL_BORDER_SIZES = { }; export const VECTOR_STYLES = { - SYMBOL: 'symbol', + SYMBOLIZE_AS: 'symbolizeAs', FILL_COLOR: 'fillColor', LINE_COLOR: 'lineColor', LINE_WIDTH: 'lineWidth', + ICON: 'icon', ICON_SIZE: 'iconSize', ICON_ORIENTATION: 'iconOrientation', LABEL_TEXT: 'labelText', @@ -54,10 +54,9 @@ export const POLYGON_STYLES = [ export function getDefaultProperties(mapColors = []) { return { ...getDefaultStaticProperties(mapColors), - [VECTOR_STYLES.SYMBOL]: { + [VECTOR_STYLES.SYMBOLIZE_AS]: { options: { - symbolizeAs: SYMBOLIZE_AS_CIRCLE, - symbolId: DEFAULT_ICON, + value: SYMBOLIZE_AS_TYPES.CIRCLE, }, }, [VECTOR_STYLES.LABEL_BORDER_SIZE]: { @@ -78,6 +77,12 @@ export function getDefaultStaticProperties(mapColors = []) { const isDarkMode = chrome.getUiSettingsClient().get('theme:darkMode', false); return { + [VECTOR_STYLES.ICON]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + value: DEFAULT_ICON, + }, + }, [VECTOR_STYLES.FILL_COLOR]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { @@ -137,6 +142,13 @@ export function getDefaultStaticProperties(mapColors = []) { export function getDefaultDynamicProperties() { return { + [VECTOR_STYLES.ICON]: { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options: { + iconPaletteId: 'filledShapes', + field: undefined, + }, + }, [VECTOR_STYLES.FILL_COLOR]: { type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { diff --git a/x-pack/legacy/plugins/maps/public/plugin.ts b/x-pack/legacy/plugins/maps/public/plugin.ts index 0df7109852486..e5f765a11d219 100644 --- a/x-pack/legacy/plugins/maps/public/plugin.ts +++ b/x-pack/legacy/plugins/maps/public/plugin.ts @@ -10,7 +10,7 @@ import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { MapListing } from './components/map_listing'; // @ts-ignore -import { setLicenseId } from './kibana_services'; +import { setLicenseId, setInspector } from './kibana_services'; /** * These are the interfaces with your public contracts. You should export these @@ -39,5 +39,7 @@ export class MapsPlugin implements Plugin { } } - public start(core: CoreStart, plugins: any) {} + public start(core: CoreStart, plugins: any) { + setInspector(plugins.np.inspector); + } } diff --git a/x-pack/legacy/plugins/maps/public/reducers/map.js b/x-pack/legacy/plugins/maps/public/reducers/map.js index f933babb6f61b..234584d08a311 100644 --- a/x-pack/legacy/plugins/maps/public/reducers/map.js +++ b/x-pack/legacy/plugins/maps/public/reducers/map.js @@ -99,11 +99,8 @@ const INITIAL_STATE = { goto: null, tooltipState: null, mapState: { - zoom: 4, - center: { - lon: -100.41, - lat: 32.82, - }, + zoom: null, // setting this value does not adjust map zoom, read only value used to store current map zoom for persisting between sessions + center: null, // setting this value does not adjust map view, read only value used to store current map center for persisting between sessions scrollZoom: true, extent: null, mouseCoordinates: null, diff --git a/x-pack/legacy/plugins/maps/public/reducers/non_serializable_instances.js b/x-pack/legacy/plugins/maps/public/reducers/non_serializable_instances.js index 689212b8e5ff0..c7de2beff0cf6 100644 --- a/x-pack/legacy/plugins/maps/public/reducers/non_serializable_instances.js +++ b/x-pack/legacy/plugins/maps/public/reducers/non_serializable_instances.js @@ -5,7 +5,7 @@ */ import chrome from 'ui/chrome'; -import { RequestAdapter } from 'ui/inspector/adapters'; +import { RequestAdapter } from '../../../../../../src/plugins/inspector/public'; import { MapAdapter } from '../inspector/adapters/map_adapter'; const REGISTER_CANCEL_CALLBACK = 'REGISTER_CANCEL_CALLBACK'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/__snapshots__/top_nav.test.tsx.snap b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/__snapshots__/top_nav.test.tsx.snap deleted file mode 100644 index f9df085d2cbe7..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/__snapshots__/top_nav.test.tsx.snap +++ /dev/null @@ -1,76 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Navigation Menu: Minimal initialization. 1`] = ` - -
- -
-
-`; diff --git a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.test.tsx b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.test.tsx index b64cccc9eb9b9..e9bec02868b71 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.test.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mount, shallow } from 'enzyme'; +import { mount } from 'enzyme'; import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; import { EuiSuperDatePicker } from '@elastic/eui'; @@ -34,8 +35,13 @@ describe('Navigation Menu: ', () => { const refreshListener = jest.fn(); const refreshSubscription = mlTimefilterRefresh$.subscribe(refreshListener); - const wrapper = shallow(); - expect(wrapper).toMatchSnapshot(); + const wrapper = mount( + + + + ); + expect(wrapper.find(TopNav)).toHaveLength(1); + expect(wrapper.find('EuiSuperDatePicker')).toHaveLength(1); expect(refreshListener).toBeCalledTimes(0); refreshSubscription.unsubscribe(); diff --git a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx index eb068f40716bc..c76967455fa42 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx @@ -15,6 +15,7 @@ import { mlTimefilterTimeChange$, } from '../../../services/timefilter_refresh_service'; import { useUiContext } from '../../../contexts/ui/use_ui_context'; +import { useUrlState } from '../../../util/url_state'; interface Duration { start: string; @@ -40,9 +41,17 @@ function updateLastRefresh(timeRange: OnRefreshProps) { export const TopNav: FC = () => { const { chrome, timefilter, timeHistory } = useUiContext(); + const [globalState, setGlobalState] = useUrlState('_g'); const getRecentlyUsedRanges = getRecentlyUsedRangesFactory(timeHistory); - const [refreshInterval, setRefreshInterval] = useState(timefilter.getRefreshInterval()); + const [refreshInterval, setRefreshInterval] = useState( + globalState?.refreshInterval ?? timefilter.getRefreshInterval() + ); + useEffect(() => { + setGlobalState({ refreshInterval }); + timefilter.setRefreshInterval(refreshInterval); + }, [refreshInterval?.pause, refreshInterval?.value]); + const [time, setTime] = useState(timefilter.getTime()); const [recentlyUsedRanges, setRecentlyUsedRanges] = useState(getRecentlyUsedRanges()); const [isAutoRefreshSelectorEnabled, setIsAutoRefreshSelectorEnabled] = useState( @@ -96,20 +105,13 @@ export const TopNav: FC = () => { } function updateInterval({ - isPaused, - refreshInterval: interval, + isPaused: pause, + refreshInterval: value, }: { isPaused: boolean; refreshInterval: number; }) { - const newInterval = { - pause: isPaused, - value: interval, - }; - // Update timefilter for controllers listening for changes - timefilter.setRefreshInterval(newInterval); - // Update state - setRefreshInterval(newInterval); + setRefreshInterval({ pause, value }); } return ( diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx index 098f8f07bee44..bd1b60d92403e 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/exploration/exploration.tsx @@ -453,7 +453,7 @@ export const Exploration: FC = React.memo(({ jobId, jobStatus }) => { const MlInMemoryTableBasic = mlInMemoryTableBasicFactory(); return ( - + diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx index 30744c1a88d83..fe2676053dde3 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/regression_exploration/evaluate_panel.tsx @@ -229,7 +229,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) }, [JSON.stringify(searchQuery)]); return ( - + @@ -296,6 +296,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) = ({ jobConfig, jobStatus, searchQuery }) = ({ jobConfig, jobStatus, searchQuery }) = ({ jobConfig, jobStatus, searchQuery }) = ({ isLoading, isMSE, title }) => ( - +export const EvaluateStat: FC = ({ isLoading, isMSE, title, dataTestSubj }) => ( + = React.memo( : searchError; return ( - + @@ -461,6 +461,7 @@ export const ResultsTable: FC = React.memo( {docFields.map(({ name }) => ( field.name === name)} onChange={() => toggleColumn(name)} diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx index fc3c00cbcf3e3..eb87bfd96c149 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/actions.tsx @@ -47,7 +47,7 @@ export const AnalyticsViewAction = { aria-label={i18n.translate('xpack.ml.dataframe.analyticsList.viewAriaLabel', { defaultMessage: 'View', })} - data-test-sub="mlAnalyticsJobViewButton" + data-test-subj="mlAnalyticsJobViewButton" > {i18n.translate('xpack.ml.dataframe.analyticsList.viewActionName', { defaultMessage: 'View', diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx index 34f281cec57d3..07ae2c176c363 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/columns.tsx @@ -191,6 +191,7 @@ export const getColumns = ( }), sortable: true, truncateText: true, + 'data-test-subj': 'mlAnalyticsTableColumnJobDescription', }, { field: DataFrameAnalyticsListColumn.configSourceIndex, diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index bc457f1a3fe89..4455e6e99ada7 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -89,6 +89,7 @@ export const TimeSeriesExplorerUrlStateManager: FC(); const refresh = useRefresh(); @@ -295,6 +296,7 @@ export const TimeSeriesExplorerUrlStateManager: FC (cal.events = [])); // loop events and combine with related calendars @@ -55,24 +77,28 @@ export class CalendarManager { * @param calendarIds * @returns {Promise<*>} */ - async getCalendarsByIds(calendarIds) { + async getCalendarsByIds(calendarIds: string) { try { - const calendars = await this.getAllCalendars(); + const calendars: Calendar[] = await this.getAllCalendars(); return calendars.filter(calendar => calendarIds.includes(calendar.calendar_id)); } catch (error) { throw Boom.badRequest(error); } } - async newCalendar(calendar) { + async newCalendar(calendar: FormCalendar) { const calendarId = calendar.calendarId; const events = calendar.events; delete calendar.calendarId; delete calendar.events; try { - await this.callWithRequest('ml.addCalendar', { calendarId, body: calendar }); + await this._client('ml.addCalendar', { + calendarId, + body: calendar, + }); + if (events.length) { - await this.eventManager.addEvents(calendarId, events); + await this._eventManager.addEvents(calendarId, events); } // return the newly created calendar @@ -82,38 +108,38 @@ export class CalendarManager { } } - async updateCalendar(calendarId, calendar) { - const origCalendar = await this.getCalendar(calendarId); + async updateCalendar(calendarId: string, calendar: Calendar) { + const origCalendar: Calendar = await this.getCalendar(calendarId); try { // update job_ids - const jobsToAdd = _.difference(calendar.job_ids, origCalendar.job_ids); - const jobsToRemove = _.difference(origCalendar.job_ids, calendar.job_ids); + const jobsToAdd = difference(calendar.job_ids, origCalendar.job_ids); + const jobsToRemove = difference(origCalendar.job_ids, calendar.job_ids); // workout the differences between the original events list and the new one // if an event has no event_id, it must be new const eventsToAdd = calendar.events.filter( - event => origCalendar.events.find(e => this.eventManager.isEqual(e, event)) === undefined + event => origCalendar.events.find(e => this._eventManager.isEqual(e, event)) === undefined ); // if an event in the original calendar cannot be found, it must have been deleted - const eventsToRemove = origCalendar.events.filter( - event => calendar.events.find(e => this.eventManager.isEqual(e, event)) === undefined + const eventsToRemove: CalendarEvent[] = origCalendar.events.filter( + event => calendar.events.find(e => this._eventManager.isEqual(e, event)) === undefined ); // note, both of the loops below could be removed if the add and delete endpoints // allowed multiple job_ids - //add all new jobs + // add all new jobs if (jobsToAdd.length) { - await this.callWithRequest('ml.addJobToCalendar', { + await this._client('ml.addJobToCalendar', { calendarId, jobId: jobsToAdd.join(','), }); } - //remove all removed jobs + // remove all removed jobs if (jobsToRemove.length) { - await this.callWithRequest('ml.removeJobFromCalendar', { + await this._client('ml.removeJobFromCalendar', { calendarId, jobId: jobsToRemove.join(','), }); @@ -121,13 +147,13 @@ export class CalendarManager { // add all new events if (eventsToAdd.length !== 0) { - await this.eventManager.addEvents(calendarId, eventsToAdd); + await this._eventManager.addEvents(calendarId, eventsToAdd); } // remove all removed events await Promise.all( eventsToRemove.map(async event => { - await this.eventManager.deleteEvent(calendarId, event.event_id); + await this._eventManager.deleteEvent(calendarId, event.event_id); }) ); } catch (error) { @@ -138,7 +164,7 @@ export class CalendarManager { return await this.getCalendar(calendarId); } - async deleteCalendar(calendarId) { - return this.callWithRequest('ml.deleteCalendar', { calendarId }); + async deleteCalendar(calendarId: string) { + return this._client('ml.deleteCalendar', { calendarId }); } } diff --git a/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.js b/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts similarity index 51% rename from x-pack/legacy/plugins/ml/server/models/calendar/event_manager.js rename to x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts index 8bdb5dcf4a3e3..19f2eda466179 100644 --- a/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.js +++ b/x-pack/legacy/plugins/ml/server/models/calendar/event_manager.ts @@ -6,14 +6,24 @@ import Boom from 'boom'; +export interface CalendarEvent { + calendar_id?: string; + event_id?: string; + description: string; + start_time: number; + end_time: number; +} + export class EventManager { - constructor(callWithRequest) { - this.callWithRequest = callWithRequest; + private _client: any; + constructor(client: any) { + this._client = client; } - async getCalendarEvents(calendarId) { + async getCalendarEvents(calendarId: string) { try { - const resp = await this.callWithRequest('ml.events', { calendarId }); + const resp = await this._client('ml.events', { calendarId }); + return resp.events; } catch (error) { throw Boom.badRequest(error); @@ -21,31 +31,38 @@ export class EventManager { } // jobId is optional - async getAllEvents(jobId) { + async getAllEvents(jobId?: string) { const calendarId = '_all'; try { - const resp = await this.callWithRequest('ml.events', { calendarId, jobId }); + const resp = await this._client('ml.events', { + calendarId, + jobId, + }); + return resp.events; } catch (error) { throw Boom.badRequest(error); } } - async addEvents(calendarId, events) { + async addEvents(calendarId: string, events: CalendarEvent[]) { const body = { events }; try { - return await this.callWithRequest('ml.addEvent', { calendarId, body }); + return await this._client('ml.addEvent', { + calendarId, + body, + }); } catch (error) { throw Boom.badRequest(error); } } - async deleteEvent(calendarId, eventId) { - return this.callWithRequest('ml.deleteEvent', { calendarId, eventId }); + async deleteEvent(calendarId: string, eventId: string) { + return this._client('ml.deleteEvent', { calendarId, eventId }); } - isEqual(ev1, ev2) { + isEqual(ev1: CalendarEvent, ev2: CalendarEvent) { return ( ev1.event_id === ev2.event_id && ev1.description === ev2.description && diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/actions.ts b/x-pack/legacy/plugins/ml/server/models/calendar/index.ts similarity index 71% rename from x-pack/plugins/endpoint/public/applications/endpoint/store/actions.ts rename to x-pack/legacy/plugins/ml/server/models/calendar/index.ts index 796dabce1d76a..2364c3ac73811 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/actions.ts +++ b/x-pack/legacy/plugins/ml/server/models/calendar/index.ts @@ -4,6 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EndpointListAction } from './endpoint_list'; - -export type AppAction = EndpointListAction; +export { CalendarManager, Calendar, FormCalendar } from './calendar_manager'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/groups.js b/x-pack/legacy/plugins/ml/server/models/job_service/groups.js index 91f82f04a9a0c..58237b2a8a730 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/groups.js +++ b/x-pack/legacy/plugins/ml/server/models/job_service/groups.js @@ -7,7 +7,7 @@ import { CalendarManager } from '../calendar'; export function groupsProvider(callWithRequest) { - const calMngr = new CalendarManager(callWithRequest); + const calMngr = new CalendarManager(true, callWithRequest); async function getAllGroups() { const groups = {}; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/jobs.js b/x-pack/legacy/plugins/ml/server/models/job_service/jobs.js index b4b476c1f926e..e60593c9f0ed5 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/jobs.js +++ b/x-pack/legacy/plugins/ml/server/models/job_service/jobs.js @@ -22,7 +22,7 @@ export function jobsProvider(callWithRequest) { const { forceDeleteDatafeed, getDatafeedIdsByJobId } = datafeedsProvider(callWithRequest); const { getAuditMessagesSummary } = jobAuditMessagesProvider(callWithRequest); const { getLatestBucketTimestampByJob } = resultsServiceProvider(callWithRequest); - const calMngr = new CalendarManager(callWithRequest); + const calMngr = new CalendarManager(true, callWithRequest); async function forceDeleteJob(jobId) { return callWithRequest('ml.deleteJob', { jobId, force: true }); diff --git a/x-pack/legacy/plugins/ml/server/new_platform/calendars_schema.ts b/x-pack/legacy/plugins/ml/server/new_platform/calendars_schema.ts new file mode 100644 index 0000000000000..f5e59d983a9aa --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/new_platform/calendars_schema.ts @@ -0,0 +1,25 @@ +/* + * 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 { schema } from '@kbn/config-schema'; + +export const calendarSchema = { + calendar_id: schema.maybe(schema.string()), + calendarId: schema.string(), + job_ids: schema.arrayOf(schema.maybe(schema.string())), + description: schema.maybe(schema.string()), + events: schema.arrayOf( + schema.maybe( + schema.object({ + event_id: schema.maybe(schema.string()), + calendar_id: schema.maybe(schema.string()), + description: schema.maybe(schema.string()), + start_time: schema.any(), + end_time: schema.any(), + }) + ) + ), +}; diff --git a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts index 681b2ff20c8aa..2b9219b2226f5 100644 --- a/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts +++ b/x-pack/legacy/plugins/ml/server/new_platform/plugin.ts @@ -41,13 +41,11 @@ import { makeMlUsageCollector } from '../lib/ml_telemetry'; import { notificationRoutes } from '../routes/notification_settings'; // @ts-ignore: could not find declaration file for module import { systemRoutes } from '../routes/system'; -// @ts-ignore: could not find declaration file for module import { dataFrameAnalyticsRoutes } from '../routes/data_frame_analytics'; // @ts-ignore: could not find declaration file for module import { dataRecognizer } from '../routes/modules'; // @ts-ignore: could not find declaration file for module import { dataVisualizerRoutes } from '../routes/data_visualizer'; -// @ts-ignore: could not find declaration file for module import { calendars } from '../routes/calendars'; // @ts-ignore: could not find declaration file for module import { fieldsService } from '../routes/fields_service'; diff --git a/x-pack/legacy/plugins/ml/server/routes/calendars.js b/x-pack/legacy/plugins/ml/server/routes/calendars.js deleted file mode 100644 index 7a0f341ef9666..0000000000000 --- a/x-pack/legacy/plugins/ml/server/routes/calendars.js +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 { callWithRequestFactory } from '../client/call_with_request_factory'; -import { wrapError } from '../client/errors'; -import { CalendarManager } from '../models/calendar'; - -function getAllCalendars(callWithRequest) { - const cal = new CalendarManager(callWithRequest); - return cal.getAllCalendars(); -} - -function getCalendar(callWithRequest, calendarId) { - const cal = new CalendarManager(callWithRequest); - return cal.getCalendar(calendarId); -} - -function newCalendar(callWithRequest, calendar) { - const cal = new CalendarManager(callWithRequest); - return cal.newCalendar(calendar); -} - -function updateCalendar(callWithRequest, calendarId, calendar) { - const cal = new CalendarManager(callWithRequest); - return cal.updateCalendar(calendarId, calendar); -} - -function deleteCalendar(callWithRequest, calendarId) { - const cal = new CalendarManager(callWithRequest); - return cal.deleteCalendar(calendarId); -} - -function getCalendarsByIds(callWithRequest, calendarIds) { - const cal = new CalendarManager(callWithRequest); - return cal.getCalendarsByIds(calendarIds); -} - -export function calendars({ commonRouteConfig, elasticsearchPlugin, route }) { - route({ - method: 'GET', - path: '/api/ml/calendars', - handler(request) { - const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); - return getAllCalendars(callWithRequest).catch(resp => wrapError(resp)); - }, - config: { - ...commonRouteConfig, - }, - }); - - route({ - method: 'GET', - path: '/api/ml/calendars/{calendarIds}', - handler(request) { - const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); - const calendarIds = request.params.calendarIds.split(','); - if (calendarIds.length === 1) { - return getCalendar(callWithRequest, calendarIds[0]).catch(resp => wrapError(resp)); - } else { - return getCalendarsByIds(callWithRequest, calendarIds).catch(resp => wrapError(resp)); - } - }, - config: { - ...commonRouteConfig, - }, - }); - - route({ - method: 'PUT', - path: '/api/ml/calendars', - handler(request) { - const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); - const body = request.payload; - return newCalendar(callWithRequest, body).catch(resp => wrapError(resp)); - }, - config: { - ...commonRouteConfig, - }, - }); - - route({ - method: 'PUT', - path: '/api/ml/calendars/{calendarId}', - handler(request) { - const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); - const calendarId = request.params.calendarId; - const body = request.payload; - return updateCalendar(callWithRequest, calendarId, body).catch(resp => wrapError(resp)); - }, - config: { - ...commonRouteConfig, - }, - }); - - route({ - method: 'DELETE', - path: '/api/ml/calendars/{calendarId}', - handler(request) { - const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); - const calendarId = request.params.calendarId; - return deleteCalendar(callWithRequest, calendarId).catch(resp => wrapError(resp)); - }, - config: { - ...commonRouteConfig, - }, - }); -} diff --git a/x-pack/legacy/plugins/ml/server/routes/calendars.ts b/x-pack/legacy/plugins/ml/server/routes/calendars.ts new file mode 100644 index 0000000000000..19d614a4e6a22 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/routes/calendars.ts @@ -0,0 +1,155 @@ +/* + * 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 { RequestHandlerContext } from 'src/core/server'; +import { schema } from '@kbn/config-schema'; +import { licensePreRoutingFactory } from '../new_platform/licence_check_pre_routing_factory'; +import { wrapError } from '../client/error_wrapper'; +import { RouteInitialization } from '../new_platform/plugin'; +import { calendarSchema } from '../new_platform/calendars_schema'; +import { CalendarManager, Calendar, FormCalendar } from '../models/calendar'; + +function getAllCalendars(context: RequestHandlerContext) { + const cal = new CalendarManager(false, context); + return cal.getAllCalendars(); +} + +function getCalendar(context: RequestHandlerContext, calendarId: string) { + const cal = new CalendarManager(false, context); + return cal.getCalendar(calendarId); +} + +function newCalendar(context: RequestHandlerContext, calendar: FormCalendar) { + const cal = new CalendarManager(false, context); + return cal.newCalendar(calendar); +} + +function updateCalendar(context: RequestHandlerContext, calendarId: string, calendar: Calendar) { + const cal = new CalendarManager(false, context); + return cal.updateCalendar(calendarId, calendar); +} + +function deleteCalendar(context: RequestHandlerContext, calendarId: string) { + const cal = new CalendarManager(false, context); + return cal.deleteCalendar(calendarId); +} + +function getCalendarsByIds(context: RequestHandlerContext, calendarIds: string) { + const cal = new CalendarManager(false, context); + return cal.getCalendarsByIds(calendarIds); +} + +export function calendars({ xpackMainPlugin, router }: RouteInitialization) { + router.get( + { + path: '/api/ml/calendars', + validate: false, + }, + licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + try { + const resp = await getAllCalendars(context); + + return response.ok({ + body: resp, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + router.get( + { + path: '/api/ml/calendars/{calendarIds}', + validate: { + params: schema.object({ calendarIds: schema.string() }), + }, + }, + licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + let returnValue; + try { + const calendarIds = request.params.calendarIds.split(','); + + if (calendarIds.length === 1) { + returnValue = await getCalendar(context, calendarIds[0]); + } else { + returnValue = await getCalendarsByIds(context, calendarIds); + } + + return response.ok({ + body: returnValue, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + router.put( + { + path: '/api/ml/calendars', + validate: { + body: schema.object({ ...calendarSchema }), + }, + }, + licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + try { + const body = request.body; + const resp = await newCalendar(context, body); + + return response.ok({ + body: resp, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + router.put( + { + path: '/api/ml/calendars/{calendarId}', + validate: { + params: schema.object({ calendarId: schema.string() }), + body: schema.object({ ...calendarSchema }), + }, + }, + licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + try { + const { calendarId } = request.params; + const body = request.body; + const resp = await updateCalendar(context, calendarId, body); + + return response.ok({ + body: resp, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); + + router.delete( + { + path: '/api/ml/calendars/{calendarId}', + validate: { + params: schema.object({ calendarId: schema.string() }), + }, + }, + licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { + try { + const { calendarId } = request.params; + const resp = await deleteCalendar(context, calendarId); + + return response.ok({ + body: resp, + }); + } catch (e) { + return response.customError(wrapError(e)); + } + }) + ); +} diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap index 789e2a5756b48..c7081dc439085 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/__tests__/__snapshots__/cells.test.js.snap @@ -28,12 +28,12 @@ exports[`Node Listing Metric Cell should format a non-percentage metric 1`] = `
- 206.5 GB max + 206.5 GB max
- 206.3 GB min + 206.3 GB min