From 472aa2e40c1c41053aee8fa0285c5c8557d9ea83 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 14 Mar 2022 18:06:36 +0100 Subject: [PATCH 01/47] Add event annotation service structure --- src/plugins/event_annotation/README.md | 3 + .../common/annotation_expression.ts | 116 ++++++++++++++++++ src/plugins/event_annotation/common/index.ts | 10 ++ src/plugins/event_annotation/common/types.ts | 52 ++++++++ src/plugins/event_annotation/jest.config.js | 18 +++ src/plugins/event_annotation/kibana.json | 16 +++ .../public/event_annotation_service/README.md | 3 + .../event_annotation_service/helpers.ts | 9 ++ .../public/event_annotation_service/index.tsx | 20 +++ .../event_annotation_service/service.tsx | 72 +++++++++++ .../public/event_annotation_service/types.ts | 14 +++ src/plugins/event_annotation/public/index.ts | 17 +++ src/plugins/event_annotation/public/mocks.ts | 12 ++ src/plugins/event_annotation/public/plugin.ts | 39 ++++++ src/plugins/event_annotation/server/index.ts | 10 ++ src/plugins/event_annotation/server/plugin.ts | 30 +++++ src/plugins/event_annotation/tsconfig.json | 22 ++++ x-pack/plugins/lens/kibana.json | 3 +- x-pack/plugins/lens/public/plugin.ts | 14 ++- x-pack/plugins/lens/tsconfig.json | 110 ++++++++++++----- 20 files changed, 558 insertions(+), 32 deletions(-) create mode 100644 src/plugins/event_annotation/README.md create mode 100644 src/plugins/event_annotation/common/annotation_expression.ts create mode 100644 src/plugins/event_annotation/common/index.ts create mode 100644 src/plugins/event_annotation/common/types.ts create mode 100644 src/plugins/event_annotation/jest.config.js create mode 100644 src/plugins/event_annotation/kibana.json create mode 100644 src/plugins/event_annotation/public/event_annotation_service/README.md create mode 100644 src/plugins/event_annotation/public/event_annotation_service/helpers.ts create mode 100644 src/plugins/event_annotation/public/event_annotation_service/index.tsx create mode 100644 src/plugins/event_annotation/public/event_annotation_service/service.tsx create mode 100644 src/plugins/event_annotation/public/event_annotation_service/types.ts create mode 100644 src/plugins/event_annotation/public/index.ts create mode 100644 src/plugins/event_annotation/public/mocks.ts create mode 100644 src/plugins/event_annotation/public/plugin.ts create mode 100644 src/plugins/event_annotation/server/index.ts create mode 100644 src/plugins/event_annotation/server/plugin.ts create mode 100644 src/plugins/event_annotation/tsconfig.json diff --git a/src/plugins/event_annotation/README.md b/src/plugins/event_annotation/README.md new file mode 100644 index 0000000000000..6d6ef8192b4e7 --- /dev/null +++ b/src/plugins/event_annotation/README.md @@ -0,0 +1,3 @@ +# Annotation service + +// TODO \ No newline at end of file diff --git a/src/plugins/event_annotation/common/annotation_expression.ts b/src/plugins/event_annotation/common/annotation_expression.ts new file mode 100644 index 0000000000000..a46d4b5099729 --- /dev/null +++ b/src/plugins/event_annotation/common/annotation_expression.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { AnnotationConfig, AnnotationResult, AnnotationKey, AnnotationKeyResult } from './types'; + +export const annotationConfig: ExpressionFunctionDefinition< + 'annotation_config', + null, + AnnotationConfig, + AnnotationResult +> = { + name: 'annotation_config', + aliases: [], + type: 'annotation_config', + help: `Configure annotation`, + inputTypes: ['null'], + args: { + annotationType: { + types: ['string'], + help: 'Annotation type manual or query based', + }, + key: { + types: ['annotation_key'], + help: 'Type specific config', + }, + id: { + types: ['string'], + help: 'The accessor this configuration is for', + }, + color: { + types: ['string'], + help: 'The color of the series', + }, + label: { + types: ['string'], + help: 'The name', + }, + isHidden: { + types: ['boolean'], + help: 'is hidden?', + }, + lineStyle: { + types: ['string'], + options: ['solid', 'dotted', 'dashed'], + help: 'The style of the annotation line', + }, + lineWidth: { + types: ['number'], + help: 'The width of the annotation line', + }, + icon: { + types: ['string'], + help: 'An optional icon used for annotation lines', + }, + iconPosition: { + types: ['string'], + options: ['auto', 'above', 'below', 'left', 'right'], + help: 'The placement of the icon for the annotation line', + }, + textVisibility: { + types: ['boolean'], + help: 'Visibility of the label on the annotation line', + }, + message: { + types: ['string'], + help: 'The tooltip message', + }, + axisMode: { + types: ['string'], + options: ['bottom', 'auto', 'right', 'left'], + help: 'Axis mode', + }, + }, + fn: function fn(input: unknown, args: AnnotationConfig) { + return { + type: 'annotation_config', + ...args, + }; + }, +}; + +export const annotationKeyConfig: ExpressionFunctionDefinition< + 'annotation_key', + null, + AnnotationKey, + AnnotationKeyResult +> = { + name: 'annotation_key', + aliases: [], + type: 'annotation_key', + help: `Configure annotation`, + inputTypes: ['null'], + args: { + keyType: { + types: ['string'], + options: ['point_in_time'], + help: '', + }, + timestamp: { + types: ['number'], + help: '', + }, + }, + fn: function fn(input: unknown, args: AnnotationKey) { + return { + type: 'annotation_key', + ...args, + }; + }, +}; diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts new file mode 100644 index 0000000000000..ac1a7592a220f --- /dev/null +++ b/src/plugins/event_annotation/common/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export { annotationConfig, annotationKeyConfig } from './annotation_expression'; +export type { AnnotationConfig, AnnotationState } from './types'; diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts new file mode 100644 index 0000000000000..f681c9b208b3e --- /dev/null +++ b/src/plugins/event_annotation/common/types.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export type LineStyle = 'solid' | 'dashed' | 'dotted'; +export type IconPosition = 'auto' | 'left' | 'right' | 'above' | 'below'; +export type AnnotationType = 'manual'; +export type KeyType = 'point_in_time'; +export type YAxisMode = 'auto' | 'bottom' | 'left' | 'right'; + +export interface AnnotationConfig { + id: string; + key: AnnotationKeyResult; + annotationType: AnnotationType; + label: string; + message?: string; + color?: string; + icon?: string; + lineWidth?: number; + lineStyle?: LineStyle; + iconPosition?: IconPosition; + textVisibility?: boolean; + isHidden?: boolean; + axisMode: YAxisMode; +} + +export interface AnnotationState { + id: string; + timestamp: number; + label: string; + message?: string; + textVisibility?: boolean; + icon?: string; + iconPosition?: IconPosition; + lineStyle?: LineStyle; + lineWidth?: number; + color?: string; + isHidden?: boolean; +} + +export type AnnotationResult = AnnotationConfig & { type: 'annotation_config' }; + +export interface AnnotationKey { + keyType: KeyType; + timestamp: number; +} + +export type AnnotationKeyResult = AnnotationKey & { type: 'annotation_key' }; diff --git a/src/plugins/event_annotation/jest.config.js b/src/plugins/event_annotation/jest.config.js new file mode 100644 index 0000000000000..a6ea4a6b430df --- /dev/null +++ b/src/plugins/event_annotation/jest.config.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/src/plugins/event_annotation'], + coverageDirectory: '/target/kibana-coverage/jest/src/plugins/event_annotation', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/src/plugins/event_annotation/{common,public,server}/**/*.{ts,tsx}', + ], +}; diff --git a/src/plugins/event_annotation/kibana.json b/src/plugins/event_annotation/kibana.json new file mode 100644 index 0000000000000..47af30fb40a40 --- /dev/null +++ b/src/plugins/event_annotation/kibana.json @@ -0,0 +1,16 @@ +{ + "id": "eventAnnotation", + "version": "kibana", + "server": true, + "ui": true, + "extraPublicDirs": [ + "common" + ], + "requiredPlugins": [ + "expressions" + ], + "owner": { + "name": "Vis Editors", + "githubTeam": "kibana-vis-editors" + } +} \ No newline at end of file diff --git a/src/plugins/event_annotation/public/event_annotation_service/README.md b/src/plugins/event_annotation/public/event_annotation_service/README.md new file mode 100644 index 0000000000000..dcc8d87018e34 --- /dev/null +++ b/src/plugins/event_annotation/public/event_annotation_service/README.md @@ -0,0 +1,3 @@ +# Annotation Service + +TBD \ No newline at end of file diff --git a/src/plugins/event_annotation/public/event_annotation_service/helpers.ts b/src/plugins/event_annotation/public/event_annotation_service/helpers.ts new file mode 100644 index 0000000000000..aed33da840574 --- /dev/null +++ b/src/plugins/event_annotation/public/event_annotation_service/helpers.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { euiLightVars } from '@kbn/ui-theme'; +export const defaultAnnotationColor = euiLightVars.euiColorAccent; diff --git a/src/plugins/event_annotation/public/event_annotation_service/index.tsx b/src/plugins/event_annotation/public/event_annotation_service/index.tsx new file mode 100644 index 0000000000000..17bdb4e1c4bfd --- /dev/null +++ b/src/plugins/event_annotation/public/event_annotation_service/index.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EventAnnotationServiceType } from './types'; + +export class EventAnnotationService { + private annotationService: EventAnnotationServiceType | undefined = undefined; + public async getService() { + if (!this.annotationService) { + const { getAnnotationService } = await import('./service'); + this.annotationService = getAnnotationService(); + } + return this.annotationService; + } +} diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx new file mode 100644 index 0000000000000..7e86037c379dd --- /dev/null +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// @ts-ignore +import chroma from 'chroma-js'; + +import { EventAnnotationServiceType } from './types'; +import { defaultAnnotationColor } from '..'; + +export function hasIcon(icon: string | undefined): icon is string { + return icon != null && icon !== 'empty'; +} + +export function getAnnotationService(): EventAnnotationServiceType { + return { + toExpression: ({ + label, + isHidden, + id, + color, + lineStyle, + lineWidth, + icon, + iconPosition, + textVisibility, + timestamp, + }) => { + return { + type: 'expression', + chain: [ + { + type: 'function', + function: 'annotation_config', + arguments: { + annotationType: ['manual'], + key: [ + { + type: 'expression', + chain: [ + { + type: 'function', + function: 'annotation_key', + arguments: { + keyType: ['point_in_time'], + timestamp: [timestamp], + }, + }, + ], + }, + ], + label: [label], + color: [color || defaultAnnotationColor], + lineWidth: [lineWidth || 1], + lineStyle: [lineStyle || 'solid'], + id: [id], + icon: hasIcon(icon) ? [icon] : ['empty'], + iconPosition: hasIcon(icon) || textVisibility ? [iconPosition || 'auto'] : ['auto'], + textVisibility: [textVisibility || false], + isHidden: [Boolean(isHidden)], + axisMode: ['bottom'], + }, + }, + ], + }; + }, + }; +} diff --git a/src/plugins/event_annotation/public/event_annotation_service/types.ts b/src/plugins/event_annotation/public/event_annotation_service/types.ts new file mode 100644 index 0000000000000..2a7a1f885de3c --- /dev/null +++ b/src/plugins/event_annotation/public/event_annotation_service/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ExpressionAstExpression } from '../../../expressions/common/ast'; +import { AnnotationState } from '../../common/types'; + +export interface EventAnnotationServiceType { + toExpression: (props: AnnotationState) => ExpressionAstExpression; +} diff --git a/src/plugins/event_annotation/public/index.ts b/src/plugins/event_annotation/public/index.ts new file mode 100644 index 0000000000000..c15429c94cbe4 --- /dev/null +++ b/src/plugins/event_annotation/public/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +// TODO: https://github.com/elastic/kibana/issues/110891 +/* eslint-disable @kbn/eslint/no_export_all */ + +import { EventAnnotationPlugin } from './plugin'; +export const plugin = () => new EventAnnotationPlugin(); +export type { EventAnnotationPluginSetup, EventAnnotationPluginStart } from './plugin'; +export * from './event_annotation_service/types'; +export { EventAnnotationService } from './event_annotation_service'; +export { defaultAnnotationColor } from './event_annotation_service/helpers'; diff --git a/src/plugins/event_annotation/public/mocks.ts b/src/plugins/event_annotation/public/mocks.ts new file mode 100644 index 0000000000000..c4942e98e5e9f --- /dev/null +++ b/src/plugins/event_annotation/public/mocks.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getAnnotationService } from './event_annotation_service/service'; + +// not really mocking but avoiding async loading +export const eventAnnotationServiceMock = getAnnotationService(); diff --git a/src/plugins/event_annotation/public/plugin.ts b/src/plugins/event_annotation/public/plugin.ts new file mode 100644 index 0000000000000..5589b343aacc2 --- /dev/null +++ b/src/plugins/event_annotation/public/plugin.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { Plugin, CoreSetup } from 'kibana/public'; +import { ExpressionsSetup } from '../../expressions/public'; +import { annotationConfig, annotationKeyConfig } from '../common'; +import { EventAnnotationService } from './event_annotation_service'; + +interface SetupDependencies { + expressions: ExpressionsSetup; +} + +/** @public */ +export type EventAnnotationPluginSetup = EventAnnotationService; + +/** @public */ +export type EventAnnotationPluginStart = EventAnnotationService; + +/** @public */ +export class EventAnnotationPlugin + implements Plugin +{ + private readonly annotationService = new EventAnnotationService(); + + public setup(core: CoreSetup, dependencies: SetupDependencies): EventAnnotationPluginSetup { + dependencies.expressions.registerFunction(annotationConfig); + dependencies.expressions.registerFunction(annotationKeyConfig); + return this.annotationService; + } + + public start(): EventAnnotationPluginStart { + return this.annotationService!; + } +} diff --git a/src/plugins/event_annotation/server/index.ts b/src/plugins/event_annotation/server/index.ts new file mode 100644 index 0000000000000..d9d13045ed10a --- /dev/null +++ b/src/plugins/event_annotation/server/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { EventAnnotationServerPlugin } from './plugin'; +export const plugin = () => new EventAnnotationServerPlugin(); diff --git a/src/plugins/event_annotation/server/plugin.ts b/src/plugins/event_annotation/server/plugin.ts new file mode 100644 index 0000000000000..e82eff90c1309 --- /dev/null +++ b/src/plugins/event_annotation/server/plugin.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CoreSetup, Plugin } from 'kibana/server'; +import { annotationKeyConfig, annotationConfig } from '../common'; +import { ExpressionsServerSetup } from '../../expressions/server'; + +interface SetupDependencies { + expressions: ExpressionsServerSetup; +} + +export class EventAnnotationServerPlugin implements Plugin { + public setup(core: CoreSetup, dependencies: SetupDependencies) { + dependencies.expressions.registerFunction(annotationConfig); + dependencies.expressions.registerFunction(annotationKeyConfig); + + return {}; + } + + public start() { + return {}; + } + + public stop() {} +} diff --git a/src/plugins/event_annotation/tsconfig.json b/src/plugins/event_annotation/tsconfig.json new file mode 100644 index 0000000000000..ca3d65a13b214 --- /dev/null +++ b/src/plugins/event_annotation/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*" + ], + "references": [ + { + "path": "../../core/tsconfig.json" + }, + { + "path": "../expressions/tsconfig.json" + } + ] +} \ No newline at end of file diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index 17a58a0f96770..18f33adf40840 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -21,7 +21,8 @@ "presentationUtil", "dataViewFieldEditor", "expressionGauge", - "expressionHeatmap" + "expressionHeatmap", + "eventAnnotation" ], "optionalPlugins": [ "usageCollection", diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index 4d883c3a27c5e..d2bb7cdbb4344 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -33,6 +33,7 @@ import type { NavigationPublicPluginStart } from '../../../../src/plugins/naviga import type { UrlForwardingSetup } from '../../../../src/plugins/url_forwarding/public'; import type { GlobalSearchPluginSetup } from '../../global_search/public'; import type { ChartsPluginSetup, ChartsPluginStart } from '../../../../src/plugins/charts/public'; +import type { EventAnnotationPluginSetup } from '../../../../src/plugins/event_annotation/public'; import type { PresentationUtilPluginStart } from '../../../../src/plugins/presentation_util/public'; import { EmbeddableStateTransfer } from '../../../../src/plugins/embeddable/public'; import type { EditorFrameService as EditorFrameServiceType } from './editor_frame_service'; @@ -104,6 +105,7 @@ export interface LensPluginSetupDependencies { embeddable?: EmbeddableSetup; visualizations: VisualizationsSetup; charts: ChartsPluginSetup; + eventAnnotation: EventAnnotationPluginSetup; globalSearch?: GlobalSearchPluginSetup; usageCollection?: UsageCollectionSetup; discover?: DiscoverSetup; @@ -120,6 +122,7 @@ export interface LensPluginStartDependencies { visualizations: VisualizationsStart; embeddable: EmbeddableStart; charts: ChartsPluginStart; + eventAnnotation: EventAnnotationPluginSetup; savedObjectsTagging?: SavedObjectTaggingPluginStart; presentationUtil: PresentationUtilPluginStart; dataViewFieldEditor: IndexPatternFieldEditorStart; @@ -235,6 +238,7 @@ export class LensPlugin { embeddable, visualizations, charts, + eventAnnotation, globalSearch, usageCollection, }: LensPluginSetupDependencies @@ -251,7 +255,8 @@ export class LensPlugin { charts, expressions, fieldFormats, - plugins.fieldFormats.deserialize + plugins.fieldFormats.deserialize, + eventAnnotation ); const visualizationMap = await this.editorFrameService!.loadVisualizations(); @@ -311,7 +316,8 @@ export class LensPlugin { charts, expressions, fieldFormats, - deps.fieldFormats.deserialize + deps.fieldFormats.deserialize, + eventAnnotation ), ensureDefaultDataView(), ]); @@ -368,7 +374,8 @@ export class LensPlugin { charts: ChartsPluginSetup, expressions: ExpressionsServiceSetup, fieldFormats: FieldFormatsSetup, - formatFactory: FormatFactory + formatFactory: FormatFactory, + eventAnnotation: EventAnnotationPluginSetup ) { const { DatatableVisualization, @@ -402,6 +409,7 @@ export class LensPlugin { charts, editorFrame: editorFrameSetupInterface, formatFactory, + eventAnnotation, }; this.indexpatternDatasource.setup(core, dependencies); this.xyVisualization.setup(core, dependencies); diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 583e2963a1ca7..76e25f8b08639 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -1,4 +1,3 @@ - { "extends": "../../../tsconfig.base.json", "compilerOptions": { @@ -15,31 +14,86 @@ "../../../typings/**/*" ], "references": [ - { "path": "../spaces/tsconfig.json" }, - { "path": "../../../src/core/tsconfig.json" }, - { "path": "../task_manager/tsconfig.json" }, - { "path": "../global_search/tsconfig.json"}, - { "path": "../saved_objects_tagging/tsconfig.json"}, - { "path": "../../../src/plugins/data/tsconfig.json"}, - { "path": "../../../src/plugins/data_views/tsconfig.json"}, - { "path": "../../../src/plugins/data_view_field_editor/tsconfig.json"}, - { "path": "../../../src/plugins/charts/tsconfig.json"}, - { "path": "../../../src/plugins/expressions/tsconfig.json"}, - { "path": "../../../src/plugins/navigation/tsconfig.json" }, - { "path": "../../../src/plugins/url_forwarding/tsconfig.json" }, - { "path": "../../../src/plugins/visualizations/tsconfig.json" }, - { "path": "../../../src/plugins/dashboard/tsconfig.json" }, - { "path": "../../../src/plugins/ui_actions/tsconfig.json" }, - { "path": "../../../src/plugins/embeddable/tsconfig.json" }, - { "path": "../../../src/plugins/share/tsconfig.json" }, - { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, - { "path": "../../../src/plugins/saved_objects/tsconfig.json"}, - { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, - { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, - { "path": "../../../src/plugins/embeddable/tsconfig.json"}, - { "path": "../../../src/plugins/presentation_util/tsconfig.json"}, - { "path": "../../../src/plugins/field_formats/tsconfig.json"}, - { "path": "../../../src/plugins/chart_expressions/expression_heatmap/tsconfig.json"}, - { "path": "../../../src/plugins/chart_expressions/expression_gauge/tsconfig.json"} + { + "path": "../spaces/tsconfig.json" + }, + { + "path": "../../../src/core/tsconfig.json" + }, + { + "path": "../task_manager/tsconfig.json" + }, + { + "path": "../global_search/tsconfig.json" + }, + { + "path": "../saved_objects_tagging/tsconfig.json" + }, + { + "path": "../../../src/plugins/data/tsconfig.json" + }, + { + "path": "../../../src/plugins/data_views/tsconfig.json" + }, + { + "path": "../../../src/plugins/data_view_field_editor/tsconfig.json" + }, + { + "path": "../../../src/plugins/charts/tsconfig.json" + }, + { + "path": "../../../src/plugins/expressions/tsconfig.json" + }, + { + "path": "../../../src/plugins/navigation/tsconfig.json" + }, + { + "path": "../../../src/plugins/url_forwarding/tsconfig.json" + }, + { + "path": "../../../src/plugins/visualizations/tsconfig.json" + }, + { + "path": "../../../src/plugins/dashboard/tsconfig.json" + }, + { + "path": "../../../src/plugins/ui_actions/tsconfig.json" + }, + { + "path": "../../../src/plugins/embeddable/tsconfig.json" + }, + { + "path": "../../../src/plugins/share/tsconfig.json" + }, + { + "path": "../../../src/plugins/usage_collection/tsconfig.json" + }, + { + "path": "../../../src/plugins/saved_objects/tsconfig.json" + }, + { + "path": "../../../src/plugins/kibana_utils/tsconfig.json" + }, + { + "path": "../../../src/plugins/kibana_react/tsconfig.json" + }, + { + "path": "../../../src/plugins/embeddable/tsconfig.json" + }, + { + "path": "../../../src/plugins/presentation_util/tsconfig.json" + }, + { + "path": "../../../src/plugins/field_formats/tsconfig.json" + }, + { + "path": "../../../src/plugins/chart_expressions/expression_heatmap/tsconfig.json" + }, + { + "path": "../../../src/plugins/chart_expressions/expression_gauge/tsconfig.json" + }, + { + "path": "../../../src/plugins/event_annotation/tsconfig.json" + } ] -} +} \ No newline at end of file From 422a231835f320362c62e02c96f1d0209e372020 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 14 Mar 2022 18:11:49 +0100 Subject: [PATCH 02/47] adding annotation layer to lens. passing event annotation service --- x-pack/plugins/lens/common/constants.ts | 1 + .../layer_config/annotation_layer_config.ts | 62 +++++++++++++++++++ .../xy_chart/layer_config/index.ts | 7 ++- .../common/expressions/xy_chart/xy_chart.ts | 8 ++- x-pack/plugins/lens/public/expressions.ts | 2 + .../xy_visualization/expression.test.tsx | 2 + .../public/xy_visualization/expression.tsx | 4 ++ .../lens/public/xy_visualization/index.ts | 7 ++- .../xy_visualization/to_expression.test.ts | 2 + .../xy_visualization/visualization.test.ts | 2 + .../public/xy_visualization/visualization.tsx | 2 + .../xy_visualization/xy_suggestions.test.ts | 2 + .../lens/server/expressions/expressions.ts | 2 + 13 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts diff --git a/x-pack/plugins/lens/common/constants.ts b/x-pack/plugins/lens/common/constants.ts index 1504e33ecacab..d0bfecbd386be 100644 --- a/x-pack/plugins/lens/common/constants.ts +++ b/x-pack/plugins/lens/common/constants.ts @@ -45,6 +45,7 @@ export const LegendDisplay = { export const layerTypes = { DATA: 'data', REFERENCELINE: 'referenceLine', + ANNOTATIONS: 'annotations', } as const; // might collide with user-supplied field names, try to make as unique as possible diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts new file mode 100644 index 0000000000000..45061fb40e16e --- /dev/null +++ b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AnnotationConfig } from '../../../../../../../src/plugins/event_annotation/common'; +import type { ExpressionFunctionDefinition } from '../../../../../../../src/plugins/expressions/common'; +import { layerTypes } from '../../../constants'; + +export interface XYAnnotationLayerConfig { + layerId: string; + layerType: typeof layerTypes.ANNOTATIONS; + config: AnnotationConfig[]; + hide?: boolean; +} + +export interface XYAnnotationLayerConfigResult { + layerId: string; + layerType: typeof layerTypes.ANNOTATIONS; + type: 'lens_xy_annotation_layer'; + config: AnnotationConfig[]; + hide?: boolean; +} + +export const annotationLayerConfig: ExpressionFunctionDefinition< + 'lens_xy_annotation_layer', + null, + XYAnnotationLayerConfig, + XYAnnotationLayerConfigResult +> = { + name: 'lens_xy_annotation_layer', + aliases: [], + type: 'lens_xy_annotation_layer', + help: `Configure a layer in the xy chart`, + inputTypes: ['null'], + args: { + layerId: { + types: ['string'], + help: '', + }, + layerType: { types: ['string'], options: [layerTypes.ANNOTATIONS], help: '' }, + hide: { + types: ['boolean'], + default: false, + help: 'Show details', + }, + config: { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + types: ['annotation_config' as any], + help: 'Additional configuration for y axes', + multi: true, + }, + }, + fn: function fn(input: unknown, args: XYAnnotationLayerConfig) { + return { + type: 'lens_xy_annotation_layer', + ...args, + }; + }, +}; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/index.ts b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/index.ts index 0b27ce7d6ed85..df27229bdb81f 100644 --- a/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/index.ts +++ b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/index.ts @@ -6,7 +6,12 @@ */ import { XYDataLayerConfig } from './data_layer_config'; import { XYReferenceLineLayerConfig } from './reference_line_layer_config'; +import { XYAnnotationLayerConfig } from './annotation_layer_config'; export * from './data_layer_config'; export * from './reference_line_layer_config'; +export * from './annotation_layer_config'; -export type XYLayerConfig = XYDataLayerConfig | XYReferenceLineLayerConfig; +export type XYLayerConfig = + | XYDataLayerConfig + | XYReferenceLineLayerConfig + | XYAnnotationLayerConfig; diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/xy_chart.ts b/x-pack/plugins/lens/common/expressions/xy_chart/xy_chart.ts index 481494d52966f..0b8d38590f800 100644 --- a/x-pack/plugins/lens/common/expressions/xy_chart/xy_chart.ts +++ b/x-pack/plugins/lens/common/expressions/xy_chart/xy_chart.ts @@ -117,8 +117,12 @@ export const xyChart: ExpressionFunctionDefinition< }), }, layers: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - types: ['lens_xy_data_layer', 'lens_xy_referenceLine_layer'] as any, + types: [ + 'lens_xy_data_layer', + 'lens_xy_referenceLine_layer', + 'lens_xy_annotation_layer', + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ] as any, help: 'Layers of visual series', multi: true, }, diff --git a/x-pack/plugins/lens/public/expressions.ts b/x-pack/plugins/lens/public/expressions.ts index 833fcc15762af..2bf1f49ae37c8 100644 --- a/x-pack/plugins/lens/public/expressions.ts +++ b/x-pack/plugins/lens/public/expressions.ts @@ -17,6 +17,7 @@ import { labelsOrientationConfig } from '../common/expressions/xy_chart/labels_o import { dataLayerConfig, referenceLineLayerConfig, + annotationLayerConfig, } from '../common/expressions/xy_chart/layer_config'; import { legendConfig } from '../common/expressions/xy_chart/legend_config'; import { tickLabelsConfig } from '../common/expressions/xy_chart/tick_labels_config'; @@ -46,6 +47,7 @@ export const setupExpressions = ( yAxisConfig, dataLayerConfig, referenceLineLayerConfig, + annotationLayerConfig, formatColumn, legendConfig, renameColumns, diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 654a0f1b94a14..3ec0a0608f6d3 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -47,6 +47,7 @@ import { mountWithIntl } from '@kbn/test-jest-helpers'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public'; import { XyEndzones } from './x_domain'; +import { eventAnnotationServiceMock } from 'src/plugins/event_annotation/public/mocks'; const onClickValue = jest.fn(); const onSelectRange = jest.fn(); @@ -536,6 +537,7 @@ describe('xy_expression', () => { onSelectRange, syncColors: false, useLegacyTimeAxis: false, + eventAnnotationService: eventAnnotationServiceMock, }; }); diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 3e300778b85b9..b9b53b2eacf71 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -49,6 +49,7 @@ import { i18n } from '@kbn/i18n'; import { RenderMode } from 'src/plugins/expressions'; import { ThemeServiceStart } from 'kibana/public'; import { FieldFormat } from 'src/plugins/field_formats/common'; +import { EventAnnotationServiceType } from '../../../../../src/plugins/event_annotation/public'; import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public'; import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; import type { ILensInterpreterRenderHandlers, LensFilterEvent, LensBrushEvent } from '../types'; @@ -103,6 +104,7 @@ export type XYChartRenderProps = XYChartProps & { onSelectRange: (data: LensBrushEvent['data']) => void; renderMode: RenderMode; syncColors: boolean; + eventAnnotationService: EventAnnotationServiceType; }; export function calculateMinInterval({ args: { layers }, data }: XYChartProps) { @@ -139,6 +141,7 @@ export const getXyChartRenderer = (dependencies: { timeZone: string; useLegacyTimeAxis: boolean; kibanaTheme: ThemeServiceStart; + eventAnnotationService: EventAnnotationServiceType; }): ExpressionRenderDefinition => ({ name: 'lens_xy_chart_renderer', displayName: 'XY chart', @@ -169,6 +172,7 @@ export const getXyChartRenderer = (dependencies: { chartsActiveCursorService={dependencies.chartsActiveCursorService} chartsThemeService={dependencies.chartsThemeService} paletteService={dependencies.paletteService} + eventAnnotationService={dependencies.eventAnnotationService} timeZone={dependencies.timeZone} useLegacyTimeAxis={dependencies.useLegacyTimeAxis} minInterval={calculateMinInterval(config)} diff --git a/x-pack/plugins/lens/public/xy_visualization/index.ts b/x-pack/plugins/lens/public/xy_visualization/index.ts index 9697ba149e16e..cfeb1387f689c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/index.ts +++ b/x-pack/plugins/lens/public/xy_visualization/index.ts @@ -6,6 +6,7 @@ */ import type { CoreSetup } from 'kibana/public'; +import { EventAnnotationPluginSetup } from '../../../../../src/plugins/event_annotation/public'; import type { ExpressionsSetup } from '../../../../../src/plugins/expressions/public'; import type { EditorFrameSetup } from '../types'; import type { ChartsPluginSetup } from '../../../../../src/plugins/charts/public'; @@ -19,6 +20,7 @@ export interface XyVisualizationPluginSetupPlugins { formatFactory: FormatFactory; editorFrame: EditorFrameSetup; charts: ChartsPluginSetup; + eventAnnotation: EventAnnotationPluginSetup; } export class XyVisualization { @@ -28,8 +30,9 @@ export class XyVisualization { ) { editorFrame.registerVisualization(async () => { const { getXyChartRenderer, getXyVisualization } = await import('../async_services'); - const [, { charts, fieldFormats }] = await core.getStartServices(); + const [, { charts, fieldFormats, eventAnnotation }] = await core.getStartServices(); const palettes = await charts.palettes.getPalettes(); + const eventAnnotationService = await eventAnnotation.getService(); const useLegacyTimeAxis = core.uiSettings.get(LEGACY_TIME_AXIS); expressions.registerRenderer( getXyChartRenderer({ @@ -37,6 +40,7 @@ export class XyVisualization { chartsThemeService: charts.theme, chartsActiveCursorService: charts.activeCursor, paletteService: palettes, + eventAnnotationService, timeZone: getTimeZone(core.uiSettings), useLegacyTimeAxis, kibanaTheme: core.theme, @@ -44,6 +48,7 @@ export class XyVisualization { ); return getXyVisualization({ paletteService: palettes, + eventAnnotationService, fieldFormats, useLegacyTimeAxis, kibanaTheme: core.theme, diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts index ac3fdcf30a4ad..a6c5c60f7797b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.test.ts @@ -15,6 +15,7 @@ import { layerTypes } from '../../common'; import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; import { defaultReferenceLineColor } from './color_assignment'; import { themeServiceMock } from '../../../../../src/core/public/mocks'; +import { eventAnnotationServiceMock } from 'src/plugins/event_annotation/public/mocks'; describe('#toExpression', () => { const xyVisualization = getXyVisualization({ @@ -22,6 +23,7 @@ describe('#toExpression', () => { fieldFormats: fieldFormatsServiceMock.createStartContract(), kibanaTheme: themeServiceMock.createStartContract(), useLegacyTimeAxis: false, + eventAnnotationService: eventAnnotationServiceMock, }); let mockDatasource: ReturnType; let frame: ReturnType; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 07e411b1993c9..a8923e7206b9a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -23,6 +23,7 @@ import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks' import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; import { Datatable } from 'src/plugins/expressions'; import { themeServiceMock } from '../../../../../src/core/public/mocks'; +import { eventAnnotationServiceMock } from 'src/plugins/event_annotation/public/mocks'; function exampleState(): State { return { @@ -49,6 +50,7 @@ const xyVisualization = getXyVisualization({ fieldFormats: fieldFormatsMock, useLegacyTimeAxis: false, kibanaTheme: themeServiceMock.createStartContract(), + eventAnnotationService: eventAnnotationServiceMock, }); describe('xy_visualization', () => { diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index c9951c24f8a47..4a76781c1f75e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -60,8 +60,10 @@ export const getXyVisualization = ({ fieldFormats, useLegacyTimeAxis, kibanaTheme, + eventAnnotationService, }: { paletteService: PaletteRegistry; + eventAnnotationService: EventAnnotationServiceType; fieldFormats: FieldFormatsStart; useLegacyTimeAxis: boolean; kibanaTheme: ThemeServiceStart; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts index 679f3537fdd6a..9b2a8d0118406 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts @@ -11,6 +11,7 @@ import { State, XYState, visualizationTypes } from './types'; import { generateId } from '../id_generator'; import { getXyVisualization } from './xy_visualization'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; +import { eventAnnotationServiceMock } from '../../../../../src/plugins/event_annotation/public/mocks'; import { PaletteOutput } from 'src/plugins/charts/public'; import { layerTypes } from '../../common'; import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; @@ -24,6 +25,7 @@ const xyVisualization = getXyVisualization({ fieldFormats: fieldFormatsServiceMock.createStartContract(), useLegacyTimeAxis: false, kibanaTheme: themeServiceMock.createStartContract(), + eventAnnotationService: eventAnnotationServiceMock, }); describe('xy_suggestions', () => { diff --git a/x-pack/plugins/lens/server/expressions/expressions.ts b/x-pack/plugins/lens/server/expressions/expressions.ts index 84e238b3eb15e..c68fed23a7fdb 100644 --- a/x-pack/plugins/lens/server/expressions/expressions.ts +++ b/x-pack/plugins/lens/server/expressions/expressions.ts @@ -12,6 +12,7 @@ import { yAxisConfig, dataLayerConfig, referenceLineLayerConfig, + annotationLayerConfig, formatColumn, legendConfig, renameColumns, @@ -40,6 +41,7 @@ export const setupExpressions = ( yAxisConfig, dataLayerConfig, referenceLineLayerConfig, + annotationLayerConfig, formatColumn, legendConfig, renameColumns, From 308778e9bf7da98998e1e6c38cbebb0f3bbfdf8d Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 14 Mar 2022 18:20:28 +0100 Subject: [PATCH 03/47] simplify initial Dimensions --- .../editor_frame/config_panel/config_panel.test.tsx | 10 ---------- .../indexpattern_datasource/indexpattern.test.ts | 4 ---- x-pack/plugins/lens/public/types.ts | 10 +++------- .../lens/public/visualizations/gauge/visualization.tsx | 6 ------ 4 files changed, 3 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index cd26cd3197587..9ba47804230f5 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -288,8 +288,6 @@ describe('ConfigPanel', () => { { groupId: 'testGroup', columnId: 'myColumn', - dataType: 'number', - label: 'Initial value', staticValue: 100, }, ], @@ -319,8 +317,6 @@ describe('ConfigPanel', () => { { groupId: 'testGroup', columnId: 'myColumn', - dataType: 'number', - label: 'Initial value', staticValue: 100, }, ], @@ -335,9 +331,7 @@ describe('ConfigPanel', () => { expect(lensStore.dispatch).toHaveBeenCalledTimes(1); expect(datasourceMap.testDatasource.initializeDimension).toHaveBeenCalledWith({}, 'newId', { columnId: 'myColumn', - dataType: 'number', groupId: 'testGroup', - label: 'Initial value', staticValue: 100, }); }); @@ -354,8 +348,6 @@ describe('ConfigPanel', () => { { groupId: 'a', columnId: 'newId', - dataType: 'number', - label: 'Initial value', staticValue: 100, }, ], @@ -374,8 +366,6 @@ describe('ConfigPanel', () => { { groupId: 'a', columnId: 'newId', - dataType: 'number', - label: 'Initial value', staticValue: 100, } ); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts index 5a0e916395728..1545480a30f1e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.test.ts @@ -2626,9 +2626,7 @@ describe('IndexPattern Data Source', () => { expect( indexPatternDatasource.initializeDimension!(state, 'first', { columnId: 'newStatic', - label: 'MyNewColumn', groupId: 'a', - dataType: 'number', }) ).toBe(state); }); @@ -2655,9 +2653,7 @@ describe('IndexPattern Data Source', () => { expect( indexPatternDatasource.initializeDimension!(state, 'first', { columnId: 'newStatic', - label: 'MyNewColumn', groupId: 'a', - dataType: 'number', staticValue: 0, // use a falsy value to check also this corner case }) ).toEqual({ diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 9bea94bd723d3..1e7d5520b21c3 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -227,10 +227,8 @@ export interface Datasource { layerId: string, value: { columnId: string; - label: string; - dataType: string; - staticValue?: unknown; groupId: string; + staticValue?: unknown; } ) => T; @@ -789,11 +787,9 @@ export interface Visualization { disabled?: boolean; toolTipContent?: string; initialDimensions?: Array<{ - groupId: string; columnId: string; - dataType: string; - label: string; - staticValue: unknown; + groupId: string; + staticValue?: unknown; }>; }>; getLayerType: (layerId: string, state?: T) => LayerType | undefined; diff --git a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx index e1885fafab5e0..1770bac893b67 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/visualization.tsx @@ -399,22 +399,16 @@ export const getGaugeVisualization = ({ { groupId: 'min', columnId: generateId(), - dataType: 'number', - label: 'minAccessor', staticValue: minValue, }, { groupId: 'max', columnId: generateId(), - dataType: 'number', - label: 'maxAccessor', staticValue: maxValue, }, { groupId: 'goal', columnId: generateId(), - dataType: 'number', - label: 'goalAccessor', staticValue: goalValue, }, ] From 6f9a9c9f94fc9210354f2c7166cd7921c6efb427 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 14 Mar 2022 18:24:02 +0100 Subject: [PATCH 04/47] add annotations to lens --- .../public/assets/chart_bar_annotations.tsx | 40 +++ .../indexpattern_datasource/indexpattern.tsx | 2 +- x-pack/plugins/lens/public/types.ts | 12 +- .../annotations/config_panel.tsx | 134 +++++++++ .../annotations/expression.scss | 18 ++ .../annotations/expression.tsx | 262 ++++++++++++++++++ .../annotations/helpers.test.ts | 210 ++++++++++++++ .../xy_visualization/annotations/helpers.tsx | 179 ++++++++++++ .../xy_visualization/color_assignment.ts | 35 +-- .../public/xy_visualization/expression.tsx | 55 +++- .../expression_reference_lines.tsx | 50 ++-- .../reference_line_helpers.tsx | 24 +- .../public/xy_visualization/state_helpers.ts | 5 +- .../public/xy_visualization/to_expression.ts | 132 ++++++--- .../xy_visualization/visualization.test.ts | 15 +- .../public/xy_visualization/visualization.tsx | 113 ++++++-- .../visualization_helpers.tsx | 30 +- .../xy_config_panel/color_picker.tsx | 9 +- .../xy_config_panel/layer_header.tsx | 16 +- .../public/xy_visualization/xy_suggestions.ts | 4 +- 20 files changed, 1210 insertions(+), 135 deletions(-) create mode 100644 x-pack/plugins/lens/public/assets/chart_bar_annotations.tsx create mode 100644 x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx create mode 100644 x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss create mode 100644 x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx create mode 100644 x-pack/plugins/lens/public/xy_visualization/annotations/helpers.test.ts create mode 100644 x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx diff --git a/x-pack/plugins/lens/public/assets/chart_bar_annotations.tsx b/x-pack/plugins/lens/public/assets/chart_bar_annotations.tsx new file mode 100644 index 0000000000000..3d31e69f910c4 --- /dev/null +++ b/x-pack/plugins/lens/public/assets/chart_bar_annotations.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { EuiIconProps } from '@elastic/eui'; + +export const LensIconChartBarAnnotations = ({ + title, + titleId, + ...props +}: Omit) => ( + + {title ? {title} : null} + + + + + +); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx index cf77d1c9c1cc2..d0b644e2bf9b4 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/indexpattern.tsx @@ -230,7 +230,7 @@ export function getIndexPatternDatasource({ }); }, - initializeDimension(state, layerId, { columnId, groupId, label, dataType, staticValue }) { + initializeDimension(state, layerId, { columnId, groupId, staticValue }) { const indexPattern = state.indexPatterns[state.layers[layerId]?.indexPatternId]; if (staticValue == null) { return state; diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 1e7d5520b21c3..30ef13025db62 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -854,7 +854,17 @@ export interface Visualization { domElement: Element, props: VisualizationDimensionEditorProps ) => ((cleanupElement: Element) => void) | void; - + /** + * TODO: used only for vis-only annotations + */ + renderDimensionTrigger?: (props: { + layerId: string; + columnId: string; + state: T; + hideTooltip?: boolean; + invalid?: boolean; + invalidMessage?: string; + }) => JSX.Element | null; /** * The frame will call this function on all visualizations at different times. The * main use cases where visualization suggestions are requested are: diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx new file mode 100644 index 0000000000000..28411201c750c --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx @@ -0,0 +1,134 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React, { useCallback } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiDatePicker, EuiFormRow, EuiSwitch } from '@elastic/eui'; +import type { PaletteRegistry } from 'src/plugins/charts/public'; +import moment from 'moment'; +import { AnnotationState } from 'src/plugins/event_annotation/common/types'; +import type { VisualizationDimensionEditorProps } from '../../types'; +import { State, XYState } from '../types'; +import { FormatFactory } from '../../../common'; +import { XYAnnotationLayerConfig } from '../../../common/expressions'; +import { ColorPicker } from '../xy_config_panel/color_picker'; +import { NameInput, useDebouncedValue } from '../../shared_components'; +import { isHorizontalChart } from '../state_helpers'; +import { MarkerDecorationSettings } from '../xy_config_panel/shared/marker_decoration_settings'; +import { LineStyleSettings } from '../xy_config_panel/shared/line_style_settings'; +import { updateLayer } from '../xy_config_panel'; + +export const defaultAnnotationLabel = i18n.translate('xpack.lens.xyChart.defaultAnnotationLabel', { + defaultMessage: 'Static Annotation', +}); + +export const AnnotationsPanel = ( + props: VisualizationDimensionEditorProps & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; + } +) => { + const { state, setState, layerId, accessor } = props; + const isHorizontal = isHorizontalChart(state.layers); + + const { inputValue: localState, handleInputChange: setLocalState } = useDebouncedValue({ + value: state, + onChange: setState, + }); + + const index = localState.layers.findIndex((l) => l.layerId === layerId); + const localLayer = localState.layers.find( + (l) => l.layerId === layerId + ) as XYAnnotationLayerConfig; + + const currentConfig = localLayer.config?.find((c) => c.id === accessor); + + const setConfig = useCallback( + (config: Partial | undefined) => { + if (config == null) { + return; + } + const newConfigs = [...(localLayer.config || [])]; + const existingIndex = newConfigs.findIndex((c) => c.id === accessor); + if (existingIndex !== -1) { + newConfigs[existingIndex] = { ...newConfigs[existingIndex], ...config }; + } else { + // that should never happen + return; + } + setLocalState(updateLayer(localState, { ...localLayer, config: newConfigs }, index)); + }, + [accessor, index, localState, localLayer, setLocalState] + ); + return ( + <> + + { + if (date) { + setConfig({ + timestamp: date?.valueOf(), + }); + } + }} + dateFormat="MMM D, YYYY @ HH:mm:ss.SSS" + data-test-subj="lnsXY_axisOrientation_groups" + /> + + { + setConfig({ label: value }); + }} + /> + + + + + setConfig({ isHidden: ev.target.checked })} + /> + + + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss new file mode 100644 index 0000000000000..07946b52b0000 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss @@ -0,0 +1,18 @@ +.lnsXyDecorationRotatedWrapper { + display: inline-block; + overflow: hidden; + line-height: 1.5; + + .lnsXyDecorationRotatedWrapper__label { + display: inline-block; + white-space: nowrap; + transform: translate(0, 100%) rotate(-90deg); + transform-origin: 0 0; + + &::after { + content: ''; + float: left; + margin-top: 100%; + } + } +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx new file mode 100644 index 0000000000000..2d02db65d5f75 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -0,0 +1,262 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import './expression.scss'; +import React from 'react'; +import { EuiIcon } from '@elastic/eui'; +import { + AnnotationDomainType, + AnnotationTooltipFormatter, + LineAnnotation, + Position, +} from '@elastic/charts'; +import type { FieldFormat } from 'src/plugins/field_formats/common'; +import { AnnotationConfig } from 'src/plugins/event_annotation/common'; +import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; +import type { IconPosition, YAxisMode, XYAnnotationLayerConfig } from '../../../common/expressions'; +import { hasIcon } from '../xy_config_panel/shared/icon_select'; + +export const ANNOTATIONS_MARKER_SIZE = 20; + +function getBaseIconPlacement(iconPosition?: IconPosition) { + return iconPosition === 'below' ? Position.Bottom : Position.Top; +} + +function mapVerticalToHorizontalPlacement(placement: Position) { + switch (placement) { + case Position.Top: + return Position.Right; + case Position.Bottom: + return Position.Left; + } +} + +function MarkerBody({ label, isHorizontal }: { label: string | undefined; isHorizontal: boolean }) { + if (!label) { + return null; + } + if (isHorizontal) { + return ( +
+ {label} +
+ ); + } + return ( +
+
+ {label} +
+
+ ); +} + +interface MarkerConfig { + axisMode?: YAxisMode; + icon?: string; + textVisibility?: boolean; +} + +function Marker({ + config, + label, + isHorizontal, + hasReducedPadding, +}: { + config: MarkerConfig; + label: string | undefined; + isHorizontal: boolean; + hasReducedPadding: boolean; +}) { + // show an icon if present + if (hasIcon(config.icon)) { + return ; + } + // if there's some text, check whether to show it as marker, or just show some padding for the icon + if (config.textVisibility) { + if (hasReducedPadding) { + return ; + } + return ; + } + return null; +} + +const getRoundedTimestamp = ( + timestamp: number, + firstTimestamp?: number, + minInterval?: number, + isBarChart?: boolean +) => { + if (!firstTimestamp || !minInterval) { + return timestamp; + } + const roundedTimestamp = timestamp - ((timestamp - firstTimestamp) % minInterval); + return isBarChart ? roundedTimestamp + minInterval / 2 : roundedTimestamp; +}; + +export interface AnnotationsProps { + collectiveAnnotationConfigs: CollectiveConfig[]; + formatter?: FieldFormat; + isHorizontal: boolean; + paddingMap: Partial>; +} + +type CollectiveConfig = AnnotationConfig & { + roundedTimestamp: number; + layerId: string; + hide: boolean; + customTooltipDetails: AnnotationTooltipFormatter | undefined; +}; + +const groupVisibleConfigsByInterval = ( + layers: XYAnnotationLayerConfig[], + minInterval?: number, + firstTimestamp?: number, + isBarChart?: boolean +) => { + return layers + .flatMap(({ config: configs, layerId, hide }) => + configs + .filter((config) => !config.isHidden) + .map((config) => ({ + ...config, + roundedTimestamp: getRoundedTimestamp( + Number(config.key.timestamp), + firstTimestamp, + minInterval, + isBarChart + ), + layerId, + hide, + })) + ) + .reduce>( + (acc, current) => ({ + ...acc, + [current.roundedTimestamp]: acc[current.roundedTimestamp] + ? [...acc[current.roundedTimestamp], current] + : [current], + }), + {} + ); +}; + +const createCustomTooltipDetails = + (config: CollectiveConfig[], formatter?: FieldFormat): AnnotationTooltipFormatter | undefined => + () => { + return ( +
+ {config.map(({ icon, label, key }) => ( +
+ + {hasIcon(icon) ? : null} + {label} + + + {formatter?.convert(key.timestamp) || String(key.timestamp)} + +
+ ))} +
+ ); + }; + +export const getCollectiveConfigsByInterval = ( + layers: XYAnnotationLayerConfig[], + minInterval?: number, + firstTimestamp?: number, + isBarChart?: boolean, + formatter?: FieldFormat +) => { + const visibleGroupedConfigs = groupVisibleConfigsByInterval( + layers, + minInterval, + firstTimestamp, + isBarChart + ); + return Object.entries(visibleGroupedConfigs).map(([, configArr]) => { + let collectiveConfig = configArr[0]; + if (configArr.length > 1) { + collectiveConfig = { + ...collectiveConfig, + iconPosition: Position.Top as IconPosition, + textVisibility: false, + icon: !collectiveConfig.hide ? 'checkInCircleFilled' : undefined, + customTooltipDetails: createCustomTooltipDetails(configArr, formatter), + }; + } + return collectiveConfig; + }); +}; + +export const Annotations = ({ + collectiveAnnotationConfigs, + formatter, + isHorizontal, + paddingMap, +}: AnnotationsProps) => { + return ( + <> + {collectiveAnnotationConfigs.map((config) => { + const markerPositionVertical = getBaseIconPlacement(config.iconPosition); + const hasReducedPadding = paddingMap[markerPositionVertical] === ANNOTATIONS_MARKER_SIZE; + const { label, roundedTimestamp, customTooltipDetails } = config; + return ( + } + markerBody={ + + } + markerPosition={ + isHorizontal + ? mapVerticalToHorizontalPlacement(markerPositionVertical) + : markerPositionVertical + } + dataValues={[ + { + dataValue: Number(roundedTimestamp), + header: formatter?.convert(roundedTimestamp) || String(roundedTimestamp), + details: label, + }, + ]} + customTooltipDetails={customTooltipDetails} + style={{ + line: { + strokeWidth: config.lineWidth || 1, + stroke: config.color || defaultAnnotationColor, + dash: + config.lineStyle === 'dashed' + ? [(config.lineWidth || 1) * 3, config.lineWidth || 1] + : config.lineStyle === 'dotted' + ? [config.lineWidth || 1, config.lineWidth || 1] + : undefined, + opacity: 1, + }, + }} + /> + ); + })} + + ); +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.test.ts b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.test.ts new file mode 100644 index 0000000000000..64ffb5cd59542 --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.test.ts @@ -0,0 +1,210 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FramePublicAPI } from '../../types'; +import { getStaticDate } from './helpers'; + +describe('annotations helpers', () => { + describe('getStaticDate', () => { + it('should return `now` value on when nothing is configured', () => { + jest.spyOn(Date, 'now').mockReturnValue(new Date('2022-04-08T11:01:58.135Z').valueOf()); + expect(getStaticDate([], undefined)).toBe(1649415718135); + }); + it('should return `now` value on when there is no active data', () => { + expect( + getStaticDate( + [ + { + layerId: 'layerId', + accessors: ['b'], + seriesType: 'bar_stacked', + layerType: 'data', + xAccessor: 'a', + }, + ], + undefined + ) + ).toBe(1649415718135); + }); + + it('should return timestamp value for single active data point', () => { + const activeData = { + layerId: { + type: 'datatable', + rows: [ + { + a: 1646002800000, + b: 1050, + }, + ], + columns: [ + { + id: 'a', + name: 'order_date per week', + meta: { type: 'date' }, + }, + { + id: 'b', + name: 'Count of records', + meta: { type: 'number', params: { id: 'number' } }, + }, + ], + }, + }; + expect( + getStaticDate( + [ + { + layerId: 'layerId', + accessors: ['b'], + seriesType: 'bar_stacked', + layerType: 'data', + xAccessor: 'a', + }, + ], + activeData as FramePublicAPI['activeData'] + ) + ).toBe(1646002800000); + }); + + it('should correctly calculate middle value for active data', () => { + const activeData = { + layerId: { + type: 'datatable', + rows: [ + { + a: 1648206000000, + b: 19, + }, + { + a: 1648249200000, + b: 73, + }, + { + a: 1648292400000, + b: 69, + }, + { + a: 1648335600000, + b: 7, + }, + ], + columns: [ + { + id: 'a', + name: 'order_date per week', + meta: { type: 'date' }, + }, + { + id: 'b', + name: 'Count of records', + meta: { type: 'number', params: { id: 'number' } }, + }, + ], + }, + }; + expect( + getStaticDate( + [ + { + layerId: 'layerId', + accessors: ['b'], + seriesType: 'bar_stacked', + layerType: 'data', + xAccessor: 'a', + }, + ], + activeData as FramePublicAPI['activeData'] + ) + ).toBe(1648270800000); + }); + + it('should calculate middle date point correctly for multiple layers', () => { + const activeData = { + layerId: { + type: 'datatable', + rows: [ + { + a: 1648206000000, + b: 19, + }, + { + a: 1648249200000, + b: 73, + }, + { + a: 1648292400000, + b: 69, + }, + { + a: 1648335600000, + b: 7, + }, + ], + columns: [ + { + id: 'a', + name: 'order_date per week', + meta: { type: 'date' }, + }, + { + id: 'b', + name: 'Count of records', + meta: { type: 'number', params: { id: 'number' } }, + }, + ], + }, + layerId2: { + type: 'datatable', + rows: [ + { + d: 1548206000000, + c: 19, + }, + { + d: 1548249200000, + c: 73, + }, + ], + columns: [ + { + id: 'd', + name: 'order_date per week', + meta: { type: 'date' }, + }, + { + id: 'c', + name: 'Count of records', + meta: { type: 'number', params: { id: 'number' } }, + }, + ], + }, + }; + expect( + getStaticDate( + [ + { + layerId: 'layerId', + accessors: ['b'], + seriesType: 'bar_stacked', + layerType: 'data', + xAccessor: 'a', + }, + { + layerId: 'layerId2', + accessors: ['c'], + seriesType: 'bar_stacked', + layerType: 'data', + xAccessor: 'd', + }, + ], + activeData as FramePublicAPI['activeData'] + ) + ).toBe(1598270800000); + }); + }); +}); diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx new file mode 100644 index 0000000000000..9125012be1acd --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { i18n } from '@kbn/i18n'; +import { layerTypes } from '../../../common'; +import type { XYDataLayerConfig, XYAnnotationLayerConfig } from '../../../common/expressions'; +import type { FramePublicAPI, Visualization } from '../../types'; +import { isHorizontalChart } from '../state_helpers'; +import type { XYState } from '../types'; +import { + checkScaleOperation, + getAxisName, + getDataLayers, + isAnnotationsLayer, +} from '../visualization_helpers'; +import { LensIconChartBarAnnotations } from '../../assets/chart_bar_annotations'; +import { generateId } from '../../id_generator'; +import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; + +const MAX_DATE = Number(new Date(8640000000000000)); +const MIN_DATE = Number(new Date(-8640000000000000)); + +// TODO with date +export function getStaticDate( + dataLayers: XYDataLayerConfig[], + activeData: FramePublicAPI['activeData'] +) { + const fallbackValue = +new Date(Date.now()); + + if (!activeData || Object.values(activeData).every(({ rows }) => !rows || !rows.length)) { + return fallbackValue; + } + + const dataLayersId = dataLayers.map(({ layerId }) => layerId); + const minDate = dataLayersId.reduce((acc, lId) => { + const xAccessor = dataLayers.find((dataLayer) => dataLayer.layerId === lId)?.xAccessor!; + const layerMinTimestamp = activeData[lId]?.rows?.[0]?.[xAccessor]; + if (layerMinTimestamp && layerMinTimestamp < acc) { + return layerMinTimestamp; + } + return acc; + }, MAX_DATE); + + const maxDate = dataLayersId.reduce((acc, lId) => { + const xAccessor = dataLayers.find((dataLayer) => dataLayer.layerId === lId)?.xAccessor!; + const layerMinTimestamp = + activeData[lId]?.rows?.[activeData?.[lId]?.rows?.length - 1]?.[xAccessor]; + if (layerMinTimestamp && layerMinTimestamp > acc) { + return layerMinTimestamp; + } + return acc; + }, MIN_DATE); + const middleDate = (minDate + maxDate) / 2; + return middleDate; +} + +export const getAnnotationsSupportedLayer = ( + state?: XYState, + frame?: Pick +) => { + const dataLayers = getDataLayers(state?.layers || []); + + const hasDateHistogram = dataLayers.every( + (dataLayer) => + dataLayer.xAccessor || + checkScaleOperation('interval', 'date', frame?.datasourceLayers || {})(dataLayer) + ); + + const initialDimensions = + state && hasDateHistogram + ? [ + { + groupId: 'xAnnotations', + columnId: generateId(), + }, + ] + : undefined; + + return { + type: layerTypes.ANNOTATIONS, + label: i18n.translate('xpack.lens.xyChart.addAnnotationsLayerLabel', { + defaultMessage: 'Annotations', + }), + icon: LensIconChartBarAnnotations, + disabled: !hasDateHistogram, + toolTipContent: !hasDateHistogram + ? i18n.translate('xpack.lens.xyChart.addAnnotationsLayerLabelDisabledHelp', { + defaultMessage: 'Annotations require a time based chart to work. Add a date histogram.', + }) + : undefined, + initialDimensions, + }; +}; + +export const setAnnotationsDimension: Visualization['setDimension'] = ({ + prevState, + layerId, + columnId, + previousColumn, + frame, +}) => { + const foundLayer = prevState.layers.find((l) => l.layerId === layerId); + if (!foundLayer || !isAnnotationsLayer(foundLayer)) { + return prevState; + } + const dataLayers = getDataLayers(prevState.layers); + const newLayer = { ...foundLayer } as XYAnnotationLayerConfig; + + const hasConfig = newLayer.config?.some(({ id }) => id === columnId); + const previousConfig = previousColumn + ? newLayer.config?.find(({ id }) => id === previousColumn) + : false; + if (!hasConfig) { + const newTimestamp = getStaticDate(dataLayers, frame?.activeData); + newLayer.config = [ + ...(newLayer.config || []), + { + label: i18n.translate('xpack.lens.xyChart.defaultAnnotationLabel', { + defaultMessage: 'Static Annotation', + }), + timestamp: newTimestamp, + ...previousConfig, + id: columnId, + }, + ]; + } + return { + ...prevState, + layers: prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)), + }; +}; + +export const getAnnotationsAccessorColorConfig = (layer: XYAnnotationLayerConfig) => { + return layer.config.map((config) => { + return { + columnId: config.id, + triggerIcon: 'color' as const, + color: config?.color || defaultAnnotationColor, + }; + }); +}; + +export const getAnnotationsConfiguration = ({ + state, + frame, + layer, +}: { + state: XYState; + frame: FramePublicAPI; + layer: XYAnnotationLayerConfig; +}) => { + const dataLayers = getDataLayers(state.layers); + + return { + noDatasource: true, + groups: [ + { + groupId: 'xAnnotations', + groupLabel: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), + accessors: getAnnotationsAccessorColorConfig(layer), + dataTestSubj: 'lnsXY_xAnnotationsPanel', + invalid: !dataLayers.some(({ xAccessor }) => xAccessor != null), + invalidMessage: i18n.translate('xpack.lens.xyChart.addAnnotationsLayerLabelDisabledHelp', { + defaultMessage: 'Annotations require a time based chart to work. Add a date histogram.', + }), + required: false, + requiresPreviousColumnOnDuplicate: true, + supportsMoreColumns: true, + supportFieldFormat: false, + enableDimensionEditor: true, + filterOperations: () => false, + }, + ], + }; +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index 82c1106e72a08..1bc18074f13a2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -13,7 +13,9 @@ import type { AccessorConfig, FramePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; import { FormatFactory, LayerType } from '../../common'; import type { XYLayerConfig } from '../../common/expressions'; -import { isDataLayer, isReferenceLayer } from './visualization_helpers'; +import { isReferenceLayer, isAnnotationsLayer, getDataLayers } from './visualization_helpers'; +import { getAnnotationsAccessorColorConfig } from './annotations/helpers'; +import { getReferenceLineAccessorColorConfig } from './reference_line_helpers'; const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object'; @@ -42,15 +44,13 @@ export function getColorAssignments( ): ColorAssignments { const layersPerPalette: Record = {}; - layers - .filter((layer) => isDataLayer(layer)) - .forEach((layer) => { - const palette = layer.palette?.name || 'default'; - if (!layersPerPalette[palette]) { - layersPerPalette[palette] = []; - } - layersPerPalette[palette].push(layer); - }); + getDataLayers(layers).forEach((layer) => { + const palette = layer.palette?.name || 'default'; + if (!layersPerPalette[palette]) { + layersPerPalette[palette] = []; + } + layersPerPalette[palette].push(layer); + }); return mapValues(layersPerPalette, (paletteLayers) => { const seriesPerLayer = paletteLayers.map((layer, layerIndex) => { @@ -102,17 +102,6 @@ export function getColorAssignments( }); } -const getReferenceLineAccessorColorConfig = (layer: XYLayerConfig) => { - return layer.accessors.map((accessor) => { - const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); - return { - columnId: accessor, - triggerIcon: 'color' as const, - color: currentYConfig?.color || defaultReferenceLineColor, - }; - }); -}; - export function getAccessorColorConfig( colorAssignments: ColorAssignments, frame: Pick, @@ -122,7 +111,9 @@ export function getAccessorColorConfig( if (isReferenceLayer(layer)) { return getReferenceLineAccessorColorConfig(layer); } - + if (isAnnotationsLayer(layer)) { + return getAnnotationsAccessorColorConfig(layer); + } const layerContainsSplits = Boolean(layer.splitAccessor); const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; const totalSeriesCount = colorAssignments[currentPalette.name]?.totalSeriesCount; diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index b9b53b2eacf71..bb102b5333f8d 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -74,11 +74,13 @@ import { getXDomain, XyEndzones } from './x_domain'; import { getLegendAction } from './get_legend_action'; import { computeChartMargins, - getReferenceLineRequiredPaddings, + getLinesCausedPaddings, ReferenceLineAnnotations, } from './expression_reference_lines'; + +import { Annotations, getCollectiveConfigsByInterval } from './annotations/expression'; import { computeOverallDataDomain } from './reference_line_helpers'; -import { getReferenceLayers, isDataLayer } from './visualization_helpers'; +import { getReferenceLayers, isDataLayer, getAnnotationsLayer } from './visualization_helpers'; declare global { interface Window { @@ -354,7 +356,25 @@ export function XYChart({ }; const referenceLineLayers = getReferenceLayers(layers); - const referenceLinePaddings = getReferenceLineRequiredPaddings(referenceLineLayers, yAxesMap); + const annotationsLayers = getAnnotationsLayer(layers); + + const firstTable = data.tables[filteredLayers[0].layerId]; + + const xColumnId = firstTable.columns.find((col) => col.id === filteredLayers[0].xAccessor)?.id; + + const collectiveAnnotationConfigs = getCollectiveConfigsByInterval( + annotationsLayers, + minInterval, + xColumnId ? firstTable.rows[0]?.[xColumnId] : undefined, + filteredBarLayers.length > 0, + xAxisFormatter + ); + const visualConfigs = [ + ...referenceLineLayers.flatMap(({ yConfig }) => yConfig), + ...collectiveAnnotationConfigs, + ].filter(Boolean); + + const linesPaddings = getLinesCausedPaddings(visualConfigs, yAxesMap); const getYAxesStyle = (groupId: 'left' | 'right') => { const tickVisible = @@ -370,9 +390,9 @@ export function XYChart({ ? args.labelsOrientation?.yRight || 0 : args.labelsOrientation?.yLeft || 0, padding: - referenceLinePaddings[groupId] != null + linesPaddings[groupId] != null ? { - inner: referenceLinePaddings[groupId], + inner: linesPaddings[groupId], } : undefined, }, @@ -383,9 +403,9 @@ export function XYChart({ : axisTitlesVisibilitySettings?.yLeft, // if labels are not visible add the padding to the title padding: - !tickVisible && referenceLinePaddings[groupId] != null + !tickVisible && linesPaddings[groupId] != null ? { - inner: referenceLinePaddings[groupId], + inner: linesPaddings[groupId], } : undefined, }, @@ -592,16 +612,13 @@ export function XYChart({ tickLabel: { visible: tickLabelsVisibilitySettings?.x, rotation: labelsOrientation?.x, - padding: - referenceLinePaddings.bottom != null - ? { inner: referenceLinePaddings.bottom } - : undefined, + padding: linesPaddings.bottom != null ? { inner: linesPaddings.bottom } : undefined, }, axisTitle: { visible: axisTitlesVisibilitySettings.x, padding: - !tickLabelsVisibilitySettings?.x && referenceLinePaddings.bottom != null - ? { inner: referenceLinePaddings.bottom } + !tickLabelsVisibilitySettings?.x && linesPaddings.bottom != null + ? { inner: linesPaddings.bottom } : undefined, }, }; @@ -634,7 +651,7 @@ export function XYChart({ chartMargins: { ...chartTheme.chartPaddings, ...computeChartMargins( - referenceLinePaddings, + linesPaddings, tickLabelsVisibilitySettings, axisTitlesVisibilitySettings, yAxesMap, @@ -983,7 +1000,15 @@ export function XYChart({ right: Boolean(yAxesMap.right), }} isHorizontal={shouldRotate} - paddingMap={referenceLinePaddings} + paddingMap={linesPaddings} + /> + ) : null} + {collectiveAnnotationConfigs.length ? ( + ) : null} diff --git a/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx b/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx index 2d22f6a6ed76e..28ac25c283ed5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx @@ -12,10 +12,15 @@ import { EuiIcon } from '@elastic/eui'; import { RectAnnotation, AnnotationDomainType, LineAnnotation, Position } from '@elastic/charts'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import type { FieldFormat } from 'src/plugins/field_formats/common'; -import { euiLightVars } from '@kbn/ui-theme'; -import type { IconPosition, ReferenceLineLayerArgs, YAxisMode } from '../../common/expressions'; +import type { + IconPosition, + ReferenceLineLayerArgs, + YAxisMode, + YConfig, +} from '../../common/expressions'; import type { LensMultiTable } from '../../common/types'; import { hasIcon } from './xy_config_panel/shared/icon_select'; +import { defaultReferenceLineColor } from './color_assignment'; export const REFERENCE_LINE_MARKER_SIZE = 20; @@ -54,25 +59,31 @@ export const computeChartMargins = ( }; // Note: it does not take into consideration whether the reference line is in view or not -export const getReferenceLineRequiredPaddings = ( - referenceLineLayers: ReferenceLineLayerArgs[], + +export const getLinesCausedPaddings = ( + visualConfigs: Array< + Pick | undefined + >, axesMap: Record<'left' | 'right', unknown> ) => { // collect all paddings for the 4 axis: if any text is detected double it. const paddings: Partial> = {}; const icons: Partial> = {}; - referenceLineLayers.forEach((layer) => { - layer.yConfig?.forEach(({ axisMode, icon, iconPosition, textVisibility }) => { - if (axisMode && (hasIcon(icon) || textVisibility)) { - const placement = getBaseIconPlacement(iconPosition, axisMode, axesMap); - paddings[placement] = Math.max( - paddings[placement] || 0, - REFERENCE_LINE_MARKER_SIZE * (textVisibility ? 2 : 1) // double the padding size if there's text - ); - icons[placement] = (icons[placement] || 0) + (hasIcon(icon) ? 1 : 0); - } - }); + visualConfigs?.forEach((config) => { + if (!config) { + return; + } + const { axisMode, icon, iconPosition, textVisibility } = config; + if (axisMode && (hasIcon(icon) || textVisibility)) { + const placement = getBaseIconPlacement(iconPosition, axisMode, axesMap); + paddings[placement] = Math.max( + paddings[placement] || 0, + REFERENCE_LINE_MARKER_SIZE * (textVisibility ? 2 : 1) // double the padding size if there's text + ); + icons[placement] = (icons[placement] || 0) + (hasIcon(icon) ? 1 : 0); + } }); + // post-process the padding based on the icon presence: // if no icon is present for the placement, just reduce the padding (Object.keys(paddings) as Position[]).forEach((placement) => { @@ -80,7 +91,6 @@ export const getReferenceLineRequiredPaddings = ( paddings[placement] = REFERENCE_LINE_MARKER_SIZE; } }); - return paddings; }; @@ -163,7 +173,7 @@ interface MarkerConfig { textVisibility?: boolean; } -function getMarkerToShow( +export function getMarkerToShow( markerConfig: MarkerConfig, label: string | undefined, isHorizontal: boolean, @@ -241,8 +251,6 @@ export const ReferenceLineAnnotations = ({ const formatter = formatters[groupId || 'bottom']; - const defaultColor = euiLightVars.euiColorDarkShade; - // get the position for vertical chart const markerPositionVertical = getBaseIconPlacement( yConfig.iconPosition, @@ -284,7 +292,7 @@ export const ReferenceLineAnnotations = ({ const sharedStyle = { strokeWidth: yConfig.lineWidth || 1, - stroke: yConfig.color || defaultColor, + stroke: yConfig.color || defaultReferenceLineColor, dash: dashStyle, }; @@ -355,7 +363,7 @@ export const ReferenceLineAnnotations = ({ })} style={{ ...sharedStyle, - fill: yConfig.color || defaultColor, + fill: yConfig.color || defaultReferenceLineColor, opacity: 0.1, }} /> diff --git a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx index ac50a81da5423..8b6a96ce24d44 100644 --- a/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/reference_line_helpers.tsx @@ -14,7 +14,7 @@ import type { YConfig, } from '../../common/expressions'; import { Datatable } from '../../../../../src/plugins/expressions/public'; -import type { AccessorConfig, DatasourcePublicAPI, FramePublicAPI, Visualization } from '../types'; +import type { DatasourcePublicAPI, FramePublicAPI, Visualization } from '../types'; import { groupAxesByType } from './axes_configuration'; import { isHorizontalChart, isPercentageSeries, isStackedChart } from './state_helpers'; import type { XYState } from './types'; @@ -27,6 +27,7 @@ import { } from './visualization_helpers'; import { generateId } from '../id_generator'; import { LensIconChartBarReferenceLine } from '../assets/chart_bar_reference_line'; +import { defaultReferenceLineColor } from './color_assignment'; export interface ReferenceLineBase { label: 'x' | 'yRight' | 'yLeft'; @@ -360,18 +361,29 @@ export const setReferenceDimension: Visualization['setDimension'] = ({ }; }; +const getSingleColorConfig = (id: string, color = defaultReferenceLineColor) => ({ + columnId: id, + triggerIcon: 'color' as const, + color, +}); + +export const getReferenceLineAccessorColorConfig = (layer: XYReferenceLineLayerConfig) => { + return layer.accessors.map((accessor) => { + const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); + return getSingleColorConfig(accessor, currentYConfig?.color); + }); +}; + export const getReferenceConfiguration = ({ state, frame, layer, sortedAccessors, - mappedAccessors, }: { state: XYState; frame: FramePublicAPI; layer: XYReferenceLineLayerConfig; sortedAccessors: string[]; - mappedAccessors: AccessorConfig[]; }) => { const idToIndex = sortedAccessors.reduce>((memo, id, index) => { memo[id] = index; @@ -420,11 +432,7 @@ export const getReferenceConfiguration = ({ groups: groupsToShow.map(({ config = [], id, label, dataTestSubj, valid }) => ({ groupId: id, groupLabel: getAxisName(label, { isHorizontal }), - accessors: config.map(({ forAccessor, color }) => ({ - columnId: forAccessor, - color: color || mappedAccessors.find(({ columnId }) => columnId === forAccessor)?.color, - triggerIcon: 'color' as const, - })), + accessors: config.map(({ forAccessor, color }) => getSingleColorConfig(forAccessor, color)), filterOperations: isNumericMetric, supportsMoreColumns: true, required: false, diff --git a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts index dee7899740173..f00f4a02df205 100644 --- a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts +++ b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts @@ -16,7 +16,7 @@ import type { XYReferenceLineLayerConfig, } from '../../common/expressions'; import { visualizationTypes } from './types'; -import { getDataLayers, isDataLayer } from './visualization_helpers'; +import { getDataLayers, isAnnotationsLayer, isDataLayer } from './visualization_helpers'; export function isHorizontalSeries(seriesType: SeriesType) { return ( @@ -53,6 +53,9 @@ export function getIconForSeries(type: SeriesType): EuiIconType { } export const getSeriesColor = (layer: XYLayerConfig, accessor: string) => { + if (isAnnotationsLayer(layer)) { + return layer?.config?.find((config) => config.id === accessor)?.color || null; + } if (isDataLayer(layer) && layer.splitAccessor) { return null; } diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 37457c61b2603..70d3c991b29a5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -8,29 +8,34 @@ import { Ast } from '@kbn/interpreter'; import { ScaleType } from '@elastic/charts'; import { PaletteRegistry } from 'src/plugins/charts/public'; +import { EventAnnotationServiceType } from 'src/plugins/event_annotation/public'; import { State } from './types'; import { OperationMetadata, DatasourcePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; import type { ValidLayer, - XYDataLayerConfig, + XYAnnotationLayerConfig, XYReferenceLineLayerConfig, YConfig, + XYDataLayerConfig, } from '../../common/expressions'; import { layerTypes } from '../../common'; import { hasIcon } from './xy_config_panel/shared/icon_select'; import { defaultReferenceLineColor } from './color_assignment'; import { getDefaultVisualValuesForLayer } from '../shared_components/datasource_default_values'; -import { isDataLayer } from './visualization_helpers'; +import { isDataLayer, isAnnotationsLayer, getLayerTypeOptions } from './visualization_helpers'; +import { defaultAnnotationLabel } from './annotations/config_panel'; export const getSortedAccessors = ( datasource: DatasourcePublicAPI, layer: XYDataLayerConfig | XYReferenceLineLayerConfig ) => { const originalOrder = datasource - .getTableSpec() - .map(({ columnId }: { columnId: string }) => columnId) - .filter((columnId: string) => layer.accessors.includes(columnId)); + ? datasource + .getTableSpec() + .map(({ columnId }: { columnId: string }) => columnId) + .filter((columnId: string) => layer.accessors.includes(columnId)) + : layer.accessors; // When we add a column it could be empty, and therefore have no order return Array.from(new Set(originalOrder.concat(layer.accessors))); }; @@ -39,7 +44,8 @@ export const toExpression = ( state: State, datasourceLayers: Record, paletteService: PaletteRegistry, - attributes: Partial<{ title: string; description: string }> = {} + attributes: Partial<{ title: string; description: string }> = {}, + eventAnnotationService: EventAnnotationServiceType ): Ast | null => { if (!state || !state.layers.length) { return null; @@ -49,38 +55,58 @@ export const toExpression = ( state.layers.forEach((layer) => { metadata[layer.layerId] = {}; const datasource = datasourceLayers[layer.layerId]; - datasource.getTableSpec().forEach((column) => { - const operation = datasourceLayers[layer.layerId].getOperationForColumnId(column.columnId); - metadata[layer.layerId][column.columnId] = operation; - }); + if (datasource) { + datasource.getTableSpec().forEach((column) => { + const operation = datasourceLayers[layer.layerId].getOperationForColumnId(column.columnId); + metadata[layer.layerId][column.columnId] = operation; + }); + } }); - return buildExpression(state, metadata, datasourceLayers, paletteService, attributes); + return buildExpression( + state, + metadata, + datasourceLayers, + paletteService, + attributes, + eventAnnotationService + ); +}; + +const simplifiedLayerExpression = { + [layerTypes.DATA]: (layer: XYDataLayerConfig) => ({ ...layer, hide: true }), + [layerTypes.REFERENCELINE]: (layer: XYReferenceLineLayerConfig) => ({ + ...layer, + hide: true, + yConfig: layer.yConfig?.map(({ lineWidth, ...config }) => ({ + ...config, + lineWidth: 1, + icon: undefined, + textVisibility: false, + })), + }), + [layerTypes.ANNOTATIONS]: (layer: XYAnnotationLayerConfig) => ({ + ...layer, + hide: true, + config: layer.config?.map(({ lineWidth, ...config }) => ({ + ...config, + lineWidth: 1, + icon: undefined, + textVisibility: false, + })), + }), }; export function toPreviewExpression( state: State, datasourceLayers: Record, - paletteService: PaletteRegistry + paletteService: PaletteRegistry, + eventAnnotationService: EventAnnotationServiceType ) { return toExpression( { ...state, - layers: state.layers.map((layer) => - isDataLayer(layer) - ? { ...layer, hide: true } - : // cap the reference line to 1px - { - ...layer, - hide: true, - yConfig: layer.yConfig?.map(({ lineWidth, ...config }) => ({ - ...config, - lineWidth: 1, - icon: undefined, - textVisibility: false, - })), - } - ), + layers: state.layers.map((layer) => getLayerTypeOptions(layer, simplifiedLayerExpression)), // hide legend for preview legend: { ...state.legend, @@ -90,7 +116,8 @@ export function toPreviewExpression( }, datasourceLayers, paletteService, - {} + {}, + eventAnnotationService ); } @@ -125,12 +152,15 @@ export const buildExpression = ( metadata: Record>, datasourceLayers: Record, paletteService: PaletteRegistry, - attributes: Partial<{ title: string; description: string }> = {} + attributes: Partial<{ title: string; description: string }> = {}, + eventAnnotationService: EventAnnotationServiceType ): Ast | null => { const validLayers = state.layers - .filter((layer): layer is ValidLayer => Boolean(layer.accessors.length)) + .filter((layer): layer is ValidLayer => + isAnnotationsLayer(layer) ? Boolean(layer.config.length) : Boolean(layer.accessors.length) + ) .map((layer) => { - if (!datasourceLayers) { + if (!datasourceLayers?.[layer.layerId]) { return layer; } const sortedAccessors = getSortedAccessors(datasourceLayers[layer.layerId], layer); @@ -316,6 +346,9 @@ export const buildExpression = ( paletteService ); } + if (isAnnotationsLayer(layer)) { + return annotationLayerToExpression(layer, eventAnnotationService); + } return referenceLineLayerToExpression( layer, datasourceLayers[(layer as XYReferenceLineLayerConfig).layerId] @@ -353,6 +386,43 @@ const referenceLineLayerToExpression = ( }; }; +const annotationLayerToExpression = ( + layer: XYAnnotationLayerConfig, + eventAnnotationService: EventAnnotationServiceType +): Ast => { + return { + type: 'expression', + chain: [ + { + type: 'function', + function: 'lens_xy_annotation_layer', + arguments: { + hide: [Boolean(layer.hide)], + layerId: [layer.layerId], + layerType: [layerTypes.ANNOTATIONS], + config: layer.config + ? layer.config.map( + (config): Ast => + eventAnnotationService.toExpression({ + id: config.id, + timestamp: config.timestamp, + label: config.label || defaultAnnotationLabel, + textVisibility: config.textVisibility, + icon: config.icon, + iconPosition: config.iconPosition, + lineStyle: config.lineStyle, + lineWidth: config.lineWidth, + color: config.color, + isHidden: Boolean(config.isHidden), + }) + ) + : [], + }, + }, + ], + }; +}; + const dataLayerToExpression = ( layer: ValidLayer, datasourceLayer: DatasourcePublicAPI, diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index a8923e7206b9a..93f29925ea7dd 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -151,7 +151,7 @@ describe('xy_visualization', () => { expect(initialState.layers).toHaveLength(1); expect((initialState.layers[0] as XYDataLayerConfig).xAccessor).not.toBeDefined(); - expect(initialState.layers[0].accessors).toHaveLength(0); + expect((initialState.layers[0] as XYDataLayerConfig).accessors).toHaveLength(0); expect(initialState).toMatchInlineSnapshot(` Object { @@ -229,7 +229,7 @@ describe('xy_visualization', () => { describe('#getSupportedLayers', () => { it('should return a double layer types', () => { - expect(xyVisualization.getSupportedLayers()).toHaveLength(2); + expect(xyVisualization.getSupportedLayers()).toHaveLength(3); }); it('should return the icon for the visualization type', () => { @@ -474,9 +474,10 @@ describe('xy_visualization', () => { layerId: 'first', context: newContext, }); - expect(state?.layers[0]).toHaveProperty('seriesType', 'area'); - expect(state?.layers[0]).toHaveProperty('layerType', 'referenceLine'); - expect(state?.layers[0].yConfig).toStrictEqual([ + const firstLayer = state?.layers[0] as XYDataLayerConfig; + expect(firstLayer).toHaveProperty('seriesType', 'area'); + expect(firstLayer).toHaveProperty('layerType', 'referenceLine'); + expect(firstLayer.yConfig).toStrictEqual([ { axisMode: 'right', color: '#68BC00', @@ -1071,7 +1072,7 @@ describe('xy_visualization', () => { it('should support static value', () => { const state = getStateWithBaseReferenceLine(); - state.layers[0].accessors = []; + (state.layers[1] as XYReferenceLineLayerConfig).accessors = []; (state.layers[1] as XYReferenceLineLayerConfig).yConfig = undefined; expect( xyVisualization.getConfiguration({ @@ -1084,7 +1085,7 @@ describe('xy_visualization', () => { it('should return no referenceLine groups for a empty data layer', () => { const state = getStateWithBaseReferenceLine(); - state.layers[0].accessors = []; + (state.layers[0] as XYDataLayerConfig).accessors = []; (state.layers[1] as XYReferenceLineLayerConfig).yConfig = undefined; const options = xyVisualization.getConfiguration({ diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 4a76781c1f75e..6aa98eacf26c9 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -13,27 +13,33 @@ import { i18n } from '@kbn/i18n'; import { PaletteRegistry } from 'src/plugins/charts/public'; import { FieldFormatsStart } from 'src/plugins/field_formats/public'; import { ThemeServiceStart } from 'kibana/public'; +import { EventAnnotationServiceType } from '../../../../../src/plugins/event_annotation/public'; import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; import { VIS_EVENT_TO_TRIGGER } from '../../../../../src/plugins/visualizations/public'; -import type { FillStyle } from '../../common/expressions/xy_chart'; +import type { FillStyle, XYLayerConfig } from '../../common/expressions/xy_chart'; import { getSuggestions } from './xy_suggestions'; import { XyToolbar } from './xy_config_panel'; import { DimensionEditor } from './xy_config_panel/dimension_editor'; import { LayerHeader } from './xy_config_panel/layer_header'; import type { Visualization, AccessorConfig, FramePublicAPI } from '../types'; import { State, visualizationTypes, XYSuggestion } from './types'; -import { SeriesType, XYDataLayerConfig, XYLayerConfig, YAxisMode } from '../../common/expressions'; +import { SeriesType, XYDataLayerConfig, YAxisMode } from '../../common/expressions'; import { layerTypes } from '../../common'; import { isHorizontalChart } from './state_helpers'; import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression'; import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; import { getColumnToLabelMap } from './state_helpers'; import { - getGroupsAvailableInData, + getGroupsAvailableInData, // TODO - see how we should handle this in annotations getReferenceConfiguration, getReferenceSupportedLayer, setReferenceDimension, } from './reference_line_helpers'; +import { + getAnnotationsConfiguration, + getAnnotationsSupportedLayer, + setAnnotationsDimension, +} from './annotations/helpers'; import { checkXAccessorCompatibility, defaultSeriesType, @@ -42,7 +48,9 @@ import { getDescription, getFirstDataLayer, getLayersByType, + getReferenceLayers, getVisualizationType, + isAnnotationsLayer, isBucketed, isDataLayer, isNumericDynamicMetric, @@ -54,6 +62,8 @@ import { import { groupAxesByType } from './axes_configuration'; import { XYState } from '..'; import { ReferenceLinePanel } from './xy_config_panel/reference_line_panel'; +import { DimensionTrigger } from '../shared_components/dimension_trigger'; +import { AnnotationsPanel, defaultAnnotationLabel } from './annotations/config_panel'; export const getXyVisualization = ({ paletteService, @@ -157,7 +167,11 @@ export const getXyVisualization = ({ }, getSupportedLayers(state, frame) { - return [supportedDataLayer, getReferenceSupportedLayer(state, frame)]; + return [ + supportedDataLayer, + getAnnotationsSupportedLayer(state, frame), + getReferenceSupportedLayer(state, frame), + ]; }, getConfiguration({ state, frame, layerId }) { @@ -166,10 +180,18 @@ export const getXyVisualization = ({ return { groups: [] }; } + if (isAnnotationsLayer(layer)) { + return getAnnotationsConfiguration({ state, frame, layer }); + } + const sortedAccessors: string[] = getSortedAccessors( frame.datasourceLayers[layer.layerId], layer ); + if (isReferenceLayer(layer)) { + return getReferenceConfiguration({ state, frame, layer, sortedAccessors }); + } + const mappedAccessors = getMappedAccessors({ state, frame, @@ -179,11 +201,7 @@ export const getXyVisualization = ({ accessors: sortedAccessors, }); - if (isReferenceLayer(layer)) { - return getReferenceConfiguration({ state, frame, layer, sortedAccessors, mappedAccessors }); - } const dataLayers = getDataLayers(state.layers); - const isHorizontal = isHorizontalChart(state.layers); const { left, right } = groupAxesByType([layer], frame.activeData); // Check locally if it has one accessor OR one accessor per axis @@ -277,6 +295,9 @@ export const getXyVisualization = ({ if (isReferenceLayer(foundLayer)) { return setReferenceDimension(props); } + if (isAnnotationsLayer(foundLayer)) { + return setAnnotationsDimension(props); + } const newLayer = { ...foundLayer }; if (groupId === 'x') { @@ -297,7 +318,7 @@ export const getXyVisualization = ({ updateLayersConfigurationFromContext({ prevState, layerId, context }) { const { chartType, axisPosition, palette, metrics } = context; const foundLayer = prevState?.layers.find((l) => l.layerId === layerId); - if (!foundLayer) { + if (!foundLayer || !isDataLayer(foundLayer)) { return prevState; } const isReferenceLine = metrics.some((metric) => metric.agg === 'static_value'); @@ -374,12 +395,23 @@ export const getXyVisualization = ({ return suggestion; }, + // todo: annotation layer types diff removeDimension({ prevState, layerId, columnId, frame }) { const foundLayer = prevState.layers.find((l) => l.layerId === layerId); if (!foundLayer) { return prevState; } - const dataLayers = getDataLayers(prevState.layers); + if (isAnnotationsLayer(foundLayer)) { + const newLayer = { ...foundLayer }; + if ('config' in newLayer) { + newLayer.config = newLayer.config.filter(({ id }) => id !== columnId); + } + const newLayers = prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)); + return { + ...prevState, + layers: newLayers, + }; + } const newLayer = { ...foundLayer }; if (isDataLayer(newLayer)) { if (newLayer.xAccessor === columnId) { @@ -394,15 +426,15 @@ export const getXyVisualization = ({ newLayer.accessors = newLayer.accessors.filter((a) => a !== columnId); } - if (newLayer.yConfig) { - newLayer.yConfig = newLayer.yConfig.filter(({ forAccessor }) => forAccessor !== columnId); + if ('yConfig' in newLayer) { + newLayer.yConfig = newLayer.yConfig?.filter(({ forAccessor }) => forAccessor !== columnId); } let newLayers = prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)); // check if there's any reference layer and pull it off if all data layers have no dimensions set // check for data layers if they all still have xAccessors const groupsAvailable = getGroupsAvailableInData( - dataLayers, + getDataLayers(prevState.layers), frame.datasourceLayers, frame?.activeData ); @@ -412,7 +444,9 @@ export const getXyVisualization = ({ (id) => !groupsAvailable[id] ) ) { - newLayers = newLayers.filter((layer) => isDataLayer(layer) || layer.accessors.length); + newLayers = newLayers.filter( + (layer) => isDataLayer(layer) || ('accessors' in layer && layer.accessors.length) + ); } return { @@ -452,9 +486,12 @@ export const getXyVisualization = ({ const layer = props.state.layers.find((l) => l.layerId === props.layerId)!; const dimensionEditor = isReferenceLayer(layer) ? ( + ) : isAnnotationsLayer(layer) ? ( + ) : ( ); + render( {dimensionEditor} @@ -464,8 +501,9 @@ export const getXyVisualization = ({ }, toExpression: (state, layers, attributes) => - toExpression(state, layers, paletteService, attributes), - toPreviewExpression: (state, layers) => toPreviewExpression(state, layers, paletteService), + toExpression(state, layers, paletteService, attributes, eventAnnotationService), + toPreviewExpression: (state, layers) => + toPreviewExpression(state, layers, paletteService, eventAnnotationService), getErrorMessages(state, datasourceLayers) { // Data error handling below here @@ -506,7 +544,7 @@ export const getXyVisualization = ({ // temporary fix for #87068 errors.push(...checkXAccessorCompatibility(state, datasourceLayers)); - for (const layer of state.layers) { + for (const layer of getDataLayers(state.layers)) { const datasourceAPI = datasourceLayers[layer.layerId]; if (datasourceAPI) { for (const accessor of layer.accessors) { @@ -542,9 +580,10 @@ export const getXyVisualization = ({ return; } - const layers = state.layers; - - const filteredLayers = layers.filter(({ accessors }: XYLayerConfig) => accessors.length > 0); + const filteredLayers = [ + ...getDataLayers(state.layers), + ...getReferenceLayers(state.layers), + ].filter(({ accessors }) => accessors.length > 0); const accessorsWithArrayValues = []; for (const layer of filteredLayers) { const { layerId, accessors } = layer; @@ -571,6 +610,36 @@ export const getXyVisualization = ({ /> )); }, + renderDimensionTrigger({ + columnId, + layerId, + state, + hideTooltip, + invalid, + invalidMessage, + }: { + columnId: string; + layerId: string; + state: XYState; + hideTooltip?: boolean; + invalid?: boolean; + invalidMessage?: string; + }) { + const layer = state.layers.find((l) => l.layerId === layerId); + if (layer && isAnnotationsLayer(layer)) { + const config = layer?.config.find((l) => l.id === columnId); + return ( + + ); + } + return null; + }, }); const getMappedAccessors = ({ @@ -586,7 +655,7 @@ const getMappedAccessors = ({ paletteService: PaletteRegistry; fieldFormats: FieldFormatsStart; state: XYState; - layer: XYLayerConfig; + layer: XYDataLayerConfig; }) => { let mappedAccessors: AccessorConfig[] = accessors.map((accessor) => ({ columnId: accessor, @@ -594,7 +663,7 @@ const getMappedAccessors = ({ if (frame.activeData) { const colorAssignments = getColorAssignments( - state.layers, + getDataLayers(state.layers), { tables: frame.activeData }, fieldFormats.deserialize ); diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx index 7446c2a06119c..94d00045ba60b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx @@ -12,6 +12,7 @@ import { State, visualizationTypes, XYState } from './types'; import { isHorizontalChart } from './state_helpers'; import { SeriesType, + XYAnnotationLayerConfig, XYDataLayerConfig, XYLayerConfig, XYReferenceLineLayerConfig, @@ -130,7 +131,7 @@ export function checkScaleOperation( export const isDataLayer = (layer: Pick): layer is XYDataLayerConfig => layer.layerType === layerTypes.DATA || !layer.layerType; -export const getDataLayers = (layers: XYLayerConfig[]) => +export const getDataLayers = (layers: Array>) => (layers || []).filter((layer): layer is XYDataLayerConfig => isDataLayer(layer)); export const getFirstDataLayer = (layers: XYLayerConfig[]) => @@ -143,6 +144,28 @@ export const isReferenceLayer = ( export const getReferenceLayers = (layers: XYLayerConfig[]) => (layers || []).filter((layer): layer is XYReferenceLineLayerConfig => isReferenceLayer(layer)); +export const isAnnotationsLayer = ( + layer: Pick +): layer is XYAnnotationLayerConfig => layer.layerType === layerTypes.ANNOTATIONS; + +export const getAnnotationsLayer = (layers: XYLayerConfig[]) => + (layers || []).filter((layer): layer is XYAnnotationLayerConfig => isAnnotationsLayer(layer)); + +export interface LayerTypeToLayer { + [layerTypes.DATA]: (layer: XYDataLayerConfig) => XYDataLayerConfig; + [layerTypes.REFERENCELINE]: (layer: XYReferenceLineLayerConfig) => XYReferenceLineLayerConfig; + [layerTypes.ANNOTATIONS]: (layer: XYAnnotationLayerConfig) => XYAnnotationLayerConfig; +} + +export const getLayerTypeOptions = (layer: XYLayerConfig, options: LayerTypeToLayer) => { + if (isDataLayer(layer)) { + return options[layerTypes.DATA](layer); + } else if (isReferenceLayer(layer)) { + return options[layerTypes.REFERENCELINE](layer); + } + return options[layerTypes.ANNOTATIONS](layer); +}; + export function getVisualizationType(state: State): VisualizationType | 'mixed' { if (!state.layers.length) { return ( @@ -255,6 +278,11 @@ const newLayerFn = { layerType: layerTypes.REFERENCELINE, accessors: [], }), + [layerTypes.ANNOTATIONS]: ({ layerId }: { layerId: string }): XYAnnotationLayerConfig => ({ + layerId, + layerType: layerTypes.ANNOTATIONS, + config: [], + }), }; export function newLayerState({ diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx index 8aa2aaf16ae5f..b448ebfbd455e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/color_picker.tsx @@ -9,6 +9,7 @@ import React, { useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFormRow, EuiColorPicker, EuiColorPickerProps, EuiToolTip, EuiIcon } from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; +import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; import type { VisualizationDimensionEditorProps } from '../../types'; import { State } from '../types'; import { FormatFactory } from '../../../common'; @@ -20,7 +21,7 @@ import { } from '../color_assignment'; import { getSortedAccessors } from '../to_expression'; import { TooltipWrapper } from '../../shared_components'; -import { isReferenceLayer } from '../visualization_helpers'; +import { isReferenceLayer, isAnnotationsLayer, getDataLayers } from '../visualization_helpers'; const tooltipContent = { auto: i18n.translate('xpack.lens.configPanel.color.tooltip.auto', { @@ -62,15 +63,17 @@ export const ColorPicker = ({ if (overwriteColor || !frame.activeData) return overwriteColor; if (isReferenceLayer(layer)) { return defaultReferenceLineColor; + } else if (isAnnotationsLayer(layer)) { + return defaultAnnotationColor; } const sortedAccessors: string[] = getSortedAccessors( - frame.datasourceLayers[layer.layerId], + frame.datasourceLayers[layer.layerId] ?? layer.accessors, layer ); const colorAssignments = getColorAssignments( - state.layers, + getDataLayers(state.layers), { tables: frame.activeData }, formatFactory ); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx index 465a627fa33b2..c4e5268cfb8af 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/layer_header.tsx @@ -16,8 +16,9 @@ import { trackUiEvent } from '../../lens_ui_telemetry'; import { StaticHeader } from '../../shared_components'; import { ToolbarButton } from '../../../../../../src/plugins/kibana_react/public'; import { LensIconChartBarReferenceLine } from '../../assets/chart_bar_reference_line'; +import { LensIconChartBarAnnotations } from '../../assets/chart_bar_annotations'; import { updateLayer } from '.'; -import { isReferenceLayer } from '../visualization_helpers'; +import { isAnnotationsLayer, isReferenceLayer } from '../visualization_helpers'; export function LayerHeader(props: VisualizationLayerWidgetProps) { const layer = props.state.layers.find((l) => l.layerId === props.layerId); @@ -26,6 +27,8 @@ export function LayerHeader(props: VisualizationLayerWidgetProps) { } if (isReferenceLayer(layer)) { return ; + } else if (isAnnotationsLayer(layer)) { + return ; } return ; } @@ -41,6 +44,17 @@ function ReferenceLayerHeader() { ); } +function AnnotationsLayerHeader() { + return ( + + ); +} + function DataLayerHeader(props: VisualizationLayerWidgetProps) { const [isPopoverOpen, setPopoverIsOpen] = useState(false); const { state, layerId } = props; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index 1578442b52815..c3dc556068e50 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -521,7 +521,9 @@ function buildSuggestion({ const keptLayers = currentState ? currentState.layers // Remove layers that aren't being suggested - .filter((layer) => keptLayerIds.includes(layer.layerId)) + .filter( + (layer) => keptLayerIds.includes(layer.layerId) || layer.layerType === 'annotations' + ) // Update in place .map((layer) => (layer.layerId === layerId ? newLayer : layer)) // Replace the seriesType on all previous layers From 7f2b283639d0ca7a31d32233d5af148f530187ee Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 14 Mar 2022 18:24:21 +0100 Subject: [PATCH 05/47] no datasource layer --- .../editor_frame/config_panel/add_layer.tsx | 8 +- .../buttons/draggable_dimension_button.tsx | 5 +- .../buttons/drop_targets_utils.tsx | 12 +- .../buttons/empty_dimension_button.tsx | 74 ++++++-- .../config_panel/config_panel.tsx | 101 ++++++----- .../editor_frame/config_panel/layer_panel.tsx | 160 +++++++++++------- .../droppable/get_drop_props.ts | 13 +- .../public/state_management/lens_slice.ts | 47 +++-- x-pack/plugins/lens/public/types.ts | 15 +- 9 files changed, 274 insertions(+), 161 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx index 0e4340300f8b2..753db499ac2a4 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx @@ -15,8 +15,9 @@ import type { FramePublicAPI, Visualization } from '../../../types'; interface AddLayerButtonProps { visualization: Visualization; visualizationState: unknown; - onAddLayerClick: (layerType: LayerType) => void; + onAddLayerClick: (layerType: LayerType, noDatasource?: boolean) => void; layersMeta: Pick; + noDatasource?: boolean; } export function getLayerType(visualization: Visualization, state: unknown, layerId: string) { @@ -28,6 +29,7 @@ export function AddLayerButton({ visualizationState, onAddLayerClick, layersMeta, + noDatasource, }: AddLayerButtonProps) { const [showLayersChoice, toggleLayersChoice] = useState(false); @@ -63,7 +65,7 @@ export function AddLayerButton({ })} fill color="text" - onClick={() => onAddLayerClick(supportedLayers[0].type)} + onClick={() => onAddLayerClick(supportedLayers[0].type, noDatasource)} iconType="layers" > {i18n.translate('xpack.lens.configPanel.addLayerButton', { @@ -115,7 +117,7 @@ export function AddLayerButton({ icon: icon && , ['data-test-subj']: `lnsLayerAddButton-${type}`, onClick: () => { - onAddLayerClick(type); + onAddLayerClick(type, noDatasource); toggleLayersChoice(false); }, }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/draggable_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/draggable_dimension_button.tsx index e88b04588d2e0..f0e0911b708fd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/draggable_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/draggable_dimension_button.tsx @@ -18,6 +18,7 @@ import { getCustomDropTarget, getAdditionalClassesOnDroppable, getAdditionalClassesOnEnter, + getDropProps, } from './drop_targets_utils'; export function DraggableDimensionButton({ @@ -59,8 +60,8 @@ export function DraggableDimensionButton({ }) { const { dragging } = useContext(DragContext); - const dropProps = layerDatasource.getDropProps({ - ...layerDatasourceDropProps, + const dropProps = getDropProps(layerDatasource, { + ...(layerDatasourceDropProps || {}), dragging, columnId, filterOperations: group.filterOperations, diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils.tsx index 7d92eb9d22cbb..a293af4d11bfe 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils.tsx @@ -9,7 +9,7 @@ import React from 'react'; import classNames from 'classnames'; import { EuiIcon, EuiFlexItem, EuiFlexGroup, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { DropType } from '../../../../types'; +import { Datasource, DropType, GetDropProps } from '../../../../types'; function getPropsForDropType(type: 'swap' | 'duplicate' | 'combine') { switch (type) { @@ -129,3 +129,13 @@ export const getAdditionalClassesOnDroppable = (dropType?: string) => { return 'lnsDragDrop-notCompatible'; } }; + +export const getDropProps = ( + layerDatasource: Datasource, + layerDatasourceDropProps: GetDropProps +) => { + if (layerDatasource) { + return layerDatasource.getDropProps(layerDatasourceDropProps); + } + return; +}; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx index 1ba3ff8f6ac34..543ab841934d2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx @@ -14,7 +14,11 @@ import { DragDrop, DragDropIdentifier, DragContext } from '../../../../drag_drop import { Datasource, VisualizationDimensionGroupConfig, DropType } from '../../../../types'; import { LayerDatasourceDropProps } from '../types'; -import { getCustomDropTarget, getAdditionalClassesOnDroppable } from './drop_targets_utils'; +import { + getCustomDropTarget, + getAdditionalClassesOnDroppable, + getDropProps, +} from './drop_targets_utils'; const label = i18n.translate('xpack.lens.indexPattern.emptyDimensionButton', { defaultMessage: 'Empty dimension', @@ -24,9 +28,41 @@ interface EmptyButtonProps { columnId: string; onClick: (id: string) => void; group: VisualizationDimensionGroupConfig; + labels?: { + ariaLabel: (group: VisualizationDimensionGroupConfig) => string; + label: JSX.Element | string; + }; } -const DefaultEmptyButton = ({ columnId, group, onClick }: EmptyButtonProps) => ( +const defaultButtonLabels = { + ariaLabel: (group: VisualizationDimensionGroupConfig) => + i18n.translate('xpack.lens.indexPattern.addColumnAriaLabel', { + defaultMessage: 'Add or drag-and-drop a field to {groupLabel}', + values: { groupLabel: group.groupLabel }, + }), + label: ( + + ), +}; + +const noDndButtonLabels = { + ariaLabel: (group: VisualizationDimensionGroupConfig) => + i18n.translate('xpack.lens.indexPattern.addColumnAriaLabel', { + defaultMessage: 'Click to add to {groupLabel}', + values: { groupLabel: group.groupLabel }, + }), + label: , +}; + +const DefaultEmptyButton = ({ + columnId, + group, + onClick, + labels = defaultButtonLabels, +}: EmptyButtonProps) => ( ( contentProps={{ className: 'lnsLayerPanel__triggerTextContent', }} - aria-label={i18n.translate('xpack.lens.indexPattern.removeColumnAriaLabel', { - defaultMessage: 'Add or drag-and-drop a field to {groupLabel}', - values: { groupLabel: group.groupLabel }, - })} + aria-label={labels.ariaLabel(group)} data-test-subj="lns-empty-dimension" onClick={() => { onClick(columnId); }} > - + {labels.label} ); @@ -60,9 +90,9 @@ const SuggestedValueButton = ({ columnId, group, onClick }: EmptyButtonProps) => contentProps={{ className: 'lnsLayerPanel__triggerTextContent', }} - aria-label={i18n.translate('xpack.lens.indexPattern.removeColumnAriaLabel', { - defaultMessage: 'Add or drag-and-drop a field to {groupLabel}', - values: { groupLabel: group.groupLabel }, + aria-label={i18n.translate('xpack.lens.indexPattern.suggestedValueAriaLabel', { + defaultMessage: 'Suggested value: {value} for {groupLabel}', + values: { value: group.suggestedValue?.(), groupLabel: group.groupLabel }, })} data-test-subj="lns-empty-dimension-suggested-value" onClick={() => { @@ -112,8 +142,8 @@ export function EmptyDimensionButton({ setNewColumnId(generateId()); }, [itemIndex]); - const dropProps = layerDatasource.getDropProps({ - ...layerDatasourceDropProps, + const dropProps = getDropProps(layerDatasource, { + ...(layerDatasourceDropProps || {}), dragging, columnId: newColumnId, filterOperations: group.filterOperations, @@ -151,6 +181,16 @@ export function EmptyDimensionButton({ [value, onDrop] ); + const buttonProps: EmptyButtonProps = { + columnId: value.columnId, + onClick, + group, + }; + + if (!dropProps) { + buttonProps.labels = noDndButtonLabels; + } + return (
{typeof group.suggestedValue?.() === 'number' ? ( - + ) : ( - + )}
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index d3574abe4f57a..c3f196c8e285a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -27,6 +27,7 @@ import { } from '../../../state_management'; import { AddLayerButton } from './add_layer'; import { getRemoveOperation } from '../../../utils'; +import { layerTypes } from '../../..'; export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: ConfigPanelWrapperProps) { const visualization = useLensSelector(selectVisualization); @@ -135,68 +136,66 @@ export function LayerPanels( [dispatchLens] ); - const datasourcePublicAPIs = props.framePublicAPI.datasourceLayers; - return ( - {layerIds.map((layerId, layerIndex) => - datasourcePublicAPIs[layerId] ? ( - { - // avoid state update if the datasource does not support initializeDimension - if ( - activeDatasourceId != null && - datasourceMap[activeDatasourceId]?.initializeDimension - ) { - dispatchLens( - setLayerDefaultDimension({ - layerId, - columnId, - groupId, - }) - ); - } - }} - onRemoveLayer={() => { + {layerIds.map((layerId, layerIndex) => ( + { + // avoid state update if the datasource does not support initializeDimension + if ( + activeDatasourceId != null && + datasourceMap[activeDatasourceId]?.initializeDimension + ) { dispatchLens( - removeOrClearLayer({ - visualizationId: activeVisualization.id, + setLayerDefaultDimension({ layerId, - layerIds, + columnId, + groupId, }) ); - removeLayerRef(layerId); - }} - toggleFullscreen={toggleFullscreen} - /> - ) : null - )} + } + }} + onRemoveLayer={() => { + dispatchLens( + removeOrClearLayer({ + visualizationId: activeVisualization.id, + layerId, + layerIds, + }) + ); + removeLayerRef(layerId); + }} + toggleFullscreen={toggleFullscreen} + /> + ))} { + onAddLayerClick={(layerType, noDatasource) => { const layerId = generateId(); - dispatchLens(addLayer({ layerId, layerType })); + dispatchLens( + addLayer({ layerId, layerType, noDatasource: layerType === layerTypes.ANNOTATIONS }) + ); trackUiEvent('layer_added'); setNextFocusedLayerId(layerId); }} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 404a40832fc2f..b3b937b09fd41 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -81,10 +81,10 @@ export function LayerPanel( updateDatasourceAsync, visualizationState, } = props; - const datasourcePublicAPI = framePublicAPI.datasourceLayers[layerId]; - const dateRange = useLensSelector(selectResolvedDateRange); + const datasourcePublicAPI = framePublicAPI.datasourceLayers?.[layerId]; const datasourceStates = useLensSelector(selectDatasourceStates); const isFullscreen = useLensSelector(selectIsFullscreenDatasource); + const dateRange = useLensSelector(selectResolvedDateRange); useEffect(() => { setActiveDimension(initialActiveDimensionState); @@ -104,8 +104,8 @@ export function LayerPanel( activeData: props.framePublicAPI.activeData, }; - const datasourceId = datasourcePublicAPI.datasourceId; - const layerDatasourceState = datasourceStates[datasourceId].state; + const datasourceId = datasourcePublicAPI?.datasourceId; + const layerDatasourceState = datasourceStates?.[datasourceId]?.state; const layerDatasourceDropProps = useMemo( () => ({ @@ -123,11 +123,10 @@ export function LayerPanel( const layerDatasourceConfigProps = { ...layerDatasourceDropProps, frame: props.framePublicAPI, - activeData: props.framePublicAPI.activeData, dateRange, }; - const { groups } = useMemo( + const { groups, noDatasource } = useMemo( () => activeVisualization.getConfiguration(layerVisualizationConfigProps), // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -140,7 +139,7 @@ export function LayerPanel( const isEmptyLayer = !groups.some((d) => d.accessors.length > 0); const { activeId, activeGroup } = activeDimension; - const columnLabelMap = layerDatasource.uniqueLabels(layerDatasourceConfigProps.state); + const columnLabelMap = layerDatasource?.uniqueLabels?.(layerDatasourceConfigProps?.state); const { setDimension, removeDimension } = activeVisualization; @@ -154,7 +153,7 @@ export function LayerPanel( registerNewRef: registerNewButtonRef, } = useFocusUpdate(allAccessors); - const layerDatasourceOnDrop = layerDatasource.onDrop; + const layerDatasourceOnDrop = layerDatasource?.onDrop; const onDrop = useMemo(() => { return ( @@ -180,16 +179,18 @@ export function LayerPanel( const filterOperations = group?.filterOperations || (() => false); - const dropResult = layerDatasourceOnDrop({ - ...layerDatasourceDropProps, - droppedItem, - columnId, - layerId: targetLayerId, - filterOperations, - dimensionGroups: groups, - groupId, - dropType, - }); + const dropResult = !noDatasource + ? layerDatasourceOnDrop({ + ...layerDatasourceDropProps, + droppedItem, + columnId, + layerId: targetLayerId, + filterOperations, + dimensionGroups: groups, + groupId, + dropType, + }) + : true; if (dropResult) { let previousColumn = typeof droppedItem.column === 'string' ? droppedItem.column : undefined; @@ -241,6 +242,7 @@ export function LayerPanel( removeDimension, layerDatasourceDropProps, setNextFocusedButtonId, + noDatasource, ]); const isDimensionPanelOpen = Boolean(activeId); @@ -341,7 +343,7 @@ export function LayerPanel( - {layerDatasource && ( + {layerDatasource && !noDatasource && ( { setActiveDimension({ @@ -478,42 +480,67 @@ export function LayerPanel( }} onRemoveClick={(id: string) => { trackUiEvent('indexpattern_dimension_removed'); - props.updateAll( - datasourceId, - layerDatasource.removeColumn({ - layerId, - columnId: id, - prevState: layerDatasourceState, - }), - activeVisualization.removeDimension({ - layerId, - columnId: id, - prevState: props.visualizationState, - frame: framePublicAPI, - }) - ); + if (datasourceId && layerDatasource) { + props.updateAll( + datasourceId, + layerDatasource.removeColumn({ + layerId, + columnId: id, + prevState: layerDatasourceState, + }), + activeVisualization.removeDimension({ + layerId, + columnId: id, + prevState: props.visualizationState, + frame: framePublicAPI, + }) + ); + } else { + props.updateVisualization( + activeVisualization.removeDimension({ + layerId, + columnId: id, + prevState: props.visualizationState, + frame: framePublicAPI, + }) + ); + } removeButtonRef(id); }} invalid={ - !layerDatasource.isValidColumn( + !noDatasource && + !layerDatasource?.isValidColumn( layerDatasourceState, layerId, columnId ) } > - + {noDatasource ? ( + <> + {activeVisualization?.renderDimensionTrigger?.({ + columnId, + layerId, + state: props.visualizationState, + hideTooltip, + invalid: group.invalid, + invalidMessage: group.invalidMessage, + })} + + ) : ( + + )}
@@ -536,7 +563,7 @@ export function LayerPanel( setActiveDimension({ activeGroup: group, activeId: id, - isNew: !group.supportStaticValue, + isNew: !group.supportStaticValue && !noDatasource, }); }} onDrop={onDrop} @@ -555,22 +582,25 @@ export function LayerPanel( isFullscreen={isFullscreen} groupLabel={activeGroup?.groupLabel || ''} handleClose={() => { - if ( - layerDatasource.canCloseDimensionEditor && - !layerDatasource.canCloseDimensionEditor(layerDatasourceState) - ) { - return false; - } - if (layerDatasource.updateStateOnCloseDimension) { - const newState = layerDatasource.updateStateOnCloseDimension({ - state: layerDatasourceState, - layerId, - columnId: activeId!, - }); - if (newState) { - props.updateDatasource(datasourceId, newState); + if (layerDatasource) { + if ( + layerDatasource.canCloseDimensionEditor && + !layerDatasource.canCloseDimensionEditor(layerDatasourceState) + ) { + return false; + } + if (layerDatasource.updateStateOnCloseDimension) { + const newState = layerDatasource.updateStateOnCloseDimension({ + state: layerDatasourceState, + layerId, + columnId: activeId!, + }); + if (newState) { + props.updateDatasource(datasourceId, newState); + } } } + setActiveDimension(initialActiveDimensionState); if (isFullscreen) { toggleFullscreen(); @@ -579,7 +609,7 @@ export function LayerPanel( }} panel={
- {activeGroup && activeId && ( + {activeGroup && activeId && !noDatasource && ( diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.ts b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.ts index f3c48bace4a5f..3318b8c30909e 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/droppable/get_drop_props.ts @@ -89,12 +89,13 @@ export function getDropProps(props: GetDropProps) { ) { const sourceColumn = state.layers[dragging.layerId].columns[dragging.columnId]; const targetColumn = state.layers[layerId].columns[columnId]; - const layerIndexPattern = state.indexPatterns[state.layers[layerId].indexPatternId]; - const isSameGroup = groupId === dragging.groupId; if (isSameGroup) { - return getDropPropsForSameGroup(targetColumn); - } else if (filterOperations(sourceColumn)) { + return getDropPropsForSameGroup(!targetColumn); + } + const layerIndexPattern = state.indexPatterns[state.layers[layerId].indexPatternId]; + + if (filterOperations(sourceColumn)) { return getDropPropsForCompatibleGroup( props.dimensionGroups, dragging.columnId, @@ -164,8 +165,8 @@ function getDropPropsForField({ return; } -function getDropPropsForSameGroup(targetColumn?: GenericIndexPatternColumn): DropProps { - return targetColumn ? { dropTypes: ['reorder'] } : { dropTypes: ['duplicate_compatible'] }; +function getDropPropsForSameGroup(isNew?: boolean): DropProps { + return !isNew ? { dropTypes: ['reorder'] } : { dropTypes: ['duplicate_compatible'] }; } function getDropPropsForCompatibleGroup( diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 56ff89f506c85..506d8f1c79df4 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -154,6 +154,7 @@ export const removeOrClearLayer = createAction<{ export const addLayer = createAction<{ layerId: string; layerType: LayerType; + noDatasource?: boolean; }>('lens/addLayer'); export const setLayerDefaultDimension = createAction<{ @@ -607,11 +608,12 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { [addLayer.type]: ( state, { - payload: { layerId, layerType }, + payload: { layerId, layerType, noDatasource }, }: { payload: { layerId: string; layerType: LayerType; + noDatasource?: boolean; }; } ) => { @@ -619,22 +621,22 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { return state; } - const activeDatasource = datasourceMap[state.activeDatasourceId]; const activeVisualization = visualizationMap[state.visualization.activeId]; - - const datasourceState = activeDatasource.insertLayer( - state.datasourceStates[state.activeDatasourceId].state, - layerId - ); - const visualizationState = activeVisualization.appendLayer!( state.visualization.state, layerId, layerType ); + const activeDatasource = noDatasource ? undefined : datasourceMap[state.activeDatasourceId]; + const { activeDatasourceState, activeVisualizationState } = addInitialValueIfAvailable({ - datasourceState, + datasourceState: activeDatasource + ? activeDatasource.insertLayer( + state.datasourceStates[state.activeDatasourceId].state, + layerId + ) + : state.datasourceStates[state.activeDatasourceId].state, visualizationState, framePublicAPI: { // any better idea to avoid `as`? @@ -710,7 +712,7 @@ function addInitialValueIfAvailable({ framePublicAPI: FramePublicAPI; visualizationState: unknown; datasourceState: unknown; - activeDatasource: Datasource; + activeDatasource?: Datasource; activeVisualization: Visualization; layerId: string; layerType: string; @@ -721,7 +723,11 @@ function addInitialValueIfAvailable({ .getSupportedLayers(visualizationState, framePublicAPI) .find(({ type }) => type === layerType); - if (layerInfo?.initialDimensions && activeDatasource?.initializeDimension) { + if ( + layerType !== 'annotations' && + layerInfo?.initialDimensions && + activeDatasource?.initializeDimension + ) { const info = groupId ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) : // pick the first available one if not passed @@ -743,6 +749,25 @@ function addInitialValueIfAvailable({ }; } } + if (layerType === 'annotations' && layerInfo?.initialDimensions) { + const info = groupId + ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) + : // pick the first available one if not passed + layerInfo.initialDimensions[0]; + + if (info) { + return { + activeDatasourceState: datasourceState, + activeVisualizationState: activeVisualization.setDimension({ + groupId: info.groupId, + layerId, + columnId: columnId || info.columnId, + prevState: visualizationState, + frame: framePublicAPI, + }), + }; + } + } return { activeDatasourceState: datasourceState, activeVisualizationState: visualizationState, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 30ef13025db62..51b482e21fc0f 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -198,6 +198,12 @@ interface ChartSettings { }; } +export type GetDropProps = DatasourceDimensionDropProps & { + groupId: string; + dragging: DragContextState['dragging']; + prioritizedOperation?: string; +}; + /** * Interface for the datasource registry */ @@ -249,11 +255,7 @@ export interface Datasource { props: DatasourceLayerPanelProps ) => ((cleanupElement: Element) => void) | void; getDropProps: ( - props: DatasourceDimensionDropProps & { - groupId: string; - dragging: DragContextState['dragging']; - prioritizedOperation?: string; - } + props: GetDropProps ) => { dropTypes: DropType[]; nextLabel?: string } | undefined; onDrop: (props: DatasourceDimensionDropHandlerProps) => false | true | { deleted: string }; /** @@ -431,6 +433,7 @@ export type DatasourceDimensionEditorProps = DatasourceDimensionPro isFullscreen: boolean; layerType: LayerType | undefined; supportStaticValue: boolean; + noDatasource?: boolean; paramEditorCustomProps?: ParamEditorCustomProps; supportFieldFormat?: boolean; }; @@ -581,6 +584,7 @@ export type VisualizationDimensionGroupConfig = SharedDimensionProps & { // need a special flag to know when to pass the previous column on duplicating requiresPreviousColumnOnDuplicate?: boolean; supportStaticValue?: boolean; + noDatasource?: boolean; paramEditorCustomProps?: ParamEditorCustomProps; supportFieldFormat?: boolean; }; @@ -800,6 +804,7 @@ export interface Visualization { * For consistency across different visualizations, the dimension configuration UI is standardized */ getConfiguration: (props: VisualizationConfigProps) => { + noDatasource?: boolean; groups: VisualizationDimensionGroupConfig[]; }; From 723d191cedf8c176e47d1ae65a532b5d833b69c4 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 15 Mar 2022 13:40:18 +0100 Subject: [PATCH 06/47] group the annotations into numerical icons --- src/plugins/event_annotation/common/types.ts | 2 +- .../annotations/expression.scss | 16 ++- .../annotations/expression.tsx | 131 +++++++++++------- .../public/xy_visualization/expression.tsx | 1 + 4 files changed, 101 insertions(+), 49 deletions(-) diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts index f681c9b208b3e..deb2bdeda6a18 100644 --- a/src/plugins/event_annotation/common/types.ts +++ b/src/plugins/event_annotation/common/types.ts @@ -18,6 +18,7 @@ export interface AnnotationConfig { annotationType: AnnotationType; label: string; message?: string; + axisMode: YAxisMode; color?: string; icon?: string; lineWidth?: number; @@ -25,7 +26,6 @@ export interface AnnotationConfig { iconPosition?: IconPosition; textVisibility?: boolean; isHidden?: boolean; - axisMode: YAxisMode; } export interface AnnotationState { diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss index 07946b52b0000..659b92b60a2e6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss @@ -15,4 +15,18 @@ margin-top: 100%; } } -} \ No newline at end of file +} + +.lnsXyAnnotationNumberIcon { + border-radius: $euiSize; + min-width: $euiSize; + height: $euiSize; + background-color: currentColor; +} + +.lnsXyAnnotationNumberIcon__text { + font-weight: 500; + font-size: 9px; + letter-spacing: -.5px; + line-height: 11px; +} diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index 2d02db65d5f75..8601c92fa88ef 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -7,7 +7,7 @@ import './expression.scss'; import React from 'react'; -import { EuiIcon } from '@elastic/eui'; +import { EuiFlexGroup, EuiIcon, EuiText } from '@elastic/eui'; import { AnnotationDomainType, AnnotationTooltipFormatter, @@ -22,6 +22,8 @@ import { hasIcon } from '../xy_config_panel/shared/icon_select'; export const ANNOTATIONS_MARKER_SIZE = 20; +const isNumericalString = (value?: string) => !value || !isNaN(Number(value)); + function getBaseIconPlacement(iconPosition?: IconPosition) { return iconPosition === 'below' ? Position.Bottom : Position.Top; } @@ -35,6 +37,21 @@ function mapVerticalToHorizontalPlacement(placement: Position) { } } +function NumberIcon({ number }: { number: number }) { + return ( + + + {number < 10 ? number : `9+`} + + + ); +} + function MarkerBody({ label, isHorizontal }: { label: string | undefined; isHorizontal: boolean }) { if (!label) { return null; @@ -69,27 +86,28 @@ interface MarkerConfig { axisMode?: YAxisMode; icon?: string; textVisibility?: boolean; + label?: string; } function Marker({ config, - label, isHorizontal, hasReducedPadding, }: { config: MarkerConfig; - label: string | undefined; isHorizontal: boolean; hasReducedPadding: boolean; }) { - // show an icon if present + if (isNumericalString(config.icon)) { + return ; + } if (hasIcon(config.icon)) { return ; } // if there's some text, check whether to show it as marker, or just show some padding for the icon if (config.textVisibility) { if (hasReducedPadding) { - return ; + return ; } return ; } @@ -114,14 +132,13 @@ export interface AnnotationsProps { formatter?: FieldFormat; isHorizontal: boolean; paddingMap: Partial>; + hide?: boolean; } -type CollectiveConfig = AnnotationConfig & { +interface CollectiveConfig extends AnnotationConfig { roundedTimestamp: number; - layerId: string; - hide: boolean; - customTooltipDetails: AnnotationTooltipFormatter | undefined; -}; + customTooltipDetails?: AnnotationTooltipFormatter | undefined; +} const groupVisibleConfigsByInterval = ( layers: XYAnnotationLayerConfig[], @@ -130,34 +147,23 @@ const groupVisibleConfigsByInterval = ( isBarChart?: boolean ) => { return layers - .flatMap(({ config: configs, layerId, hide }) => - configs - .filter((config) => !config.isHidden) - .map((config) => ({ - ...config, - roundedTimestamp: getRoundedTimestamp( - Number(config.key.timestamp), - firstTimestamp, - minInterval, - isBarChart - ), - layerId, - hide, - })) - ) - .reduce>( - (acc, current) => ({ + .flatMap(({ config: configs }) => configs.filter((config) => !config.isHidden)) + .reduce>((acc, current) => { + const roundedTimestamp = getRoundedTimestamp( + Number(current.key.timestamp), + firstTimestamp, + minInterval, + isBarChart + ); + return { ...acc, - [current.roundedTimestamp]: acc[current.roundedTimestamp] - ? [...acc[current.roundedTimestamp], current] - : [current], - }), - {} - ); + [roundedTimestamp]: acc[roundedTimestamp] ? [...acc[roundedTimestamp], current] : [current], + }; + }, {}); }; const createCustomTooltipDetails = - (config: CollectiveConfig[], formatter?: FieldFormat): AnnotationTooltipFormatter | undefined => + (config: AnnotationConfig[], formatter?: FieldFormat): AnnotationTooltipFormatter | undefined => () => { return (
@@ -176,6 +182,30 @@ const createCustomTooltipDetails = ); }; +function getCommonProperty( + configArr: AnnotationConfig[], + propertyName: K, + fallbackValue: T +) { + const firstStyle = configArr[0][propertyName]; + if (configArr.every((config) => firstStyle === config[propertyName])) { + return firstStyle; + } + return fallbackValue; +} + +const getCommonStyles = (configArr: AnnotationConfig[]) => { + return { + color: getCommonProperty( + configArr, + 'color', + defaultAnnotationColor + ), + lineWidth: getCommonProperty(configArr, 'lineWidth', 1), + lineStyle: getCommonProperty(configArr, 'lineStyle', 'solid'), + }; +}; + export const getCollectiveConfigsByInterval = ( layers: XYAnnotationLayerConfig[], minInterval?: number, @@ -189,14 +219,17 @@ export const getCollectiveConfigsByInterval = ( firstTimestamp, isBarChart ); - return Object.entries(visibleGroupedConfigs).map(([, configArr]) => { - let collectiveConfig = configArr[0]; + let collectiveConfig: CollectiveConfig; + return Object.entries(visibleGroupedConfigs).map(([roundedTimestamp, configArr]) => { + collectiveConfig = { ...configArr[0], roundedTimestamp: Number(roundedTimestamp) }; if (configArr.length > 1) { + const commonStyles = getCommonStyles(configArr); collectiveConfig = { ...collectiveConfig, + ...commonStyles, iconPosition: Position.Top as IconPosition, textVisibility: false, - icon: !collectiveConfig.hide ? 'checkInCircleFilled' : undefined, + icon: String(configArr.length), customTooltipDetails: createCustomTooltipDetails(configArr, formatter), }; } @@ -209,24 +242,28 @@ export const Annotations = ({ formatter, isHorizontal, paddingMap, + hide, }: AnnotationsProps) => { return ( <> {collectiveAnnotationConfigs.map((config) => { + const { roundedTimestamp } = config; const markerPositionVertical = getBaseIconPlacement(config.iconPosition); const hasReducedPadding = paddingMap[markerPositionVertical] === ANNOTATIONS_MARKER_SIZE; - const { label, roundedTimestamp, customTooltipDetails } = config; + const id = `${config.id}-line`; return ( } + marker={!hide ? : undefined} markerBody={ - + !hide ? ( + + ) : undefined } markerPosition={ isHorizontal @@ -237,10 +274,10 @@ export const Annotations = ({ { dataValue: Number(roundedTimestamp), header: formatter?.convert(roundedTimestamp) || String(roundedTimestamp), - details: label, + details: config.label, }, ]} - customTooltipDetails={customTooltipDetails} + customTooltipDetails={config.customTooltipDetails} style={{ line: { strokeWidth: config.lineWidth || 1, diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index bb102b5333f8d..f6b19b5a1404b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -1005,6 +1005,7 @@ export function XYChart({ ) : null} {collectiveAnnotationConfigs.length ? ( Date: Wed, 16 Mar 2022 07:52:41 +0100 Subject: [PATCH 07/47] color icons in tooltip, add the annotation icon, fix date interval bug --- .../public/assets/chart_bar_annotations.tsx | 19 ++++++++----------- .../annotations/expression.tsx | 4 ++-- .../xy_visualization/annotations/helpers.tsx | 3 +-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/lens/public/assets/chart_bar_annotations.tsx b/x-pack/plugins/lens/public/assets/chart_bar_annotations.tsx index 3d31e69f910c4..63fc9023533f6 100644 --- a/x-pack/plugins/lens/public/assets/chart_bar_annotations.tsx +++ b/x-pack/plugins/lens/public/assets/chart_bar_annotations.tsx @@ -14,9 +14,9 @@ export const LensIconChartBarAnnotations = ({ ...props }: Omit) => ( {title} : null} - + diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index 8601c92fa88ef..e8c36dfe6b491 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -167,10 +167,10 @@ const createCustomTooltipDetails = () => { return (
- {config.map(({ icon, label, key }) => ( + {config.map(({ icon, label, key, color }) => (
- {hasIcon(icon) ? : null} + {hasIcon(icon) ? : null} {label} diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index 9125012be1acd..dd4f1009ef54a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -24,7 +24,6 @@ import { defaultAnnotationColor } from '../../../../../../src/plugins/event_anno const MAX_DATE = Number(new Date(8640000000000000)); const MIN_DATE = Number(new Date(-8640000000000000)); -// TODO with date export function getStaticDate( dataLayers: XYDataLayerConfig[], activeData: FramePublicAPI['activeData'] @@ -66,7 +65,7 @@ export const getAnnotationsSupportedLayer = ( const hasDateHistogram = dataLayers.every( (dataLayer) => - dataLayer.xAccessor || + dataLayer.xAccessor && checkScaleOperation('interval', 'date', frame?.datasourceLayers || {})(dataLayer) ); From b97ba4c7250ee5318f7914cdcf50d303cb9dfd33 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 16 Mar 2022 08:22:46 +0100 Subject: [PATCH 08/47] display old time axis for annotations --- .../plugins/lens/public/xy_visualization/expression.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index f6b19b5a1404b..50822954bf385 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -586,8 +586,14 @@ export function XYChart({ !chartHasMoreThanOneBarSeries) ); + const bottomAnnotationsExist = collectiveAnnotationConfigs.length && linesPaddings.bottom; + const shouldUseNewTimeAxis = - isTimeViz && isHistogramModeEnabled && !useLegacyTimeAxis && !shouldRotate; + isTimeViz && + isHistogramModeEnabled && + !useLegacyTimeAxis && + !shouldRotate && + !bottomAnnotationsExist; const gridLineStyle = { visible: gridlinesVisibilitySettings?.x, From df2d6d8e584b888273313340651dba08f0b8114a Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 16 Mar 2022 08:25:08 +0100 Subject: [PATCH 09/47] error in annotation dimension when date histogram is removed --- .../lens/public/xy_visualization/annotations/helpers.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index dd4f1009ef54a..6b26e3ac140f0 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -154,6 +154,12 @@ export const getAnnotationsConfiguration = ({ }) => { const dataLayers = getDataLayers(state.layers); + const hasDateHistogram = dataLayers.every( + (dataLayer) => + dataLayer.xAccessor && + checkScaleOperation('interval', 'date', frame?.datasourceLayers || {})(dataLayer) + ); + return { noDatasource: true, groups: [ @@ -162,7 +168,7 @@ export const getAnnotationsConfiguration = ({ groupLabel: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), accessors: getAnnotationsAccessorColorConfig(layer), dataTestSubj: 'lnsXY_xAnnotationsPanel', - invalid: !dataLayers.some(({ xAccessor }) => xAccessor != null), + invalid: !hasDateHistogram, invalidMessage: i18n.translate('xpack.lens.xyChart.addAnnotationsLayerLabelDisabledHelp', { defaultMessage: 'Annotations require a time based chart to work. Add a date histogram.', }), From 0d8edbdc003638d4a0b446f38e1d6ef8a843e556 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 16 Mar 2022 09:20:02 +0100 Subject: [PATCH 10/47] refactor: use the same methods for annotations and reference lines --- .../annotations/expression.tsx | 125 ++------- .../xy_visualization/annotations_helpers.tsx | 241 ++++++++++++++++++ .../public/xy_visualization/expression.tsx | 8 +- .../expression_reference_lines.tsx | 230 +++-------------- 4 files changed, 302 insertions(+), 302 deletions(-) create mode 100644 x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index e8c36dfe6b491..eafc22654d8db 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -7,7 +7,7 @@ import './expression.scss'; import React from 'react'; -import { EuiFlexGroup, EuiIcon, EuiText } from '@elastic/eui'; +import { EuiIcon } from '@elastic/eui'; import { AnnotationDomainType, AnnotationTooltipFormatter, @@ -17,102 +17,15 @@ import { import type { FieldFormat } from 'src/plugins/field_formats/common'; import { AnnotationConfig } from 'src/plugins/event_annotation/common'; import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; -import type { IconPosition, YAxisMode, XYAnnotationLayerConfig } from '../../../common/expressions'; +import type { IconPosition, XYAnnotationLayerConfig } from '../../../common/expressions'; import { hasIcon } from '../xy_config_panel/shared/icon_select'; - -export const ANNOTATIONS_MARKER_SIZE = 20; - -const isNumericalString = (value?: string) => !value || !isNaN(Number(value)); - -function getBaseIconPlacement(iconPosition?: IconPosition) { - return iconPosition === 'below' ? Position.Bottom : Position.Top; -} - -function mapVerticalToHorizontalPlacement(placement: Position) { - switch (placement) { - case Position.Top: - return Position.Right; - case Position.Bottom: - return Position.Left; - } -} - -function NumberIcon({ number }: { number: number }) { - return ( - - - {number < 10 ? number : `9+`} - - - ); -} - -function MarkerBody({ label, isHorizontal }: { label: string | undefined; isHorizontal: boolean }) { - if (!label) { - return null; - } - if (isHorizontal) { - return ( -
- {label} -
- ); - } - return ( -
-
- {label} -
-
- ); -} - -interface MarkerConfig { - axisMode?: YAxisMode; - icon?: string; - textVisibility?: boolean; - label?: string; -} - -function Marker({ - config, - isHorizontal, - hasReducedPadding, -}: { - config: MarkerConfig; - isHorizontal: boolean; - hasReducedPadding: boolean; -}) { - if (isNumericalString(config.icon)) { - return ; - } - if (hasIcon(config.icon)) { - return ; - } - // if there's some text, check whether to show it as marker, or just show some padding for the icon - if (config.textVisibility) { - if (hasReducedPadding) { - return ; - } - return ; - } - return null; -} +import { + getBaseIconPlacement, + mapVerticalToHorizontalPlacement, + LINES_MARKER_SIZE, + MarkerBody, + Marker, +} from '../annotations_helpers'; const getRoundedTimestamp = ( timestamp: number, @@ -203,6 +116,9 @@ const getCommonStyles = (configArr: AnnotationConfig[]) => { ), lineWidth: getCommonProperty(configArr, 'lineWidth', 1), lineStyle: getCommonProperty(configArr, 'lineStyle', 'solid'), + iconPosition: getCommonProperty(configArr, 'iconPosition', Position.Top as IconPosition), + label: getCommonProperty(configArr, 'label', ''), + textVisibility: getCommonProperty(configArr, 'textVisibility', false), }; }; @@ -227,8 +143,6 @@ export const getCollectiveConfigsByInterval = ( collectiveConfig = { ...collectiveConfig, ...commonStyles, - iconPosition: Position.Top as IconPosition, - textVisibility: false, icon: String(configArr.length), customTooltipDetails: createCustomTooltipDetails(configArr, formatter), }; @@ -249,14 +163,25 @@ export const Annotations = ({ {collectiveAnnotationConfigs.map((config) => { const { roundedTimestamp } = config; const markerPositionVertical = getBaseIconPlacement(config.iconPosition); - const hasReducedPadding = paddingMap[markerPositionVertical] === ANNOTATIONS_MARKER_SIZE; + const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE; const id = `${config.id}-line`; return ( : undefined} + marker={ + !hide ? ( + + ) : undefined + } markerBody={ !hide ? ( >, + labelVisibility: Partial>, + titleVisibility: Partial>, + axesMap: Record<'left' | 'right', unknown>, + isHorizontal: boolean +) => { + const result: Partial> = {}; + if (!labelVisibility?.x && !titleVisibility?.x && referenceLinePaddings.bottom) { + const placement = isHorizontal ? mapVerticalToHorizontalPlacement('bottom') : 'bottom'; + result[placement] = referenceLinePaddings.bottom; + } + if ( + referenceLinePaddings.left && + (isHorizontal || (!labelVisibility?.yLeft && !titleVisibility?.yLeft)) + ) { + const placement = isHorizontal ? mapVerticalToHorizontalPlacement('left') : 'left'; + result[placement] = referenceLinePaddings.left; + } + if ( + referenceLinePaddings.right && + (isHorizontal || !axesMap.right || (!labelVisibility?.yRight && !titleVisibility?.yRight)) + ) { + const placement = isHorizontal ? mapVerticalToHorizontalPlacement('right') : 'right'; + result[placement] = referenceLinePaddings.right; + } + // there's no top axis, so just check if a margin has been computed + if (referenceLinePaddings.top) { + const placement = isHorizontal ? mapVerticalToHorizontalPlacement('top') : 'top'; + result[placement] = referenceLinePaddings.top; + } + return result; +}; + +// Note: it does not take into consideration whether the reference line is in view or not + +export const getLinesCausedPaddings = ( + visualConfigs: Array< + Pick | undefined + >, + axesMap: Record<'left' | 'right', unknown> +) => { + // collect all paddings for the 4 axis: if any text is detected double it. + const paddings: Partial> = {}; + const icons: Partial> = {}; + visualConfigs?.forEach((config) => { + if (!config) { + return; + } + const { axisMode, icon, iconPosition, textVisibility } = config; + if (axisMode && (hasIcon(icon) || textVisibility)) { + const placement = getBaseIconPlacement(iconPosition, axesMap, axisMode); + paddings[placement] = Math.max( + paddings[placement] || 0, + LINES_MARKER_SIZE * (textVisibility ? 2 : 1) // double the padding size if there's text + ); + icons[placement] = (icons[placement] || 0) + (hasIcon(icon) ? 1 : 0); + } + }); + // post-process the padding based on the icon presence: + // if no icon is present for the placement, just reduce the padding + (Object.keys(paddings) as Position[]).forEach((placement) => { + if (!icons[placement]) { + paddings[placement] = LINES_MARKER_SIZE; + } + }); + return paddings; +}; + +export function mapVerticalToHorizontalPlacement(placement: Position) { + switch (placement) { + case Position.Top: + return Position.Right; + case Position.Bottom: + return Position.Left; + case Position.Left: + return Position.Bottom; + case Position.Right: + return Position.Top; + } +} + +// if there's just one axis, put it on the other one +// otherwise use the same axis +// this function assume the chart is vertical +export function getBaseIconPlacement( + iconPosition: IconPosition | undefined, + axesMap?: Record, + axisMode?: YAxisMode +) { + if (iconPosition === 'auto') { + if (axisMode === 'bottom') { + return Position.Top; + } + if (axesMap) { + if (axisMode === 'left') { + return axesMap.right ? Position.Left : Position.Right; + } + return axesMap.left ? Position.Right : Position.Left; + } + } + + if (iconPosition === 'left') { + return Position.Left; + } + if (iconPosition === 'right') { + return Position.Right; + } + if (iconPosition === 'below') { + return Position.Bottom; + } + return Position.Top; +} + +export function MarkerBody({ + label, + isHorizontal, +}: { + label: string | undefined; + isHorizontal: boolean; +}) { + if (!label) { + return null; + } + if (isHorizontal) { + return ( +
+ {label} +
+ ); + } + return ( +
+
+ {label} +
+
+ ); +} + +interface MarkerConfig { + axisMode?: YAxisMode; + icon?: string; + textVisibility?: boolean; +} + +export function getMarkerToShow( + markerConfig: MarkerConfig, + label: string | undefined, + isHorizontal: boolean, + hasReducedPadding: boolean +) { + // show an icon if present + if (hasIcon(markerConfig.icon)) { + return ; + } + // if there's some text, check whether to show it as marker, or just show some padding for the icon + if (markerConfig.textVisibility) { + if (hasReducedPadding) { + return ; + } + return ; + } +} + +const isNumericalString = (value?: string) => value && !isNaN(Number(value)); + +function NumberIcon({ number }: { number: number }) { + return ( + + + {number < 10 ? number : `9+`} + + + ); +} + +interface MarkerConfig { + axisMode?: YAxisMode; + icon?: string; + textVisibility?: boolean; +} + +export function Marker({ + config, + isHorizontal, + hasReducedPadding, + label, +}: { + config: MarkerConfig; + isHorizontal: boolean; + hasReducedPadding: boolean; + label?: string; +}) { + console.log('hello', config); + if (isNumericalString(config.icon)) { + return ; + } + if (hasIcon(config.icon)) { + return ; + } + // if there's some text, check whether to show it as marker, or just show some padding for the icon + if (config.textVisibility) { + if (hasReducedPadding) { + return ; + } + return ; + } + return null; +} diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 50822954bf385..2c108717ef8d7 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -72,11 +72,9 @@ import { getAxesConfiguration, GroupsConfiguration, validateExtent } from './axe import { getColorAssignments } from './color_assignment'; import { getXDomain, XyEndzones } from './x_domain'; import { getLegendAction } from './get_legend_action'; -import { - computeChartMargins, - getLinesCausedPaddings, - ReferenceLineAnnotations, -} from './expression_reference_lines'; +import { ReferenceLineAnnotations } from './expression_reference_lines'; + +import { computeChartMargins, getLinesCausedPaddings } from './annotations_helpers'; import { Annotations, getCollectiveConfigsByInterval } from './annotations/expression'; import { computeOverallDataDomain } from './reference_line_helpers'; diff --git a/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx b/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx index 28ac25c283ed5..7817db573e419 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression_reference_lines.tsx @@ -8,193 +8,19 @@ import './expression_reference_lines.scss'; import React from 'react'; import { groupBy } from 'lodash'; -import { EuiIcon } from '@elastic/eui'; import { RectAnnotation, AnnotationDomainType, LineAnnotation, Position } from '@elastic/charts'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import type { FieldFormat } from 'src/plugins/field_formats/common'; -import type { - IconPosition, - ReferenceLineLayerArgs, - YAxisMode, - YConfig, -} from '../../common/expressions'; +import type { ReferenceLineLayerArgs } from '../../common/expressions'; import type { LensMultiTable } from '../../common/types'; -import { hasIcon } from './xy_config_panel/shared/icon_select'; import { defaultReferenceLineColor } from './color_assignment'; - -export const REFERENCE_LINE_MARKER_SIZE = 20; - -export const computeChartMargins = ( - referenceLinePaddings: Partial>, - labelVisibility: Partial>, - titleVisibility: Partial>, - axesMap: Record<'left' | 'right', unknown>, - isHorizontal: boolean -) => { - const result: Partial> = {}; - if (!labelVisibility?.x && !titleVisibility?.x && referenceLinePaddings.bottom) { - const placement = isHorizontal ? mapVerticalToHorizontalPlacement('bottom') : 'bottom'; - result[placement] = referenceLinePaddings.bottom; - } - if ( - referenceLinePaddings.left && - (isHorizontal || (!labelVisibility?.yLeft && !titleVisibility?.yLeft)) - ) { - const placement = isHorizontal ? mapVerticalToHorizontalPlacement('left') : 'left'; - result[placement] = referenceLinePaddings.left; - } - if ( - referenceLinePaddings.right && - (isHorizontal || !axesMap.right || (!labelVisibility?.yRight && !titleVisibility?.yRight)) - ) { - const placement = isHorizontal ? mapVerticalToHorizontalPlacement('right') : 'right'; - result[placement] = referenceLinePaddings.right; - } - // there's no top axis, so just check if a margin has been computed - if (referenceLinePaddings.top) { - const placement = isHorizontal ? mapVerticalToHorizontalPlacement('top') : 'top'; - result[placement] = referenceLinePaddings.top; - } - return result; -}; - -// Note: it does not take into consideration whether the reference line is in view or not - -export const getLinesCausedPaddings = ( - visualConfigs: Array< - Pick | undefined - >, - axesMap: Record<'left' | 'right', unknown> -) => { - // collect all paddings for the 4 axis: if any text is detected double it. - const paddings: Partial> = {}; - const icons: Partial> = {}; - visualConfigs?.forEach((config) => { - if (!config) { - return; - } - const { axisMode, icon, iconPosition, textVisibility } = config; - if (axisMode && (hasIcon(icon) || textVisibility)) { - const placement = getBaseIconPlacement(iconPosition, axisMode, axesMap); - paddings[placement] = Math.max( - paddings[placement] || 0, - REFERENCE_LINE_MARKER_SIZE * (textVisibility ? 2 : 1) // double the padding size if there's text - ); - icons[placement] = (icons[placement] || 0) + (hasIcon(icon) ? 1 : 0); - } - }); - - // post-process the padding based on the icon presence: - // if no icon is present for the placement, just reduce the padding - (Object.keys(paddings) as Position[]).forEach((placement) => { - if (!icons[placement]) { - paddings[placement] = REFERENCE_LINE_MARKER_SIZE; - } - }); - return paddings; -}; - -function mapVerticalToHorizontalPlacement(placement: Position) { - switch (placement) { - case Position.Top: - return Position.Right; - case Position.Bottom: - return Position.Left; - case Position.Left: - return Position.Bottom; - case Position.Right: - return Position.Top; - } -} - -// if there's just one axis, put it on the other one -// otherwise use the same axis -// this function assume the chart is vertical -function getBaseIconPlacement( - iconPosition: IconPosition | undefined, - axisMode: YAxisMode | undefined, - axesMap: Record -) { - if (iconPosition === 'auto') { - if (axisMode === 'bottom') { - return Position.Top; - } - if (axisMode === 'left') { - return axesMap.right ? Position.Left : Position.Right; - } - return axesMap.left ? Position.Right : Position.Left; - } - - if (iconPosition === 'left') { - return Position.Left; - } - if (iconPosition === 'right') { - return Position.Right; - } - if (iconPosition === 'below') { - return Position.Bottom; - } - return Position.Top; -} - -function getMarkerBody(label: string | undefined, isHorizontal: boolean) { - if (!label) { - return; - } - if (isHorizontal) { - return ( -
- {label} -
- ); - } - return ( -
-
- {label} -
-
- ); -} - -interface MarkerConfig { - axisMode?: YAxisMode; - icon?: string; - textVisibility?: boolean; -} - -export function getMarkerToShow( - markerConfig: MarkerConfig, - label: string | undefined, - isHorizontal: boolean, - hasReducedPadding: boolean -) { - // show an icon if present - if (hasIcon(markerConfig.icon)) { - return ; - } - // if there's some text, check whether to show it as marker, or just show some padding for the icon - if (markerConfig.textVisibility) { - if (hasReducedPadding) { - return getMarkerBody( - label, - (!isHorizontal && markerConfig.axisMode === 'bottom') || - (isHorizontal && markerConfig.axisMode !== 'bottom') - ); - } - return ; - } -} +import { + MarkerBody, + Marker, + LINES_MARKER_SIZE, + mapVerticalToHorizontalPlacement, + getBaseIconPlacement, +} from './annotations_helpers'; export interface ReferenceLineAnnotationsProps { layers: ReferenceLineLayerArgs[]; @@ -254,27 +80,37 @@ export const ReferenceLineAnnotations = ({ // get the position for vertical chart const markerPositionVertical = getBaseIconPlacement( yConfig.iconPosition, - yConfig.axisMode, - axesMap + axesMap, + yConfig.axisMode ); // the padding map is built for vertical chart - const hasReducedPadding = - paddingMap[markerPositionVertical] === REFERENCE_LINE_MARKER_SIZE; + const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE; const props = { groupId, - marker: getMarkerToShow( - yConfig, - columnToLabelMap[yConfig.forAccessor], - isHorizontal, - hasReducedPadding + marker: ( + ), - markerBody: getMarkerBody( - yConfig.textVisibility && !hasReducedPadding - ? columnToLabelMap[yConfig.forAccessor] - : undefined, - (!isHorizontal && yConfig.axisMode === 'bottom') || - (isHorizontal && yConfig.axisMode !== 'bottom') + markerBody: ( + ), // rotate the position if required markerPosition: isHorizontal From d99ea65f41bfeab21b132fd25c3dad2befe2e360 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 16 Mar 2022 09:20:49 +0100 Subject: [PATCH 11/47] wip --- src/plugins/event_annotation/common/index.ts | 2 +- src/plugins/event_annotation/common/types.ts | 16 +--------------- .../public/event_annotation_service/service.tsx | 4 ++-- .../public/event_annotation_service/types.ts | 4 ++-- .../annotations/config_panel.tsx | 12 ++++++++---- .../xy_visualization/annotations/helpers.tsx | 8 +++++++- .../public/xy_visualization/to_expression.ts | 4 +++- 7 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts index ac1a7592a220f..1087cb9f560d7 100644 --- a/src/plugins/event_annotation/common/index.ts +++ b/src/plugins/event_annotation/common/index.ts @@ -7,4 +7,4 @@ */ export { annotationConfig, annotationKeyConfig } from './annotation_expression'; -export type { AnnotationConfig, AnnotationState } from './types'; +export type { AnnotationConfig } from './types'; diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts index deb2bdeda6a18..d10aaffbc10d8 100644 --- a/src/plugins/event_annotation/common/types.ts +++ b/src/plugins/event_annotation/common/types.ts @@ -18,7 +18,6 @@ export interface AnnotationConfig { annotationType: AnnotationType; label: string; message?: string; - axisMode: YAxisMode; color?: string; icon?: string; lineWidth?: number; @@ -26,20 +25,7 @@ export interface AnnotationConfig { iconPosition?: IconPosition; textVisibility?: boolean; isHidden?: boolean; -} - -export interface AnnotationState { - id: string; - timestamp: number; - label: string; - message?: string; - textVisibility?: boolean; - icon?: string; - iconPosition?: IconPosition; - lineStyle?: LineStyle; - lineWidth?: number; - color?: string; - isHidden?: boolean; + axisMode: YAxisMode; } export type AnnotationResult = AnnotationConfig & { type: 'annotation_config' }; diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index 7e86037c379dd..46f9e4891dbf3 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -28,7 +28,7 @@ export function getAnnotationService(): EventAnnotationServiceType { icon, iconPosition, textVisibility, - timestamp, + key, }) => { return { type: 'expression', @@ -47,7 +47,7 @@ export function getAnnotationService(): EventAnnotationServiceType { function: 'annotation_key', arguments: { keyType: ['point_in_time'], - timestamp: [timestamp], + timestamp: [key.timestamp], }, }, ], diff --git a/src/plugins/event_annotation/public/event_annotation_service/types.ts b/src/plugins/event_annotation/public/event_annotation_service/types.ts index 2a7a1f885de3c..078073c2538fb 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/types.ts +++ b/src/plugins/event_annotation/public/event_annotation_service/types.ts @@ -7,8 +7,8 @@ */ import { ExpressionAstExpression } from '../../../expressions/common/ast'; -import { AnnotationState } from '../../common/types'; +import { AnnotationConfig } from '../../common'; export interface EventAnnotationServiceType { - toExpression: (props: AnnotationState) => ExpressionAstExpression; + toExpression: (props: AnnotationConfig) => ExpressionAstExpression; } diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx index 28411201c750c..ef6aece1f701b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiDatePicker, EuiFormRow, EuiSwitch } from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import moment from 'moment'; -import { AnnotationState } from 'src/plugins/event_annotation/common/types'; +import { AnnotationConfig } from 'src/plugins/event_annotation/common/types'; import type { VisualizationDimensionEditorProps } from '../../types'; import { State, XYState } from '../types'; import { FormatFactory } from '../../../common'; @@ -48,7 +48,7 @@ export const AnnotationsPanel = ( const currentConfig = localLayer.config?.find((c) => c.id === accessor); const setConfig = useCallback( - (config: Partial | undefined) => { + (config: Partial | undefined) => { if (config == null) { return; } @@ -75,11 +75,15 @@ export const AnnotationsPanel = ( > { if (date) { setConfig({ - timestamp: date?.valueOf(), + key: { + ...(currentConfig?.key || { keyType: 'point_in_time' }), + type: 'annotation_key', + timestamp: date?.valueOf(), + }, }); } }} diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index 6b26e3ac140f0..e3e2ee0ba309b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -121,7 +121,13 @@ export const setAnnotationsDimension: Visualization['setDimension'] = ( label: i18n.translate('xpack.lens.xyChart.defaultAnnotationLabel', { defaultMessage: 'Static Annotation', }), - timestamp: newTimestamp, + annotationType: 'manual', + axisMode: 'bottom', + key: { + keyType: 'point_in_time', + type: 'annotation_key', + timestamp: newTimestamp, + }, ...previousConfig, id: columnId, }, diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 70d3c991b29a5..dfe597d84bf6a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -405,7 +405,9 @@ const annotationLayerToExpression = ( (config): Ast => eventAnnotationService.toExpression({ id: config.id, - timestamp: config.timestamp, + annotationType: config.annotationType, + key: config.key, + axisMode: 'bottom', label: config.label || defaultAnnotationLabel, textVisibility: config.textVisibility, icon: config.icon, From fc451cb682cfeedb18027c46b96de619b9015b03 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 16 Mar 2022 10:47:23 +0100 Subject: [PATCH 12/47] only check activeData for dataLayers --- .../xy_visualization/annotations/helpers.tsx | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index e3e2ee0ba309b..dd9e0d3609040 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -30,28 +30,26 @@ export function getStaticDate( ) { const fallbackValue = +new Date(Date.now()); - if (!activeData || Object.values(activeData).every(({ rows }) => !rows || !rows.length)) { + const dataLayersId = dataLayers.map(({ layerId }) => layerId); + if ( + !activeData || + Object.entries(activeData) + .filter(([key]) => dataLayersId.includes(key)) + .every(([, { rows }]) => !rows || !rows.length) + ) { return fallbackValue; } - const dataLayersId = dataLayers.map(({ layerId }) => layerId); const minDate = dataLayersId.reduce((acc, lId) => { const xAccessor = dataLayers.find((dataLayer) => dataLayer.layerId === lId)?.xAccessor!; - const layerMinTimestamp = activeData[lId]?.rows?.[0]?.[xAccessor]; - if (layerMinTimestamp && layerMinTimestamp < acc) { - return layerMinTimestamp; - } - return acc; + const firstTimestamp = activeData[lId]?.rows?.[0]?.[xAccessor]; + return firstTimestamp && firstTimestamp < acc ? firstTimestamp : acc; }, MAX_DATE); const maxDate = dataLayersId.reduce((acc, lId) => { const xAccessor = dataLayers.find((dataLayer) => dataLayer.layerId === lId)?.xAccessor!; - const layerMinTimestamp = - activeData[lId]?.rows?.[activeData?.[lId]?.rows?.length - 1]?.[xAccessor]; - if (layerMinTimestamp && layerMinTimestamp > acc) { - return layerMinTimestamp; - } - return acc; + const lastTimestamp = activeData[lId]?.rows?.[activeData?.[lId]?.rows?.length - 1]?.[xAccessor]; + return lastTimestamp && lastTimestamp > acc ? lastTimestamp : acc; }, MIN_DATE); const middleDate = (minDate + maxDate) / 2; return middleDate; From 512b09aa5644600f9f672cfcf9e52668e1c56cbf Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 16 Mar 2022 12:03:04 +0100 Subject: [PATCH 13/47] added new icons for annotations --- .../public/assets/annotation_icons/circle.tsx | 32 +++++++++++ .../assets/annotation_icons/hexagon.tsx | 31 ++++++++++ .../public/assets/annotation_icons/index.tsx | 11 ++++ .../public/assets/annotation_icons/square.tsx | 28 ++++++++++ .../assets/annotation_icons/triangle.tsx | 29 ++++++++++ .../annotations/config_panel.tsx | 35 ++++++++++++ .../annotations/expression.tsx | 3 +- .../xy_visualization/annotations/helpers.tsx | 1 + .../xy_visualization/annotations_helpers.tsx | 56 +++++++++---------- .../xy_config_panel/shared/icon_select.tsx | 24 +++++--- .../shared/marker_decoration_settings.tsx | 5 +- 11 files changed, 215 insertions(+), 40 deletions(-) create mode 100644 x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx create mode 100644 x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx create mode 100644 x-pack/plugins/lens/public/assets/annotation_icons/index.tsx create mode 100644 x-pack/plugins/lens/public/assets/annotation_icons/square.tsx create mode 100644 x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx new file mode 100644 index 0000000000000..76b753e9af990 --- /dev/null +++ b/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +interface SVGRProps { + title?: string; + titleId?: string; +} + +const IconCircle = ({ title, titleId, ...props }: React.SVGProps & SVGRProps) => ( + + {title ? {title} : null} + + +); + +export const Circle = IconCircle; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx new file mode 100644 index 0000000000000..ced02345fbc9f --- /dev/null +++ b/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +interface SVGRProps { + title?: string; + titleId?: string; +} + +const IconHexagon = ({ title, titleId, ...props }: React.SVGProps & SVGRProps) => ( + + {title ? {title} : null} + + +); + +export const Hexagon = IconHexagon; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx new file mode 100644 index 0000000000000..9a85f56b19f8c --- /dev/null +++ b/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { Circle } from './circle'; +export { Hexagon } from './hexagon'; +export { Square } from './square'; +export { Triangle } from './triangle'; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx new file mode 100644 index 0000000000000..c5880e459f182 --- /dev/null +++ b/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +interface SVGRProps { + title?: string; + titleId?: string; +} + +const IconSquare = ({ title, titleId, ...props }: React.SVGProps & SVGRProps) => ( + + {title ? {title} : null} + + +); + +export const Square = IconSquare; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx new file mode 100644 index 0000000000000..f567e05f0622f --- /dev/null +++ b/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import * as React from 'react'; +interface SVGRProps { + title?: string; + titleId?: string; +} + +const IconTriangle = ({ title, titleId, ...props }: React.SVGProps & SVGRProps) => ( + + {title ? {title} : null} + + +); + +export const Triangle = IconTriangle; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx index ef6aece1f701b..8faade4ff70d9 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx @@ -21,6 +21,8 @@ import { isHorizontalChart } from '../state_helpers'; import { MarkerDecorationSettings } from '../xy_config_panel/shared/marker_decoration_settings'; import { LineStyleSettings } from '../xy_config_panel/shared/line_style_settings'; import { updateLayer } from '../xy_config_panel'; +import { Square, Triangle, Hexagon, Circle } from '../../assets/annotation_icons'; +import { euiIconsSet } from '../xy_config_panel/shared/icon_select'; export const defaultAnnotationLabel = i18n.translate('xpack.lens.xyChart.defaultAnnotationLabel', { defaultMessage: 'Static Annotation', @@ -102,6 +104,7 @@ export const AnnotationsPanel = ( isHorizontal={isHorizontal} setConfig={setConfig} currentConfig={currentConfig} + customIconSet={annotationsIconSet} /> ); }; + +const annotationsIconSet = [ + { + value: 'triangle', + label: i18n.translate('xpack.lens.xyChart.iconSelect.triangleIconLabel', { + defaultMessage: 'Triangle', + }), + icon: Triangle, + }, + { + value: 'square', + label: i18n.translate('xpack.lens.xyChart.iconSelect.squareIconLabel', { + defaultMessage: 'Square', + }), + icon: Square, + }, + { + value: 'circle', + label: i18n.translate('xpack.lens.xyChart.iconSelect.circleIconLabel', { + defaultMessage: 'Circle', + }), + icon: Circle, + }, + { + value: 'hexagon', + label: i18n.translate('xpack.lens.xyChart.iconSelect.hexagonIconLabel', { + defaultMessage: 'Hexagon', + }), + icon: Hexagon, + }, + ...euiIconsSet, +]; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index eafc22654d8db..9e0ba852e5faf 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -25,6 +25,7 @@ import { LINES_MARKER_SIZE, MarkerBody, Marker, + AnnotationIcon, } from '../annotations_helpers'; const getRoundedTimestamp = ( @@ -83,7 +84,7 @@ const createCustomTooltipDetails = {config.map(({ icon, label, key, color }) => (
- {hasIcon(icon) ? : null} + {hasIcon(icon) ? : null} {label} diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index dd9e0d3609040..a0439717146d2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -126,6 +126,7 @@ export const setAnnotationsDimension: Visualization['setDimension'] = ( type: 'annotation_key', timestamp: newTimestamp, }, + icon: 'triangle', ...previousConfig, id: columnId, }, diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx index 4fcff6841452b..c1800a3869992 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx @@ -7,10 +7,11 @@ import './expression_reference_lines.scss'; import React from 'react'; -import { EuiFlexGroup, EuiIcon, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText, IconType } from '@elastic/eui'; import { Position } from '@elastic/charts'; import type { IconPosition, YAxisMode, YConfig } from '../../common/expressions'; import { hasIcon } from './xy_config_panel/shared/icon_select'; +import { Circle, Hexagon, Square, Triangle } from '../assets/annotation_icons'; export const LINES_MARKER_SIZE = 20; @@ -164,32 +165,19 @@ export function MarkerBody({ ); } -interface MarkerConfig { - axisMode?: YAxisMode; - icon?: string; - textVisibility?: boolean; -} +const isNumericalString = (value: string) => !isNaN(Number(value)); -export function getMarkerToShow( - markerConfig: MarkerConfig, - label: string | undefined, - isHorizontal: boolean, - hasReducedPadding: boolean -) { - // show an icon if present - if (hasIcon(markerConfig.icon)) { - return ; - } - // if there's some text, check whether to show it as marker, or just show some padding for the icon - if (markerConfig.textVisibility) { - if (hasReducedPadding) { - return ; - } - return ; - } -} +const shapesIconMap = { + circle: Circle, + hexagon: Hexagon, + triangle: Triangle, + square: Square, +} as const; -const isNumericalString = (value?: string) => value && !isNaN(Number(value)); +const isCustomAnnotationShape = ( + value: string +): value is 'circle' | 'hexagon' | 'triangle' | 'square' => + ['circle', 'hexagon', 'triangle', 'square'].includes(value); function NumberIcon({ number }: { number: number }) { return ( @@ -212,6 +200,14 @@ interface MarkerConfig { textVisibility?: boolean; } +export const AnnotationIcon = ({ type, ...rest }: { type: string } & EuiIconProps) => { + if (isNumericalString(type)) { + return ; + } + const iconType = isCustomAnnotationShape(type) ? shapesIconMap[type] : type; + return ; +}; + export function Marker({ config, isHorizontal, @@ -223,13 +219,11 @@ export function Marker({ hasReducedPadding: boolean; label?: string; }) { - console.log('hello', config); - if (isNumericalString(config.icon)) { - return ; - } - if (hasIcon(config.icon)) { - return ; + const { icon } = config; + if (hasIcon(icon)) { + return ; } + // if there's some text, check whether to show it as marker, or just show some padding for the icon if (config.textVisibility) { if (hasReducedPadding) { diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx index fec1d8b37a0e4..d813730a74df0 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx @@ -7,13 +7,15 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiComboBox, EuiIcon } from '@elastic/eui'; +import { EuiComboBox, EuiIcon, IconType } from '@elastic/eui'; export function hasIcon(icon: string | undefined): icon is string { return icon != null && icon !== 'empty'; } -const icons = [ +export type IconSet = Array<{ value: string; label: string; icon?: IconType }>; + +export const euiIconsSet = [ { value: 'empty', label: i18n.translate('xpack.lens.xyChart.iconSelect.noIconLabel', { @@ -70,11 +72,11 @@ const icons = [ }, ]; -const IconView = (props: { value?: string; label: string }) => { +const IconView = (props: { value?: string; label: string; icon?: IconType }) => { if (!props.value) return null; return ( - + {` ${props.label}`} ); @@ -83,16 +85,20 @@ const IconView = (props: { value?: string; label: string }) => { export const IconSelect = ({ value, onChange, + customIconSet = euiIconsSet, }: { value?: string; onChange: (newIcon: string) => void; + customIconSet?: IconSet; }) => { - const selectedIcon = icons.find((option) => value === option.value) || icons[0]; + const selectedIcon = + customIconSet.find((option) => value === option.value) || + customIconSet.find((option) => option.value === 'empty')!; return ( { onChange(selection[0].value!); @@ -100,7 +106,11 @@ export const IconSelect = ({ singleSelection={{ asPlainText: true }} renderOption={IconView} compressed - prepend={hasIcon(selectedIcon.value) ? : undefined} + prepend={ + hasIcon(selectedIcon.value) ? ( + + ) : undefined + } /> ); }; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx index b5400feb5d3b6..2f91cb5c1f516 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx @@ -11,7 +11,7 @@ import { EuiButtonGroup, EuiFormRow } from '@elastic/eui'; import { IconPosition, YAxisMode } from '../../../../common/expressions/xy_chart'; import { TooltipWrapper } from '../../../shared_components'; -import { hasIcon, IconSelect } from './icon_select'; +import { hasIcon, IconSelect, IconSet } from './icon_select'; import { idPrefix } from '../dimension_editor'; interface LabelConfigurationOptions { @@ -81,10 +81,12 @@ export const MarkerDecorationSettings = ({ currentConfig, setConfig, isHorizontal, + customIconSet, }: { currentConfig?: MarkerDecorationConfig; setConfig: (config: MarkerDecorationConfig) => void; isHorizontal: boolean; + customIconSet?: IconSet; }) => { return ( <> @@ -133,6 +135,7 @@ export const MarkerDecorationSettings = ({ })} > { setConfig({ icon: newIcon }); From f83332c5c07862776ce7691b7c55d30f9ddf5692 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 16 Mar 2022 13:24:51 +0100 Subject: [PATCH 14/47] refactor icons --- .../public/assets/annotation_icons/circle.tsx | 9 +- .../assets/annotation_icons/hexagon.tsx | 9 +- .../public/assets/annotation_icons/index.tsx | 8 +- .../public/assets/annotation_icons/square.tsx | 10 +- .../assets/annotation_icons/triangle.tsx | 9 +- .../annotations/config_panel/icon_set.ts | 46 +++++ .../index.tsx} | 165 +++++++++--------- .../annotations/expression.tsx | 1 - .../xy_visualization/annotations/helpers.tsx | 3 +- .../xy_visualization/annotations_helpers.tsx | 14 +- 10 files changed, 147 insertions(+), 127 deletions(-) create mode 100644 x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts rename x-pack/plugins/lens/public/xy_visualization/annotations/{config_panel.tsx => config_panel/index.tsx} (51%) diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx index 76b753e9af990..326c51ff0890f 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx @@ -6,12 +6,9 @@ */ import * as React from 'react'; -interface SVGRProps { - title?: string; - titleId?: string; -} +import { EuiIconProps } from '@elastic/eui'; -const IconCircle = ({ title, titleId, ...props }: React.SVGProps & SVGRProps) => ( +export const IconCircle = ({ title, titleId, ...props }: Omit) => ( /> ); - -export const Circle = IconCircle; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx index ced02345fbc9f..6ff52856cf558 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx @@ -6,12 +6,9 @@ */ import * as React from 'react'; -interface SVGRProps { - title?: string; - titleId?: string; -} +import { EuiIconProps } from '@elastic/eui'; -const IconHexagon = ({ title, titleId, ...props }: React.SVGProps & SVGRProps) => ( +export const IconHexagon = ({ title, titleId, ...props }: Omit) => ( /> ); - -export const Hexagon = IconHexagon; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx index 9a85f56b19f8c..05f9514312d46 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -export { Circle } from './circle'; -export { Hexagon } from './hexagon'; -export { Square } from './square'; -export { Triangle } from './triangle'; +export { IconCircle } from './circle'; +export { IconHexagon } from './hexagon'; +export { IconSquare } from './square'; +export { IconTriangle } from './triangle'; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx index c5880e459f182..1f5d7697cb32c 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx @@ -6,12 +6,8 @@ */ import * as React from 'react'; -interface SVGRProps { - title?: string; - titleId?: string; -} - -const IconSquare = ({ title, titleId, ...props }: React.SVGProps & SVGRProps) => ( +import { EuiIconProps } from '@elastic/eui'; +export const IconSquare = ({ title, titleId, ...props }: Omit) => ( ); - -export const Square = IconSquare; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx index f567e05f0622f..af0b7a67c729f 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx @@ -6,12 +6,9 @@ */ import * as React from 'react'; -interface SVGRProps { - title?: string; - titleId?: string; -} +import { EuiIconProps } from '@elastic/eui'; -const IconTriangle = ({ title, titleId, ...props }: React.SVGProps & SVGRProps) => ( +export const IconTriangle = ({ title, titleId, ...props }: Omit) => ( ); - -export const Triangle = IconTriangle; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts new file mode 100644 index 0000000000000..40a07ab382d6f --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { i18n } from '@kbn/i18n'; +import { + IconSquare, + IconTriangle, + IconHexagon, + IconCircle, +} from '../../../assets/annotation_icons'; +import { euiIconsSet } from '../../xy_config_panel/shared/icon_select'; + +export const annotationsIconSet = [ + { + value: 'triangle', + label: i18n.translate('xpack.lens.xyChart.iconSelect.triangleIconLabel', { + defaultMessage: 'Triangle', + }), + icon: IconTriangle, + }, + { + value: 'square', + label: i18n.translate('xpack.lens.xyChart.iconSelect.squareIconLabel', { + defaultMessage: 'Square', + }), + icon: IconSquare, + }, + { + value: 'circle', + label: i18n.translate('xpack.lens.xyChart.iconSelect.circleIconLabel', { + defaultMessage: 'Circle', + }), + icon: IconCircle, + }, + { + value: 'hexagon', + label: i18n.translate('xpack.lens.xyChart.iconSelect.hexagonIconLabel', { + defaultMessage: 'Hexagon', + }), + icon: IconHexagon, + }, + ...euiIconsSet, +]; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx similarity index 51% rename from x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx rename to x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx index 8faade4ff70d9..ca3ee0bbc9c3b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx @@ -7,22 +7,21 @@ import React, { useCallback } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiDatePicker, EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { EuiDatePicker, EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import moment from 'moment'; import { AnnotationConfig } from 'src/plugins/event_annotation/common/types'; -import type { VisualizationDimensionEditorProps } from '../../types'; -import { State, XYState } from '../types'; -import { FormatFactory } from '../../../common'; -import { XYAnnotationLayerConfig } from '../../../common/expressions'; -import { ColorPicker } from '../xy_config_panel/color_picker'; -import { NameInput, useDebouncedValue } from '../../shared_components'; -import { isHorizontalChart } from '../state_helpers'; -import { MarkerDecorationSettings } from '../xy_config_panel/shared/marker_decoration_settings'; -import { LineStyleSettings } from '../xy_config_panel/shared/line_style_settings'; -import { updateLayer } from '../xy_config_panel'; -import { Square, Triangle, Hexagon, Circle } from '../../assets/annotation_icons'; -import { euiIconsSet } from '../xy_config_panel/shared/icon_select'; +import type { VisualizationDimensionEditorProps } from '../../../types'; +import { State, XYState } from '../../types'; +import { FormatFactory } from '../../../../common'; +import { XYAnnotationLayerConfig } from '../../../../common/expressions'; +import { ColorPicker } from '../../xy_config_panel/color_picker'; +import { NameInput, useDebouncedValue } from '../../../shared_components'; +import { isHorizontalChart } from '../../state_helpers'; +import { MarkerDecorationSettings } from '../../xy_config_panel/shared/marker_decoration_settings'; +import { LineStyleSettings } from '../../xy_config_panel/shared/line_style_settings'; +import { updateLayer } from '../../xy_config_panel'; +import { annotationsIconSet } from './icon_set'; export const defaultAnnotationLabel = i18n.translate('xpack.lens.xyChart.defaultAnnotationLabel', { defaultMessage: 'Static Annotation', @@ -59,40 +58,33 @@ export const AnnotationsPanel = ( if (existingIndex !== -1) { newConfigs[existingIndex] = { ...newConfigs[existingIndex], ...config }; } else { - // that should never happen - return; + return; // that should never happen because annotations are created before config panel is opened } setLocalState(updateLayer(localState, { ...localLayer, config: newConfigs }, index)); }, [accessor, index, localState, localLayer, setLocalState] ); + return ( <> - { + if (date) { + setConfig({ + key: { + ...(currentConfig?.key || { keyType: 'point_in_time' }), + type: 'annotation_key', + timestamp: date?.valueOf(), + }, + }); + } + }} label={i18n.translate('xpack.lens.xyChart.annotationDate', { defaultMessage: 'Annotation date', })} - > - { - if (date) { - setConfig({ - key: { - ...(currentConfig?.key || { keyType: 'point_in_time' }), - type: 'annotation_key', - timestamp: date?.valueOf(), - }, - }); - } - }} - dateFormat="MMM D, YYYY @ HH:mm:ss.SSS" - data-test-subj="lnsXY_axisOrientation_groups" - /> - + /> + ; - setConfig({ isHidden: ev.target.checked })} + /> + + ); +}; + +const ConfigPanelDatePicker = ({ + value, + label, + onChange, +}: { + value: moment.Moment; + label: string; + onChange: (val: moment.Moment | null) => void; +}) => { + return ( + + + + ); +}; + +const ConfigPanelHideSwitch = ({ + value, + onChange, +}: { + value: boolean; + onChange: (event: EuiSwitchEvent) => void; +}) => { + return ( + + - setConfig({ isHidden: ev.target.checked })} - /> - - + showLabel={false} + data-test-subj="lns-annotations-hide-annotation" + checked={value} + onChange={onChange} + /> + ); }; - -const annotationsIconSet = [ - { - value: 'triangle', - label: i18n.translate('xpack.lens.xyChart.iconSelect.triangleIconLabel', { - defaultMessage: 'Triangle', - }), - icon: Triangle, - }, - { - value: 'square', - label: i18n.translate('xpack.lens.xyChart.iconSelect.squareIconLabel', { - defaultMessage: 'Square', - }), - icon: Square, - }, - { - value: 'circle', - label: i18n.translate('xpack.lens.xyChart.iconSelect.circleIconLabel', { - defaultMessage: 'Circle', - }), - icon: Circle, - }, - { - value: 'hexagon', - label: i18n.translate('xpack.lens.xyChart.iconSelect.hexagonIconLabel', { - defaultMessage: 'Hexagon', - }), - icon: Hexagon, - }, - ...euiIconsSet, -]; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index 9e0ba852e5faf..6369aba7b9b55 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -7,7 +7,6 @@ import './expression.scss'; import React from 'react'; -import { EuiIcon } from '@elastic/eui'; import { AnnotationDomainType, AnnotationTooltipFormatter, diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index a0439717146d2..c1dd109b281ea 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -142,7 +142,7 @@ export const getAnnotationsAccessorColorConfig = (layer: XYAnnotationLayerConfig return layer.config.map((config) => { return { columnId: config.id, - triggerIcon: 'color' as const, + triggerIcon: config.isHidden ? ('invisible' as const) : ('color' as const), color: config?.color || defaultAnnotationColor, }; }); @@ -164,7 +164,6 @@ export const getAnnotationsConfiguration = ({ dataLayer.xAccessor && checkScaleOperation('interval', 'date', frame?.datasourceLayers || {})(dataLayer) ); - return { noDatasource: true, groups: [ diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx index c1800a3869992..bc086034344c6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx @@ -7,11 +7,11 @@ import './expression_reference_lines.scss'; import React from 'react'; -import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText, IconType } from '@elastic/eui'; +import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText } from '@elastic/eui'; import { Position } from '@elastic/charts'; import type { IconPosition, YAxisMode, YConfig } from '../../common/expressions'; import { hasIcon } from './xy_config_panel/shared/icon_select'; -import { Circle, Hexagon, Square, Triangle } from '../assets/annotation_icons'; +import { IconCircle, IconHexagon, IconSquare, IconTriangle } from '../assets/annotation_icons'; export const LINES_MARKER_SIZE = 20; @@ -168,11 +168,11 @@ export function MarkerBody({ const isNumericalString = (value: string) => !isNaN(Number(value)); const shapesIconMap = { - circle: Circle, - hexagon: Hexagon, - triangle: Triangle, - square: Square, -} as const; + circle: IconCircle, + hexagon: IconHexagon, + triangle: IconTriangle, + square: IconSquare, +}; const isCustomAnnotationShape = ( value: string From 465a8d17104d29636e410433269073a29ee2c654 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 16 Mar 2022 18:34:24 +0100 Subject: [PATCH 15/47] uniqueLabels --- .../annotations/config_panel/index.tsx | 3 +- .../annotations/expression.tsx | 15 +++++-- .../xy_visualization/annotations/helpers.tsx | 43 +++++++++++++++++-- .../public/xy_visualization/visualization.tsx | 4 +- 4 files changed, 54 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx index ca3ee0bbc9c3b..2cde793dd85b2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx @@ -24,7 +24,7 @@ import { updateLayer } from '../../xy_config_panel'; import { annotationsIconSet } from './icon_set'; export const defaultAnnotationLabel = i18n.translate('xpack.lens.xyChart.defaultAnnotationLabel', { - defaultMessage: 'Static Annotation', + defaultMessage: 'Event', }); export const AnnotationsPanel = ( @@ -84,7 +84,6 @@ export const AnnotationsPanel = ( defaultMessage: 'Annotation date', })} /> - ; , minInterval?: number, firstTimestamp?: number, - isBarChart?: boolean + isBarChart?: boolean, ) => { return layers .flatMap(({ config: configs }) => configs.filter((config) => !config.isHidden)) .reduce>((acc, current) => { + const label = uniqueLabels[current.id] const roundedTimestamp = getRoundedTimestamp( Number(current.key.timestamp), firstTimestamp, minInterval, isBarChart ); + const currentWithUniqueLabel = { + ...current, + label + } return { ...acc, - [roundedTimestamp]: acc[roundedTimestamp] ? [...acc[roundedTimestamp], current] : [current], + [roundedTimestamp]: acc[roundedTimestamp] ? [...acc[roundedTimestamp], currentWithUniqueLabel] : [currentWithUniqueLabel], }; }, {}); }; @@ -129,11 +136,13 @@ export const getCollectiveConfigsByInterval = ( isBarChart?: boolean, formatter?: FieldFormat ) => { + const uniqueLabels = getUniqueLabels(layers) const visibleGroupedConfigs = groupVisibleConfigsByInterval( layers, + uniqueLabels, minInterval, firstTimestamp, - isBarChart + isBarChart, ); let collectiveConfig: CollectiveConfig; return Object.entries(visibleGroupedConfigs).map(([roundedTimestamp, configArr]) => { diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index c1dd109b281ea..35e635479699b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -7,12 +7,17 @@ import { i18n } from '@kbn/i18n'; import { layerTypes } from '../../../common'; -import type { XYDataLayerConfig, XYAnnotationLayerConfig } from '../../../common/expressions'; +import type { + XYDataLayerConfig, + XYAnnotationLayerConfig, + XYLayerConfig, +} from '../../../common/expressions'; import type { FramePublicAPI, Visualization } from '../../types'; import { isHorizontalChart } from '../state_helpers'; import type { XYState } from '../types'; import { checkScaleOperation, + getAnnotationsLayer, getAxisName, getDataLayers, isAnnotationsLayer, @@ -20,6 +25,7 @@ import { import { LensIconChartBarAnnotations } from '../../assets/chart_bar_annotations'; import { generateId } from '../../id_generator'; import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; +import { defaultAnnotationLabel } from './config_panel'; const MAX_DATE = Number(new Date(8640000000000000)); const MIN_DATE = Number(new Date(-8640000000000000)); @@ -116,9 +122,7 @@ export const setAnnotationsDimension: Visualization['setDimension'] = ( newLayer.config = [ ...(newLayer.config || []), { - label: i18n.translate('xpack.lens.xyChart.defaultAnnotationLabel', { - defaultMessage: 'Static Annotation', - }), + label: defaultAnnotationLabel, annotationType: 'manual', axisMode: 'bottom', key: { @@ -186,3 +190,34 @@ export const getAnnotationsConfiguration = ({ ], }; }; + +export const getUniqueLabels = (layers: XYLayerConfig[]) => { + const annotationLayers = getAnnotationsLayer(layers); + const columnLabelMap = {} as Record; + const counts = {} as Record; + + const makeUnique = (label: string) => { + let uniqueLabel = label; + + while (counts[uniqueLabel] >= 0) { + const num = ++counts[uniqueLabel]; + uniqueLabel = i18n.translate('xpack.lens.indexPattern.uniqueLabel', { + defaultMessage: '{label} [{num}]', + values: { label, num }, + }); + } + + counts[uniqueLabel] = 0; + return uniqueLabel; + }; + + annotationLayers.forEach((layer) => { + if (!layer.config) { + return; + } + layer.config.forEach((l) => { + columnLabelMap[l.id] = makeUnique(l.label); + }); + }); + return columnLabelMap; +}; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 6aa98eacf26c9..931a3b54282ca 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -39,6 +39,7 @@ import { getAnnotationsConfiguration, getAnnotationsSupportedLayer, setAnnotationsDimension, + getUniqueLabels, } from './annotations/helpers'; import { checkXAccessorCompatibility, @@ -627,14 +628,13 @@ export const getXyVisualization = ({ }) { const layer = state.layers.find((l) => l.layerId === layerId); if (layer && isAnnotationsLayer(layer)) { - const config = layer?.config.find((l) => l.id === columnId); return ( ); } From b82c9f4cf0cecc1d3e2a07d1584d227bfcfa175a Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Thu, 17 Mar 2022 09:01:39 +0100 Subject: [PATCH 16/47] unique Labels --- .../annotations/expression.tsx | 11 +-- .../xy_visualization/annotations/helpers.tsx | 6 +- .../public/xy_visualization/expression.tsx | 4 +- .../public/xy_visualization/to_expression.ts | 67 ++++++++++++------- .../visualization_helpers.tsx | 2 +- 5 files changed, 49 insertions(+), 41 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index e511f7b3d6c53..ee3d2aca68238 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -26,7 +26,6 @@ import { Marker, AnnotationIcon, } from '../annotations_helpers'; -import { getUniqueLabels } from './helpers'; const getRoundedTimestamp = ( timestamp: number, @@ -56,7 +55,6 @@ interface CollectiveConfig extends AnnotationConfig { const groupVisibleConfigsByInterval = ( layers: XYAnnotationLayerConfig[], - uniqueLabels: Record, minInterval?: number, firstTimestamp?: number, isBarChart?: boolean, @@ -64,20 +62,15 @@ const groupVisibleConfigsByInterval = ( return layers .flatMap(({ config: configs }) => configs.filter((config) => !config.isHidden)) .reduce>((acc, current) => { - const label = uniqueLabels[current.id] const roundedTimestamp = getRoundedTimestamp( Number(current.key.timestamp), firstTimestamp, minInterval, isBarChart ); - const currentWithUniqueLabel = { - ...current, - label - } return { ...acc, - [roundedTimestamp]: acc[roundedTimestamp] ? [...acc[roundedTimestamp], currentWithUniqueLabel] : [currentWithUniqueLabel], + [roundedTimestamp]: acc[roundedTimestamp] ? [...acc[roundedTimestamp], current] : [current], }; }, {}); }; @@ -136,10 +129,8 @@ export const getCollectiveConfigsByInterval = ( isBarChart?: boolean, formatter?: FieldFormat ) => { - const uniqueLabels = getUniqueLabels(layers) const visibleGroupedConfigs = groupVisibleConfigsByInterval( layers, - uniqueLabels, minInterval, firstTimestamp, isBarChart, diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index 35e635479699b..77f8a461add5a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -17,7 +17,7 @@ import { isHorizontalChart } from '../state_helpers'; import type { XYState } from '../types'; import { checkScaleOperation, - getAnnotationsLayer, + getAnnotationsLayers, getAxisName, getDataLayers, isAnnotationsLayer, @@ -192,7 +192,7 @@ export const getAnnotationsConfiguration = ({ }; export const getUniqueLabels = (layers: XYLayerConfig[]) => { - const annotationLayers = getAnnotationsLayer(layers); + const annotationLayers = getAnnotationsLayers(layers); const columnLabelMap = {} as Record; const counts = {} as Record; @@ -201,7 +201,7 @@ export const getUniqueLabels = (layers: XYLayerConfig[]) => { while (counts[uniqueLabel] >= 0) { const num = ++counts[uniqueLabel]; - uniqueLabel = i18n.translate('xpack.lens.indexPattern.uniqueLabel', { + uniqueLabel = i18n.translate('xpack.lens.uniqueLabel', { defaultMessage: '{label} [{num}]', values: { label, num }, }); diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 2c108717ef8d7..6e5d21007176f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -78,7 +78,7 @@ import { computeChartMargins, getLinesCausedPaddings } from './annotations_helpe import { Annotations, getCollectiveConfigsByInterval } from './annotations/expression'; import { computeOverallDataDomain } from './reference_line_helpers'; -import { getReferenceLayers, isDataLayer, getAnnotationsLayer } from './visualization_helpers'; +import { getReferenceLayers, isDataLayer, getAnnotationsLayers } from './visualization_helpers'; declare global { interface Window { @@ -354,7 +354,7 @@ export function XYChart({ }; const referenceLineLayers = getReferenceLayers(layers); - const annotationsLayers = getAnnotationsLayer(layers); + const annotationsLayers = getAnnotationsLayers(layers); const firstTable = data.tables[filteredLayers[0].layerId]; diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index dfe597d84bf6a..9a3deba12c571 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -23,8 +23,14 @@ import { layerTypes } from '../../common'; import { hasIcon } from './xy_config_panel/shared/icon_select'; import { defaultReferenceLineColor } from './color_assignment'; import { getDefaultVisualValuesForLayer } from '../shared_components/datasource_default_values'; -import { isDataLayer, isAnnotationsLayer, getLayerTypeOptions } from './visualization_helpers'; +import { + getLayerTypeOptions, + getDataLayers, + getReferenceLayers, + getAnnotationsLayers, +} from './visualization_helpers'; import { defaultAnnotationLabel } from './annotations/config_panel'; +import { getUniqueLabels } from './annotations/helpers'; export const getSortedAccessors = ( datasource: DatasourcePublicAPI, @@ -155,23 +161,32 @@ export const buildExpression = ( attributes: Partial<{ title: string; description: string }> = {}, eventAnnotationService: EventAnnotationServiceType ): Ast | null => { - const validLayers = state.layers - .filter((layer): layer is ValidLayer => - isAnnotationsLayer(layer) ? Boolean(layer.config.length) : Boolean(layer.accessors.length) - ) - .map((layer) => { - if (!datasourceLayers?.[layer.layerId]) { - return layer; - } - const sortedAccessors = getSortedAccessors(datasourceLayers[layer.layerId], layer); + const validDataLayers = getDataLayers(state.layers) + .filter((layer): layer is ValidLayer => Boolean(layer.accessors.length)) + .map((layer) => ({ + ...layer, + accessors: getSortedAccessors(datasourceLayers[layer.layerId], layer), + })); + + // sorting doesn't change anything so we don't sort reference layers (TODO: should we make it work?) + const validReferenceLayers = getReferenceLayers(state.layers).filter((layer) => + Boolean(layer.accessors.length) + ); + const uniqueLabels = getUniqueLabels(state.layers); + const validAnnotationsLayers = getAnnotationsLayers(state.layers) + .filter((layer) => Boolean(layer.config.length)) + .map((layer) => { return { ...layer, - accessors: sortedAccessors, + config: layer.config.map((c) => ({ + ...c, + label: uniqueLabels[c.id], + })), }; }); - if (!validLayers.length) { + if (!validDataLayers.length) { return null; } @@ -337,23 +352,25 @@ export const buildExpression = ( valueLabels: [state?.valueLabels || 'hide'], hideEndzones: [state?.hideEndzones || false], valuesInLegend: [state?.valuesInLegend || false], - layers: validLayers.map((layer) => { - if (isDataLayer(layer)) { - return dataLayerToExpression( + layers: [ + ...validDataLayers.map((layer) => + dataLayerToExpression( layer, datasourceLayers[layer.layerId], metadata, paletteService - ); - } - if (isAnnotationsLayer(layer)) { - return annotationLayerToExpression(layer, eventAnnotationService); - } - return referenceLineLayerToExpression( - layer, - datasourceLayers[(layer as XYReferenceLineLayerConfig).layerId] - ); - }), + ) + ), + ...validReferenceLayers.map((layer) => + referenceLineLayerToExpression( + layer, + datasourceLayers[(layer as XYReferenceLineLayerConfig).layerId] + ) + ), + ...validAnnotationsLayers.map((layer) => + annotationLayerToExpression(layer, eventAnnotationService) + ), + ], }, }, ], diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx index 94d00045ba60b..c080686cc7477 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx @@ -148,7 +148,7 @@ export const isAnnotationsLayer = ( layer: Pick ): layer is XYAnnotationLayerConfig => layer.layerType === layerTypes.ANNOTATIONS; -export const getAnnotationsLayer = (layers: XYLayerConfig[]) => +export const getAnnotationsLayers = (layers: XYLayerConfig[]) => (layers || []).filter((layer): layer is XYAnnotationLayerConfig => isAnnotationsLayer(layer)); export interface LayerTypeToLayer { From e9a8b75fea05e1cfcb18a7a34f7f5be0065c869d Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Thu, 17 Mar 2022 09:47:40 +0100 Subject: [PATCH 17/47] diff config from args --- .../common/annotation_expression.ts | 83 ++++------------ src/plugins/event_annotation/common/index.ts | 4 +- src/plugins/event_annotation/common/types.ts | 25 +++-- .../public/event_annotation_service/index.tsx | 10 +- .../event_annotation_service/service.tsx | 26 +---- .../public/event_annotation_service/types.ts | 4 +- src/plugins/event_annotation/public/mocks.ts | 4 +- src/plugins/event_annotation/public/plugin.ts | 11 +-- src/plugins/event_annotation/server/plugin.ts | 5 +- .../layer_config/annotation_layer_config.ts | 25 +++-- .../common/expressions/xy_chart/xy_args.ts | 5 +- .../annotations/config_panel/index.tsx | 12 ++- .../annotations/expression.tsx | 35 ++++--- .../xy_visualization/annotations/helpers.tsx | 5 +- .../xy_visualization/color_assignment.ts | 4 +- .../xy_visualization/expression.test.tsx | 97 +++++++++++++------ .../public/xy_visualization/expression.tsx | 48 +++++---- .../public/xy_visualization/to_expression.ts | 5 +- .../visualization_helpers.tsx | 13 ++- .../shared/marker_decoration_settings.tsx | 25 ++--- 20 files changed, 221 insertions(+), 225 deletions(-) diff --git a/src/plugins/event_annotation/common/annotation_expression.ts b/src/plugins/event_annotation/common/annotation_expression.ts index a46d4b5099729..2d1eadb459bb6 100644 --- a/src/plugins/event_annotation/common/annotation_expression.ts +++ b/src/plugins/event_annotation/common/annotation_expression.ts @@ -7,44 +7,32 @@ */ import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -import { AnnotationConfig, AnnotationResult, AnnotationKey, AnnotationKeyResult } from './types'; +import { EventAnnotationArgs, EventAnnotationOutput } from './types'; -export const annotationConfig: ExpressionFunctionDefinition< - 'annotation_config', +export const manualEventAnnotation: ExpressionFunctionDefinition< + 'manual_event_annotation', null, - AnnotationConfig, - AnnotationResult + EventAnnotationArgs, + EventAnnotationOutput > = { - name: 'annotation_config', + name: 'manual_event_annotation', aliases: [], - type: 'annotation_config', - help: `Configure annotation`, + type: 'manual_event_annotation', + help: `Configure manual annotation`, inputTypes: ['null'], args: { - annotationType: { - types: ['string'], - help: 'Annotation type manual or query based', - }, - key: { - types: ['annotation_key'], - help: 'Type specific config', + time: { + types: ['number'], + help: 'The name', }, - id: { + label: { types: ['string'], - help: 'The accessor this configuration is for', + help: 'The name', }, color: { types: ['string'], help: 'The color of the series', }, - label: { - types: ['string'], - help: 'The name', - }, - isHidden: { - types: ['boolean'], - help: 'is hidden?', - }, lineStyle: { types: ['string'], options: ['solid', 'dotted', 'dashed'], @@ -67,49 +55,14 @@ export const annotationConfig: ExpressionFunctionDefinition< types: ['boolean'], help: 'Visibility of the label on the annotation line', }, - message: { - types: ['string'], - help: 'The tooltip message', - }, - axisMode: { - types: ['string'], - options: ['bottom', 'auto', 'right', 'left'], - help: 'Axis mode', - }, - }, - fn: function fn(input: unknown, args: AnnotationConfig) { - return { - type: 'annotation_config', - ...args, - }; - }, -}; - -export const annotationKeyConfig: ExpressionFunctionDefinition< - 'annotation_key', - null, - AnnotationKey, - AnnotationKeyResult -> = { - name: 'annotation_key', - aliases: [], - type: 'annotation_key', - help: `Configure annotation`, - inputTypes: ['null'], - args: { - keyType: { - types: ['string'], - options: ['point_in_time'], - help: '', - }, - timestamp: { - types: ['number'], - help: '', + isHidden: { + types: ['boolean'], + help: 'is hidden?', }, }, - fn: function fn(input: unknown, args: AnnotationKey) { + fn: function fn(input: unknown, args: EventAnnotationArgs) { return { - type: 'annotation_key', + type: 'manual_event_annotation', ...args, }; }, diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts index 1087cb9f560d7..f0060d3169c1a 100644 --- a/src/plugins/event_annotation/common/index.ts +++ b/src/plugins/event_annotation/common/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { annotationConfig, annotationKeyConfig } from './annotation_expression'; -export type { AnnotationConfig } from './types'; +export { manualEventAnnotation } from './annotation_expression'; +export type { EventAnnotationConfig, EventAnnotationArgs } from './types'; diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts index d10aaffbc10d8..6a03de8d6d8e9 100644 --- a/src/plugins/event_annotation/common/types.ts +++ b/src/plugins/event_annotation/common/types.ts @@ -10,14 +10,9 @@ export type LineStyle = 'solid' | 'dashed' | 'dotted'; export type IconPosition = 'auto' | 'left' | 'right' | 'above' | 'below'; export type AnnotationType = 'manual'; export type KeyType = 'point_in_time'; -export type YAxisMode = 'auto' | 'bottom' | 'left' | 'right'; -export interface AnnotationConfig { - id: string; - key: AnnotationKeyResult; - annotationType: AnnotationType; +interface StyleProps { label: string; - message?: string; color?: string; icon?: string; lineWidth?: number; @@ -25,14 +20,18 @@ export interface AnnotationConfig { iconPosition?: IconPosition; textVisibility?: boolean; isHidden?: boolean; - axisMode: YAxisMode; } -export type AnnotationResult = AnnotationConfig & { type: 'annotation_config' }; +export type EventAnnotationConfig = { + id: string; + key: { + type: KeyType; + timestamp: number; + }; +} & StyleProps; -export interface AnnotationKey { - keyType: KeyType; - timestamp: number; -} +export type EventAnnotationArgs = { + time: number; +} & StyleProps; -export type AnnotationKeyResult = AnnotationKey & { type: 'annotation_key' }; +export type EventAnnotationOutput = EventAnnotationArgs & { type: 'manual_event_annotation' }; diff --git a/src/plugins/event_annotation/public/event_annotation_service/index.tsx b/src/plugins/event_annotation/public/event_annotation_service/index.tsx index 17bdb4e1c4bfd..bc102d8c33a93 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/index.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/index.tsx @@ -9,12 +9,12 @@ import { EventAnnotationServiceType } from './types'; export class EventAnnotationService { - private annotationService: EventAnnotationServiceType | undefined = undefined; + private eventAnnotationService: EventAnnotationServiceType | undefined = undefined; public async getService() { - if (!this.annotationService) { - const { getAnnotationService } = await import('./service'); - this.annotationService = getAnnotationService(); + if (!this.eventAnnotationService) { + const { getEventAnnotationService } = await import('./service'); + this.eventAnnotationService = getEventAnnotationService(); } - return this.annotationService; + return this.eventAnnotationService; } } diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index 46f9e4891dbf3..2d1aa201146bf 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -16,53 +16,35 @@ export function hasIcon(icon: string | undefined): icon is string { return icon != null && icon !== 'empty'; } -export function getAnnotationService(): EventAnnotationServiceType { +export function getEventAnnotationService(): EventAnnotationServiceType { return { toExpression: ({ label, isHidden, - id, color, lineStyle, lineWidth, icon, iconPosition, textVisibility, - key, + time, }) => { return { type: 'expression', chain: [ { type: 'function', - function: 'annotation_config', + function: 'manual_event_annotation', arguments: { - annotationType: ['manual'], - key: [ - { - type: 'expression', - chain: [ - { - type: 'function', - function: 'annotation_key', - arguments: { - keyType: ['point_in_time'], - timestamp: [key.timestamp], - }, - }, - ], - }, - ], + time: [time], label: [label], color: [color || defaultAnnotationColor], lineWidth: [lineWidth || 1], lineStyle: [lineStyle || 'solid'], - id: [id], icon: hasIcon(icon) ? [icon] : ['empty'], iconPosition: hasIcon(icon) || textVisibility ? [iconPosition || 'auto'] : ['auto'], textVisibility: [textVisibility || false], isHidden: [Boolean(isHidden)], - axisMode: ['bottom'], }, }, ], diff --git a/src/plugins/event_annotation/public/event_annotation_service/types.ts b/src/plugins/event_annotation/public/event_annotation_service/types.ts index 078073c2538fb..bb0b6eb4cc200 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/types.ts +++ b/src/plugins/event_annotation/public/event_annotation_service/types.ts @@ -7,8 +7,8 @@ */ import { ExpressionAstExpression } from '../../../expressions/common/ast'; -import { AnnotationConfig } from '../../common'; +import { EventAnnotationArgs } from '../../common'; export interface EventAnnotationServiceType { - toExpression: (props: AnnotationConfig) => ExpressionAstExpression; + toExpression: (props: EventAnnotationArgs) => ExpressionAstExpression; } diff --git a/src/plugins/event_annotation/public/mocks.ts b/src/plugins/event_annotation/public/mocks.ts index c4942e98e5e9f..e78d4e8f75de7 100644 --- a/src/plugins/event_annotation/public/mocks.ts +++ b/src/plugins/event_annotation/public/mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getAnnotationService } from './event_annotation_service/service'; +import { getEventAnnotationService } from './event_annotation_service/service'; // not really mocking but avoiding async loading -export const eventAnnotationServiceMock = getAnnotationService(); +export const eventAnnotationServiceMock = getEventAnnotationService(); diff --git a/src/plugins/event_annotation/public/plugin.ts b/src/plugins/event_annotation/public/plugin.ts index 5589b343aacc2..47a7ff15360d3 100644 --- a/src/plugins/event_annotation/public/plugin.ts +++ b/src/plugins/event_annotation/public/plugin.ts @@ -8,7 +8,7 @@ import { Plugin, CoreSetup } from 'kibana/public'; import { ExpressionsSetup } from '../../expressions/public'; -import { annotationConfig, annotationKeyConfig } from '../common'; +import { manualEventAnnotation } from '../common'; import { EventAnnotationService } from './event_annotation_service'; interface SetupDependencies { @@ -25,15 +25,14 @@ export type EventAnnotationPluginStart = EventAnnotationService; export class EventAnnotationPlugin implements Plugin { - private readonly annotationService = new EventAnnotationService(); + private readonly eventAnnotationService = new EventAnnotationService(); public setup(core: CoreSetup, dependencies: SetupDependencies): EventAnnotationPluginSetup { - dependencies.expressions.registerFunction(annotationConfig); - dependencies.expressions.registerFunction(annotationKeyConfig); - return this.annotationService; + dependencies.expressions.registerFunction(manualEventAnnotation); + return this.eventAnnotationService; } public start(): EventAnnotationPluginStart { - return this.annotationService!; + return this.eventAnnotationService!; } } diff --git a/src/plugins/event_annotation/server/plugin.ts b/src/plugins/event_annotation/server/plugin.ts index e82eff90c1309..9af8dc228e39f 100644 --- a/src/plugins/event_annotation/server/plugin.ts +++ b/src/plugins/event_annotation/server/plugin.ts @@ -7,7 +7,7 @@ */ import { CoreSetup, Plugin } from 'kibana/server'; -import { annotationKeyConfig, annotationConfig } from '../common'; +import { manualEventAnnotation } from '../common'; import { ExpressionsServerSetup } from '../../expressions/server'; interface SetupDependencies { @@ -16,8 +16,7 @@ interface SetupDependencies { export class EventAnnotationServerPlugin implements Plugin { public setup(core: CoreSetup, dependencies: SetupDependencies) { - dependencies.expressions.registerFunction(annotationConfig); - dependencies.expressions.registerFunction(annotationKeyConfig); + dependencies.expressions.registerFunction(manualEventAnnotation); return {}; } diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts index 45061fb40e16e..15b6fc69d8834 100644 --- a/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts +++ b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts @@ -5,30 +5,39 @@ * 2.0. */ -import { AnnotationConfig } from '../../../../../../../src/plugins/event_annotation/common'; +import { + EventAnnotationArgs, + EventAnnotationConfig, +} from '../../../../../../../src/plugins/event_annotation/common'; import type { ExpressionFunctionDefinition } from '../../../../../../../src/plugins/expressions/common'; import { layerTypes } from '../../../constants'; export interface XYAnnotationLayerConfig { layerId: string; layerType: typeof layerTypes.ANNOTATIONS; - config: AnnotationConfig[]; + config: EventAnnotationConfig[]; hide?: boolean; } -export interface XYAnnotationLayerConfigResult { +export interface AnnotationLayerArgs { + layerId: string; + layerType: typeof layerTypes.ANNOTATIONS; + config: EventAnnotationArgs[]; + hide?: boolean; +} +export interface XYAnnotationLayerArgsResult { layerId: string; layerType: typeof layerTypes.ANNOTATIONS; type: 'lens_xy_annotation_layer'; - config: AnnotationConfig[]; + config: EventAnnotationArgs[]; hide?: boolean; } export const annotationLayerConfig: ExpressionFunctionDefinition< 'lens_xy_annotation_layer', null, - XYAnnotationLayerConfig, - XYAnnotationLayerConfigResult + AnnotationLayerArgs, + XYAnnotationLayerArgsResult > = { name: 'lens_xy_annotation_layer', aliases: [], @@ -48,12 +57,12 @@ export const annotationLayerConfig: ExpressionFunctionDefinition< }, config: { // eslint-disable-next-line @typescript-eslint/no-explicit-any - types: ['annotation_config' as any], + types: ['manual_event_annotation' as any], help: 'Additional configuration for y axes', multi: true, }, }, - fn: function fn(input: unknown, args: XYAnnotationLayerConfig) { + fn: function fn(input: unknown, args: AnnotationLayerArgs) { return { type: 'lens_xy_annotation_layer', ...args, diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/xy_args.ts b/x-pack/plugins/lens/common/expressions/xy_chart/xy_args.ts index 1334c1149f47b..b2e2b4f0744d5 100644 --- a/x-pack/plugins/lens/common/expressions/xy_chart/xy_args.ts +++ b/x-pack/plugins/lens/common/expressions/xy_chart/xy_args.ts @@ -8,13 +8,14 @@ import type { AxisExtentConfigResult, AxisTitlesVisibilityConfigResult } from './axis_config'; import type { FittingFunction } from './fitting_function'; import type { GridlinesConfigResult } from './grid_lines_config'; -import type { DataLayerArgs } from './layer_config'; +import type { AnnotationLayerArgs, DataLayerArgs } from './layer_config'; import type { LegendConfigResult } from './legend_config'; import type { TickLabelsConfigResult } from './tick_labels_config'; import type { LabelsOrientationConfigResult } from './labels_orientation_config'; import type { ValueLabelConfig } from '../../types'; export type XYCurveType = 'LINEAR' | 'CURVE_MONOTONE_X'; +export type XYLayerArgs = DataLayerArgs | AnnotationLayerArgs; // Arguments to XY chart expression, with computed properties export interface XYArgs { @@ -27,7 +28,7 @@ export interface XYArgs { yRightExtent: AxisExtentConfigResult; legend: LegendConfigResult; valueLabels: ValueLabelConfig; - layers: DataLayerArgs[]; + layers: XYLayerArgs[]; fittingFunction?: FittingFunction; axisTitlesVisibilitySettings?: AxisTitlesVisibilityConfigResult; tickLabelsVisibilitySettings?: TickLabelsConfigResult; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx index 2cde793dd85b2..0caddfc48f9ec 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiDatePicker, EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import moment from 'moment'; -import { AnnotationConfig } from 'src/plugins/event_annotation/common/types'; +import { EventAnnotationConfig } from 'src/plugins/event_annotation/common/types'; import type { VisualizationDimensionEditorProps } from '../../../types'; import { State, XYState } from '../../types'; import { FormatFactory } from '../../../../common'; @@ -49,7 +49,7 @@ export const AnnotationsPanel = ( const currentConfig = localLayer.config?.find((c) => c.id === accessor); const setConfig = useCallback( - (config: Partial | undefined) => { + (config: Partial | undefined) => { if (config == null) { return; } @@ -73,8 +73,7 @@ export const AnnotationsPanel = ( if (date) { setConfig({ key: { - ...(currentConfig?.key || { keyType: 'point_in_time' }), - type: 'annotation_key', + ...(currentConfig?.key || { type: 'point_in_time' }), timestamp: date?.valueOf(), }, }); @@ -94,7 +93,10 @@ export const AnnotationsPanel = ( { return layers .flatMap(({ config: configs }) => configs.filter((config) => !config.isHidden)) - .reduce>((acc, current) => { + .reduce>((acc, current) => { const roundedTimestamp = getRoundedTimestamp( - Number(current.key.timestamp), + Number(current.time), firstTimestamp, minInterval, isBarChart @@ -76,18 +78,18 @@ const groupVisibleConfigsByInterval = ( }; const createCustomTooltipDetails = - (config: AnnotationConfig[], formatter?: FieldFormat): AnnotationTooltipFormatter | undefined => + (config: EventAnnotationArgs[], formatter?: FieldFormat): AnnotationTooltipFormatter | undefined => () => { return (
- {config.map(({ icon, label, key, color }) => ( + {config.map(({ icon, label, time, color }) => (
{hasIcon(icon) ? : null} {label} - {formatter?.convert(key.timestamp) || String(key.timestamp)} + {formatter?.convert(time) || String(time)}
))} @@ -95,8 +97,8 @@ const createCustomTooltipDetails = ); }; -function getCommonProperty( - configArr: AnnotationConfig[], +function getCommonProperty( + configArr: EventAnnotationArgs[], propertyName: K, fallbackValue: T ) { @@ -107,9 +109,9 @@ function getCommonProperty( return fallbackValue; } -const getCommonStyles = (configArr: AnnotationConfig[]) => { +const getCommonStyles = (configArr: EventAnnotationArgs[]) => { return { - color: getCommonProperty( + color: getCommonProperty( configArr, 'color', defaultAnnotationColor @@ -123,7 +125,7 @@ const getCommonStyles = (configArr: AnnotationConfig[]) => { }; export const getCollectiveConfigsByInterval = ( - layers: XYAnnotationLayerConfig[], + layers: AnnotationLayerArgs[], minInterval?: number, firstTimestamp?: number, isBarChart?: boolean, @@ -137,7 +139,7 @@ export const getCollectiveConfigsByInterval = ( ); let collectiveConfig: CollectiveConfig; return Object.entries(visibleGroupedConfigs).map(([roundedTimestamp, configArr]) => { - collectiveConfig = { ...configArr[0], roundedTimestamp: Number(roundedTimestamp) }; + collectiveConfig = { ...configArr[0], roundedTimestamp: Number(roundedTimestamp), axisMode: 'bottom' }; if (configArr.length > 1) { const commonStyles = getCommonStyles(configArr); collectiveConfig = { @@ -164,7 +166,8 @@ export const Annotations = ({ const { roundedTimestamp } = config; const markerPositionVertical = getBaseIconPlacement(config.iconPosition); const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE; - const id = `${config.id}-line`; + const id = snakeCase(config.label) + return ( ['setDimension'] = ( ...(newLayer.config || []), { label: defaultAnnotationLabel, - annotationType: 'manual', - axisMode: 'bottom', key: { - keyType: 'point_in_time', - type: 'annotation_key', + type: 'point_in_time', timestamp: newTimestamp, }, icon: 'triangle', diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index 1bc18074f13a2..f8d5805279a2e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -13,7 +13,7 @@ import type { AccessorConfig, FramePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; import { FormatFactory, LayerType } from '../../common'; import type { XYLayerConfig } from '../../common/expressions'; -import { isReferenceLayer, isAnnotationsLayer, getDataLayers } from './visualization_helpers'; +import { isReferenceLayer, isAnnotationsLayer } from './visualization_helpers'; import { getAnnotationsAccessorColorConfig } from './annotations/helpers'; import { getReferenceLineAccessorColorConfig } from './reference_line_helpers'; @@ -44,7 +44,7 @@ export function getColorAssignments( ): ColorAssignments { const layersPerPalette: Record = {}; - getDataLayers(layers).forEach((layer) => { + layers.forEach((layer) => { const palette = layer.palette?.name || 'default'; if (!layersPerPalette[palette]) { layersPerPalette[palette] = []; diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 3ec0a0608f6d3..41cddda65bf17 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -548,7 +548,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -615,7 +615,9 @@ describe('xy_expression', () => { }} args={{ ...args, - layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'time' }], + layers: [ + { ...(args.layers[0] as DataLayerArgs), seriesType: 'line', xScaleType: 'time' }, + ], }} minInterval={undefined} /> @@ -804,7 +806,7 @@ describe('xy_expression', () => { ...args, layers: [ { - ...args.layers[0], + ...(args.layers[0] as DataLayerArgs), seriesType: 'line', xScaleType: 'time', isHistogram: true, @@ -880,7 +882,7 @@ describe('xy_expression', () => { ...args, layers: [ { - ...args.layers[0], + ...(args.layers[0] as DataLayerArgs), seriesType: 'bar', xScaleType: 'time', isHistogram: true, @@ -977,7 +979,7 @@ describe('xy_expression', () => { }, layers: [ { - ...args.layers[0], + ...(args.layers[0] as DataLayerArgs), seriesType: 'area', }, ], @@ -1008,7 +1010,7 @@ describe('xy_expression', () => { }, layers: [ { - ...args.layers[0], + ...(args.layers[0] as DataLayerArgs), seriesType: 'bar', }, ], @@ -1085,7 +1087,9 @@ describe('xy_expression', () => { }} args={{ ...args, - layers: [{ ...args.layers[0], seriesType: 'line', xScaleType: 'linear' }], + layers: [ + { ...(args.layers[0] as DataLayerArgs), seriesType: 'line', xScaleType: 'linear' }, + ], }} /> ); @@ -1104,7 +1108,12 @@ describe('xy_expression', () => { args={{ ...args, layers: [ - { ...args.layers[0], seriesType: 'line', xScaleType: 'linear', isHistogram: true }, + { + ...(args.layers[0] as DataLayerArgs), + seriesType: 'line', + xScaleType: 'linear', + isHistogram: true, + }, ], }} /> @@ -1152,7 +1161,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -1167,7 +1176,7 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -1182,7 +1191,10 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -1680,7 +1692,10 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -1695,7 +1710,10 @@ describe('xy_expression', () => { ); expect(component).toMatchSnapshot(); @@ -1712,7 +1730,9 @@ describe('xy_expression', () => { data={data} args={{ ...args, - layers: [{ ...args.layers[0], seriesType: 'bar_horizontal_stacked' }], + layers: [ + { ...(args.layers[0] as DataLayerArgs), seriesType: 'bar_horizontal_stacked' }, + ], }} /> ); @@ -1734,7 +1754,7 @@ describe('xy_expression', () => { ...args, layers: [ { - ...args.layers[0], + ...(args.layers[0] as DataLayerArgs), xAccessor: undefined, splitAccessor: 'e', seriesType: 'bar_stacked', @@ -1764,7 +1784,7 @@ describe('xy_expression', () => { accessors: ['b'], seriesType: 'bar', isHistogram: true, - }; + } as DataLayerArgs; delete firstLayer.splitAccessor; const component = shallow( @@ -1774,7 +1794,11 @@ describe('xy_expression', () => { test('it does not apply histogram mode to more than one bar series for unstacked bar chart', () => { const { data, args } = sampleArgs(); - const firstLayer: DataLayerArgs = { ...args.layers[0], seriesType: 'bar', isHistogram: true }; + const firstLayer: DataLayerArgs = { + ...args.layers[0], + seriesType: 'bar', + isHistogram: true, + } as DataLayerArgs; delete firstLayer.splitAccessor; const component = shallow( @@ -1789,13 +1813,13 @@ describe('xy_expression', () => { ...args.layers[0], seriesType: 'line', isHistogram: true, - }; + } as DataLayerArgs; delete firstLayer.splitAccessor; const secondLayer: DataLayerArgs = { ...args.layers[0], seriesType: 'line', isHistogram: true, - }; + } as DataLayerArgs; delete secondLayer.splitAccessor; const component = shallow( { ...args, layers: [ { - ...args.layers[0], + ...(args.layers[0] as DataLayerArgs), seriesType: 'bar_stacked', isHistogram: true, }, @@ -1838,7 +1862,9 @@ describe('xy_expression', () => { data={data} args={{ ...args, - layers: [{ ...args.layers[0], seriesType: 'bar', isHistogram: true }], + layers: [ + { ...(args.layers[0] as DataLayerArgs), seriesType: 'bar', isHistogram: true }, + ], }} /> ); @@ -2234,7 +2260,10 @@ describe('xy_expression', () => { ); expect(component.find(LineSeries).at(0).prop('xScaleType')).toEqual(ScaleType.Ordinal); @@ -2248,7 +2277,7 @@ describe('xy_expression', () => { ); expect(component.find(LineSeries).at(0).prop('yScaleType')).toEqual(ScaleType.Sqrt); @@ -2270,7 +2299,7 @@ describe('xy_expression', () => { ); expect(getFormatSpy).toHaveBeenCalledWith({ @@ -2680,7 +2709,9 @@ describe('xy_expression', () => { data={{ ...data }} args={{ ...args, - layers: [{ ...args.layers[0], accessors: ['a'], splitAccessor: undefined }], + layers: [ + { ...(args.layers[0] as DataLayerArgs), accessors: ['a'], splitAccessor: undefined }, + ], legend: { ...args.legend, isVisible: true, showSingleSeries: true }, }} /> @@ -2698,7 +2729,13 @@ describe('xy_expression', () => { data={{ ...data }} args={{ ...args, - layers: [{ ...args.layers[0], accessors: ['a'], splitAccessor: undefined }], + layers: [ + { + ...(args.layers[0] as DataLayerArgs), + accessors: ['a'], + splitAccessor: undefined, + }, + ], legend: { ...args.legend, isVisible: true, isInside: true }, }} /> @@ -2784,7 +2821,7 @@ describe('xy_expression', () => { test('it should apply None fitting function if not specified', () => { const { data, args } = sampleArgs(); - args.layers[0].accessors = ['a']; + (args.layers[0] as DataLayerArgs).accessors = ['a']; const component = shallow( @@ -2929,7 +2966,7 @@ describe('xy_expression', () => { beforeEach(() => { xyProps = sampleArgs(); - xyProps.args.layers[0].xScaleType = 'time'; + (xyProps.args.layers[0] as DataLayerArgs).xScaleType = 'time'; }); it('should use first valid layer and determine interval', async () => { xyProps.data.tables.first.columns[2].meta.source = 'esaggs'; @@ -2944,7 +2981,7 @@ describe('xy_expression', () => { }); it('should return interval of number histogram if available on first x axis columns', async () => { - xyProps.args.layers[0].xScaleType = 'linear'; + (xyProps.args.layers[0] as DataLayerArgs).xScaleType = 'linear'; xyProps.data.tables.first.columns[2].meta = { source: 'esaggs', type: 'number', @@ -2986,7 +3023,7 @@ describe('xy_expression', () => { }); it('should return undefined if x axis is not a date', async () => { - xyProps.args.layers[0].xScaleType = 'ordinal'; + (xyProps.args.layers[0] as DataLayerArgs).xScaleType = 'ordinal'; xyProps.data.tables.first.columns.splice(2, 1); const result = await calculateMinInterval(xyProps); expect(result).toEqual(undefined); diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 6e5d21007176f..efd614f14f0c1 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -54,7 +54,12 @@ import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public'; import { KibanaThemeProvider } from '../../../../../src/plugins/kibana_react/public'; import type { ILensInterpreterRenderHandlers, LensFilterEvent, LensBrushEvent } from '../types'; import type { LensMultiTable, FormatFactory } from '../../common'; -import type { DataLayerArgs, SeriesType, XYChartProps } from '../../common/expressions'; +import type { + DataLayerArgs, + SeriesType, + XYChartProps, + XYLayerArgs, +} from '../../common/expressions'; import { visualizationTypes } from './types'; import { VisualizationContainer } from '../visualization_container'; import { isHorizontalChart, getSeriesColor } from './state_helpers'; @@ -78,7 +83,11 @@ import { computeChartMargins, getLinesCausedPaddings } from './annotations_helpe import { Annotations, getCollectiveConfigsByInterval } from './annotations/expression'; import { computeOverallDataDomain } from './reference_line_helpers'; -import { getReferenceLayers, isDataLayer, getAnnotationsLayers } from './visualization_helpers'; +import { + getReferenceLayers, + getDataLayersArgs, + getAnnotationsLayersArgs, +} from './visualization_helpers'; declare global { interface Window { @@ -266,7 +275,9 @@ export function XYChart({ }); if (filteredLayers.length === 0) { - const icon: IconType = getIconForSeriesType(layers?.[0]?.seriesType || 'bar'); + const icon: IconType = getIconForSeriesType( + getDataLayersArgs(layers)?.[0]?.seriesType || 'bar' + ); return ; } @@ -354,7 +365,7 @@ export function XYChart({ }; const referenceLineLayers = getReferenceLayers(layers); - const annotationsLayers = getAnnotationsLayers(layers); + const annotationsLayers = getAnnotationsLayersArgs(layers); const firstTable = data.tables[filteredLayers[0].layerId]; @@ -477,7 +488,7 @@ export function XYChart({ const valueLabelsStyling = shouldShowValueLabels && valueLabels !== 'hide' && getValueLabelsStyling(shouldRotate); - const colorAssignments = getColorAssignments(args.layers, data, formatFactory); + const colorAssignments = getColorAssignments(getDataLayersArgs(args.layers), data, formatFactory); const clickHandler: ElementClickListener = ([[geometry, series]]) => { // for xyChart series is always XYChartSeriesIdentifier and geometry is always type of GeometryValue @@ -1020,22 +1031,19 @@ export function XYChart({ ); } -function getFilteredLayers(layers: DataLayerArgs[], data: LensMultiTable) { - return layers.filter((layer) => { +function getFilteredLayers(layers: XYLayerArgs[], data: LensMultiTable) { + return getDataLayersArgs(layers).filter((layer) => { const { layerId, xAccessor, accessors, splitAccessor } = layer; - return ( - isDataLayer(layer) && - !( - !accessors.length || - !data.tables[layerId] || - data.tables[layerId].rows.length === 0 || - (xAccessor && - data.tables[layerId].rows.every((row) => typeof row[xAccessor] === 'undefined')) || - // stacked percentage bars have no xAccessors but splitAccessor with undefined values in them when empty - (!xAccessor && - splitAccessor && - data.tables[layerId].rows.every((row) => typeof row[splitAccessor] === 'undefined')) - ) + return !( + !accessors.length || + !data.tables[layerId] || + data.tables[layerId].rows.length === 0 || + (xAccessor && + data.tables[layerId].rows.every((row) => typeof row[xAccessor] === 'undefined')) || + // stacked percentage bars have no xAccessors but splitAccessor with undefined values in them when empty + (!xAccessor && + splitAccessor && + data.tables[layerId].rows.every((row) => typeof row[splitAccessor] === 'undefined')) ); }); } diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 9a3deba12c571..45f54c559f400 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -421,10 +421,7 @@ const annotationLayerToExpression = ( ? layer.config.map( (config): Ast => eventAnnotationService.toExpression({ - id: config.id, - annotationType: config.annotationType, - key: config.key, - axisMode: 'bottom', + time: config.key.timestamp, label: config.label || defaultAnnotationLabel, textVisibility: config.textVisibility, icon: config.icon, diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx index c080686cc7477..b93c3dec601ae 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx @@ -11,9 +11,12 @@ import { DatasourcePublicAPI, OperationMetadata, VisualizationType } from '../ty import { State, visualizationTypes, XYState } from './types'; import { isHorizontalChart } from './state_helpers'; import { + AnnotationLayerArgs, + DataLayerArgs, SeriesType, XYAnnotationLayerConfig, XYDataLayerConfig, + XYLayerArgs, XYLayerConfig, XYReferenceLineLayerConfig, } from '../../common/expressions'; @@ -134,6 +137,9 @@ export const isDataLayer = (layer: Pick): layer is X export const getDataLayers = (layers: Array>) => (layers || []).filter((layer): layer is XYDataLayerConfig => isDataLayer(layer)); +export const getDataLayersArgs = (layers: XYLayerArgs[]) => + (layers || []).filter((layer): layer is DataLayerArgs => isDataLayer(layer)); + export const getFirstDataLayer = (layers: XYLayerConfig[]) => (layers || []).find((layer): layer is XYDataLayerConfig => isDataLayer(layer)); @@ -141,16 +147,19 @@ export const isReferenceLayer = ( layer: Pick ): layer is XYReferenceLineLayerConfig => layer.layerType === layerTypes.REFERENCELINE; -export const getReferenceLayers = (layers: XYLayerConfig[]) => +export const getReferenceLayers = (layers: Array>) => (layers || []).filter((layer): layer is XYReferenceLineLayerConfig => isReferenceLayer(layer)); export const isAnnotationsLayer = ( layer: Pick ): layer is XYAnnotationLayerConfig => layer.layerType === layerTypes.ANNOTATIONS; -export const getAnnotationsLayers = (layers: XYLayerConfig[]) => +export const getAnnotationsLayers = (layers: Array>) => (layers || []).filter((layer): layer is XYAnnotationLayerConfig => isAnnotationsLayer(layer)); +export const getAnnotationsLayersArgs = (layers: XYLayerArgs[]) => + (layers || []).filter((layer): layer is AnnotationLayerArgs => isAnnotationsLayer(layer)); + export interface LayerTypeToLayer { [layerTypes.DATA]: (layer: XYDataLayerConfig) => XYDataLayerConfig; [layerTypes.REFERENCELINE]: (layer: XYReferenceLineLayerConfig) => XYReferenceLineLayerConfig; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx index 2f91cb5c1f516..919df17447b98 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx @@ -19,6 +19,19 @@ interface LabelConfigurationOptions { axisMode?: YAxisMode; } +const topLabel = i18n.translate('xpack.lens.xyChart.markerPosition.above', { + defaultMessage: 'Top', +}); +const bottomLabel = i18n.translate('xpack.lens.xyChart.markerPosition.below', { + defaultMessage: 'Bottom', +}); +const leftLabel = i18n.translate('xpack.lens.xyChart.markerPosition.left', { + defaultMessage: 'Left', +}); +const rightLabel = i18n.translate('xpack.lens.xyChart.markerPosition.right', { + defaultMessage: 'Right', +}); + function getIconPositionOptions({ isHorizontal, axisMode }: LabelConfigurationOptions) { const autoOption = { id: `${idPrefix}auto`, @@ -28,18 +41,6 @@ function getIconPositionOptions({ isHorizontal, axisMode }: LabelConfigurationOp 'data-test-subj': 'lnsXY_markerPosition_auto', }; - const topLabel = i18n.translate('xpack.lens.xyChart.markerPosition.above', { - defaultMessage: 'Top', - }); - const bottomLabel = i18n.translate('xpack.lens.xyChart.markerPosition.below', { - defaultMessage: 'Bottom', - }); - const leftLabel = i18n.translate('xpack.lens.xyChart.markerPosition.left', { - defaultMessage: 'Left', - }); - const rightLabel = i18n.translate('xpack.lens.xyChart.markerPosition.right', { - defaultMessage: 'Right', - }); if (axisMode === 'bottom') { return [ { From fcfe9876fec48e8b82a024c6d0a56343d5a7ce9f Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Thu, 17 Mar 2022 12:06:58 +0100 Subject: [PATCH 18/47] change timestamp format --- packages/kbn-optimizer/limits.yml | 1 + src/plugins/event_annotation/common/index.ts | 4 +- .../index.ts} | 4 +- .../common/manual_event_annotation/types.ts | 15 +++++ src/plugins/event_annotation/common/types.ts | 11 +--- .../annotations/config_panel/index.tsx | 4 +- .../annotations/expression.tsx | 58 +++++++++---------- .../xy_visualization/annotations/helpers.tsx | 5 +- .../public/xy_visualization/expression.tsx | 3 +- 9 files changed, 59 insertions(+), 46 deletions(-) rename src/plugins/event_annotation/common/{annotation_expression.ts => manual_event_annotation/index.ts} (96%) create mode 100644 src/plugins/event_annotation/common/manual_event_annotation/types.ts diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 396ffd4599284..526c1ff5dad82 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -124,3 +124,4 @@ pageLoadAssetSize: sessionView: 77750 cloudSecurityPosture: 19109 visTypeGauge: 24113 + eventAnnotation: 19334 diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts index f0060d3169c1a..0d55edc0dd5a4 100644 --- a/src/plugins/event_annotation/common/index.ts +++ b/src/plugins/event_annotation/common/index.ts @@ -6,5 +6,5 @@ * Side Public License, v 1. */ -export { manualEventAnnotation } from './annotation_expression'; -export type { EventAnnotationConfig, EventAnnotationArgs } from './types'; +export { EventAnnotationArgs, manualEventAnnotation } from './manual_event_annotation'; +export type { EventAnnotationConfig } from './types'; diff --git a/src/plugins/event_annotation/common/annotation_expression.ts b/src/plugins/event_annotation/common/manual_event_annotation/index.ts similarity index 96% rename from src/plugins/event_annotation/common/annotation_expression.ts rename to src/plugins/event_annotation/common/manual_event_annotation/index.ts index 2d1eadb459bb6..166b39da4b47f 100644 --- a/src/plugins/event_annotation/common/annotation_expression.ts +++ b/src/plugins/event_annotation/common/manual_event_annotation/index.ts @@ -8,7 +8,7 @@ import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; import { EventAnnotationArgs, EventAnnotationOutput } from './types'; - +export { EventAnnotationArgs } from './types'; export const manualEventAnnotation: ExpressionFunctionDefinition< 'manual_event_annotation', null, @@ -22,7 +22,7 @@ export const manualEventAnnotation: ExpressionFunctionDefinition< inputTypes: ['null'], args: { time: { - types: ['number'], + types: ['string'], help: 'The name', }, label: { diff --git a/src/plugins/event_annotation/common/manual_event_annotation/types.ts b/src/plugins/event_annotation/common/manual_event_annotation/types.ts new file mode 100644 index 0000000000000..e1bed4a592d23 --- /dev/null +++ b/src/plugins/event_annotation/common/manual_event_annotation/types.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { StyleProps } from '../types'; + +export type EventAnnotationArgs = { + time: string; +} & StyleProps; + +export type EventAnnotationOutput = EventAnnotationArgs & { type: 'manual_event_annotation' }; diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts index 6a03de8d6d8e9..7b3d21a1bcc69 100644 --- a/src/plugins/event_annotation/common/types.ts +++ b/src/plugins/event_annotation/common/types.ts @@ -11,7 +11,7 @@ export type IconPosition = 'auto' | 'left' | 'right' | 'above' | 'below'; export type AnnotationType = 'manual'; export type KeyType = 'point_in_time'; -interface StyleProps { +export interface StyleProps { label: string; color?: string; icon?: string; @@ -22,16 +22,11 @@ interface StyleProps { isHidden?: boolean; } +// Lens state export type EventAnnotationConfig = { id: string; key: { type: KeyType; - timestamp: number; + timestamp: string; }; } & StyleProps; - -export type EventAnnotationArgs = { - time: number; -} & StyleProps; - -export type EventAnnotationOutput = EventAnnotationArgs & { type: 'manual_event_annotation' }; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx index 0caddfc48f9ec..6ec540bb247fe 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx @@ -74,7 +74,7 @@ export const AnnotationsPanel = ( setConfig({ key: { ...(currentConfig?.key || { type: 'point_in_time' }), - timestamp: date?.valueOf(), + timestamp: date.toISOString(), }, }); } @@ -95,7 +95,7 @@ export const AnnotationsPanel = ( setConfig={setConfig} currentConfig={{ axisMode: 'bottom', - ...currentConfig + ...currentConfig, }} customIconSet={annotationsIconSet} /> diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index 893e999b637b3..86669cc159699 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -16,6 +16,7 @@ import { } from '@elastic/charts'; import type { FieldFormat } from 'src/plugins/field_formats/common'; import { EventAnnotationArgs } from 'src/plugins/event_annotation/common'; +import moment from 'moment'; import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; import type { AnnotationLayerArgs, IconPosition } from '../../../common/expressions'; import { hasIcon } from '../xy_config_panel/shared/icon_select'; @@ -28,17 +29,13 @@ import { AnnotationIcon, } from '../annotations_helpers'; -const getRoundedTimestamp = ( - timestamp: number, - firstTimestamp?: number, - minInterval?: number, - isBarChart?: boolean -) => { +const getRoundedTimestamp = (timestamp: number, firstTimestamp?: number, minInterval?: number) => { if (!firstTimestamp || !minInterval) { return timestamp; } const roundedTimestamp = timestamp - ((timestamp - firstTimestamp) % minInterval); - return isBarChart ? roundedTimestamp + minInterval / 2 : roundedTimestamp; + // todo: postprocess if it's bar + return roundedTimestamp; }; export interface AnnotationsProps { @@ -47,6 +44,8 @@ export interface AnnotationsProps { isHorizontal: boolean; paddingMap: Partial>; hide?: boolean; + minInterval?: number; + isBarChart?: boolean; } interface CollectiveConfig extends EventAnnotationArgs { @@ -58,17 +57,15 @@ interface CollectiveConfig extends EventAnnotationArgs { const groupVisibleConfigsByInterval = ( layers: AnnotationLayerArgs[], minInterval?: number, - firstTimestamp?: number, - isBarChart?: boolean, + firstTimestamp?: number ) => { return layers .flatMap(({ config: configs }) => configs.filter((config) => !config.isHidden)) .reduce>((acc, current) => { const roundedTimestamp = getRoundedTimestamp( - Number(current.time), + moment(current.time).valueOf(), firstTimestamp, - minInterval, - isBarChart + minInterval ); return { ...acc, @@ -78,7 +75,10 @@ const groupVisibleConfigsByInterval = ( }; const createCustomTooltipDetails = - (config: EventAnnotationArgs[], formatter?: FieldFormat): AnnotationTooltipFormatter | undefined => + ( + config: EventAnnotationArgs[], + formatter?: FieldFormat + ): AnnotationTooltipFormatter | undefined => () => { return (
@@ -88,9 +88,7 @@ const createCustomTooltipDetails = {hasIcon(icon) ? : null} {label} - - {formatter?.convert(time) || String(time)} - + {formatter?.convert(time) || String(time)}
))}
@@ -128,18 +126,16 @@ export const getCollectiveConfigsByInterval = ( layers: AnnotationLayerArgs[], minInterval?: number, firstTimestamp?: number, - isBarChart?: boolean, formatter?: FieldFormat ) => { - const visibleGroupedConfigs = groupVisibleConfigsByInterval( - layers, - minInterval, - firstTimestamp, - isBarChart, - ); + const visibleGroupedConfigs = groupVisibleConfigsByInterval(layers, minInterval, firstTimestamp); let collectiveConfig: CollectiveConfig; return Object.entries(visibleGroupedConfigs).map(([roundedTimestamp, configArr]) => { - collectiveConfig = { ...configArr[0], roundedTimestamp: Number(roundedTimestamp), axisMode: 'bottom' }; + collectiveConfig = { + ...configArr[0], + roundedTimestamp: Number(roundedTimestamp), + axisMode: 'bottom', + }; if (configArr.length > 1) { const commonStyles = getCommonStyles(configArr); collectiveConfig = { @@ -159,15 +155,16 @@ export const Annotations = ({ isHorizontal, paddingMap, hide, + minInterval, + isBarChart, }: AnnotationsProps) => { return ( <> {collectiveAnnotationConfigs.map((config) => { - const { roundedTimestamp } = config; const markerPositionVertical = getBaseIconPlacement(config.iconPosition); const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE; - const id = snakeCase(config.label) - + const id = snakeCase(config.label); + const { roundedTimestamp } = config; return ( layerId); if ( @@ -58,7 +59,7 @@ export function getStaticDate( return lastTimestamp && lastTimestamp > acc ? lastTimestamp : acc; }, MIN_DATE); const middleDate = (minDate + maxDate) / 2; - return middleDate; + return moment(middleDate).toISOString(); } export const getAnnotationsSupportedLayer = ( diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index efd614f14f0c1..540b27c6c0668 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -375,7 +375,6 @@ export function XYChart({ annotationsLayers, minInterval, xColumnId ? firstTable.rows[0]?.[xColumnId] : undefined, - filteredBarLayers.length > 0, xAxisFormatter ); const visualConfigs = [ @@ -1025,6 +1024,8 @@ export function XYChart({ formatter={xAxisFormatter} isHorizontal={shouldRotate} paddingMap={linesPaddings} + isBarChart={filteredBarLayers.length > 0} + minInterval={minInterval} /> ) : null} From ed3f858f1fd2af9b329d461c4e7aeaff50833431 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Thu, 17 Mar 2022 14:30:48 +0100 Subject: [PATCH 19/47] added expression event_annotation_group --- .../common/event_annotation_group/index.ts | 47 +++++++++++++++++++ src/plugins/event_annotation/common/index.ts | 1 + src/plugins/event_annotation/public/plugin.ts | 3 +- src/plugins/event_annotation/server/plugin.ts | 3 +- 4 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 src/plugins/event_annotation/common/event_annotation_group/index.ts diff --git a/src/plugins/event_annotation/common/event_annotation_group/index.ts b/src/plugins/event_annotation/common/event_annotation_group/index.ts new file mode 100644 index 0000000000000..4bbfb0237ce12 --- /dev/null +++ b/src/plugins/event_annotation/common/event_annotation_group/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { EventAnnotationOutput } from '../manual_event_annotation/types'; + +export interface EventAnnotationGroupOutput { + type: 'event_annotation_group'; + annotations: EventAnnotationOutput[]; +} + +export interface EventAnnotationGroupArgs { + annotations: EventAnnotationOutput[]; +} + +export function eventAnnotationGroup(): ExpressionFunctionDefinition< + 'event_annotation_group', + null, + EventAnnotationGroupArgs, + EventAnnotationGroupOutput +> { + return { + name: 'event_annotation_group', + aliases: [], + type: 'event_annotation_group', + inputTypes: ['null'], + help: '', + args: { + annotations: { + types: ['manual_event_annotation'], + help: '', + multi: true, + }, + }, + fn: (input, args) => { + return { + type: 'event_annotation_group', + annotations: args.annotations, + }; + }, + }; +} diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts index 0d55edc0dd5a4..d923251bd0fd8 100644 --- a/src/plugins/event_annotation/common/index.ts +++ b/src/plugins/event_annotation/common/index.ts @@ -7,4 +7,5 @@ */ export { EventAnnotationArgs, manualEventAnnotation } from './manual_event_annotation'; +export { eventAnnotationGroup, EventAnnotationGroupArgs } from './event_annotation_group'; export type { EventAnnotationConfig } from './types'; diff --git a/src/plugins/event_annotation/public/plugin.ts b/src/plugins/event_annotation/public/plugin.ts index 47a7ff15360d3..cfe7df4018ab5 100644 --- a/src/plugins/event_annotation/public/plugin.ts +++ b/src/plugins/event_annotation/public/plugin.ts @@ -8,7 +8,7 @@ import { Plugin, CoreSetup } from 'kibana/public'; import { ExpressionsSetup } from '../../expressions/public'; -import { manualEventAnnotation } from '../common'; +import { manualEventAnnotation, eventAnnotationGroup } from '../common'; import { EventAnnotationService } from './event_annotation_service'; interface SetupDependencies { @@ -29,6 +29,7 @@ export class EventAnnotationPlugin public setup(core: CoreSetup, dependencies: SetupDependencies): EventAnnotationPluginSetup { dependencies.expressions.registerFunction(manualEventAnnotation); + dependencies.expressions.registerFunction(eventAnnotationGroup); return this.eventAnnotationService; } diff --git a/src/plugins/event_annotation/server/plugin.ts b/src/plugins/event_annotation/server/plugin.ts index 9af8dc228e39f..ef4e0216fb5ac 100644 --- a/src/plugins/event_annotation/server/plugin.ts +++ b/src/plugins/event_annotation/server/plugin.ts @@ -7,7 +7,7 @@ */ import { CoreSetup, Plugin } from 'kibana/server'; -import { manualEventAnnotation } from '../common'; +import { manualEventAnnotation, eventAnnotationGroup } from '../common'; import { ExpressionsServerSetup } from '../../expressions/server'; interface SetupDependencies { @@ -17,6 +17,7 @@ interface SetupDependencies { export class EventAnnotationServerPlugin implements Plugin { public setup(core: CoreSetup, dependencies: SetupDependencies) { dependencies.expressions.registerFunction(manualEventAnnotation); + dependencies.expressions.registerFunction(eventAnnotationGroup); return {}; } From 437d464fa181c4d2b00808ca56fb8c804409fcc7 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Thu, 17 Mar 2022 15:29:16 +0100 Subject: [PATCH 20/47] names refactor --- .../layer_config/annotation_layer_config.ts | 76 +++++++++---------- .../annotations/config_panel/index.tsx | 40 +++++----- .../annotations/expression.tsx | 40 +++++----- .../xy_visualization/annotations/helpers.tsx | 20 ++--- .../public/xy_visualization/expression.tsx | 12 +-- .../public/xy_visualization/state_helpers.ts | 2 +- .../public/xy_visualization/to_expression.ts | 36 ++++----- .../public/xy_visualization/visualization.tsx | 5 +- .../visualization_helpers.tsx | 2 +- 9 files changed, 115 insertions(+), 118 deletions(-) diff --git a/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts index 15b6fc69d8834..45b4bf31c0cdc 100644 --- a/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts +++ b/x-pack/plugins/lens/common/expressions/xy_chart/layer_config/annotation_layer_config.ts @@ -6,8 +6,8 @@ */ import { - EventAnnotationArgs, EventAnnotationConfig, + EventAnnotationOutput, } from '../../../../../../../src/plugins/event_annotation/common'; import type { ExpressionFunctionDefinition } from '../../../../../../../src/plugins/expressions/common'; import { layerTypes } from '../../../constants'; @@ -15,57 +15,53 @@ import { layerTypes } from '../../../constants'; export interface XYAnnotationLayerConfig { layerId: string; layerType: typeof layerTypes.ANNOTATIONS; - config: EventAnnotationConfig[]; + annotations: EventAnnotationConfig[]; hide?: boolean; } export interface AnnotationLayerArgs { + annotations: EventAnnotationOutput[]; layerId: string; layerType: typeof layerTypes.ANNOTATIONS; - config: EventAnnotationArgs[]; hide?: boolean; } -export interface XYAnnotationLayerArgsResult { - layerId: string; - layerType: typeof layerTypes.ANNOTATIONS; +export type XYAnnotationLayerArgsResult = AnnotationLayerArgs & { type: 'lens_xy_annotation_layer'; - config: EventAnnotationArgs[]; - hide?: boolean; -} - -export const annotationLayerConfig: ExpressionFunctionDefinition< +}; +export function annotationLayerConfig(): ExpressionFunctionDefinition< 'lens_xy_annotation_layer', null, AnnotationLayerArgs, XYAnnotationLayerArgsResult -> = { - name: 'lens_xy_annotation_layer', - aliases: [], - type: 'lens_xy_annotation_layer', - help: `Configure a layer in the xy chart`, - inputTypes: ['null'], - args: { - layerId: { - types: ['string'], - help: '', - }, - layerType: { types: ['string'], options: [layerTypes.ANNOTATIONS], help: '' }, - hide: { - types: ['boolean'], - default: false, - help: 'Show details', +> { + return { + name: 'lens_xy_annotation_layer', + aliases: [], + type: 'lens_xy_annotation_layer', + inputTypes: ['null'], + help: 'Annotation layer in lens', + args: { + layerId: { + types: ['string'], + help: '', + }, + layerType: { types: ['string'], options: [layerTypes.ANNOTATIONS], help: '' }, + hide: { + types: ['boolean'], + default: false, + help: 'Show details', + }, + annotations: { + types: ['manual_event_annotation'], + help: '', + multi: true, + }, }, - config: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - types: ['manual_event_annotation' as any], - help: 'Additional configuration for y axes', - multi: true, + fn: (input, args) => { + return { + type: 'lens_xy_annotation_layer', + ...args, + }; }, - }, - fn: function fn(input: unknown, args: AnnotationLayerArgs) { - return { - type: 'lens_xy_annotation_layer', - ...args, - }; - }, -}; + }; +} diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx index 6ec540bb247fe..fff9b8fecc471 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx @@ -46,21 +46,21 @@ export const AnnotationsPanel = ( (l) => l.layerId === layerId ) as XYAnnotationLayerConfig; - const currentConfig = localLayer.config?.find((c) => c.id === accessor); + const currentAnnotations = localLayer.annotations?.find((c) => c.id === accessor); - const setConfig = useCallback( - (config: Partial | undefined) => { - if (config == null) { + const setAnnotations = useCallback( + (annotations: Partial | undefined) => { + if (annotations == null) { return; } - const newConfigs = [...(localLayer.config || [])]; + const newConfigs = [...(localLayer.annotations || [])]; const existingIndex = newConfigs.findIndex((c) => c.id === accessor); if (existingIndex !== -1) { - newConfigs[existingIndex] = { ...newConfigs[existingIndex], ...config }; + newConfigs[existingIndex] = { ...newConfigs[existingIndex], ...annotations }; } else { - return; // that should never happen because annotations are created before config panel is opened + return; // that should never happen because annotations are created before annotations panel is opened } - setLocalState(updateLayer(localState, { ...localLayer, config: newConfigs }, index)); + setLocalState(updateLayer(localState, { ...localLayer, annotations: newConfigs }, index)); }, [accessor, index, localState, localLayer, setLocalState] ); @@ -68,12 +68,12 @@ export const AnnotationsPanel = ( return ( <> { if (date) { - setConfig({ + setAnnotations({ key: { - ...(currentConfig?.key || { type: 'point_in_time' }), + ...(currentAnnotations?.key || { type: 'point_in_time' }), timestamp: date.toISOString(), }, }); @@ -84,37 +84,37 @@ export const AnnotationsPanel = ( })} /> { - setConfig({ label: value }); + setAnnotations({ label: value }); }} /> setConfig({ isHidden: ev.target.checked })} + value={Boolean(currentAnnotations?.isHidden)} + onChange={(ev) => setAnnotations({ isHidden: ev.target.checked })} /> ); diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index 86669cc159699..7a8c8f954c5c5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -39,7 +39,7 @@ const getRoundedTimestamp = (timestamp: number, firstTimestamp?: number, minInte }; export interface AnnotationsProps { - collectiveAnnotationConfigs: CollectiveConfig[]; + groupedAnnotations: CollectiveConfig[]; formatter?: FieldFormat; isHorizontal: boolean; paddingMap: Partial>; @@ -60,7 +60,7 @@ const groupVisibleConfigsByInterval = ( firstTimestamp?: number ) => { return layers - .flatMap(({ config: configs }) => configs.filter((config) => !config.isHidden)) + .flatMap(({ annotations }) => annotations.filter((a) => !a.isHidden)) .reduce>((acc, current) => { const roundedTimestamp = getRoundedTimestamp( moment(current.time).valueOf(), @@ -122,7 +122,7 @@ const getCommonStyles = (configArr: EventAnnotationArgs[]) => { }; }; -export const getCollectiveConfigsByInterval = ( +export const getAnnotationsGroupedByInterval = ( layers: AnnotationLayerArgs[], minInterval?: number, firstTimestamp?: number, @@ -150,7 +150,7 @@ export const getCollectiveConfigsByInterval = ( }; export const Annotations = ({ - collectiveAnnotationConfigs, + groupedAnnotations, formatter, isHorizontal, paddingMap, @@ -160,11 +160,11 @@ export const Annotations = ({ }: AnnotationsProps) => { return ( <> - {collectiveAnnotationConfigs.map((config) => { - const markerPositionVertical = getBaseIconPlacement(config.iconPosition); + {groupedAnnotations.map((annotation) => { + const markerPositionVertical = getBaseIconPlacement(annotation.iconPosition); const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE; - const id = snakeCase(config.label); - const { roundedTimestamp } = config; + const id = snakeCase(annotation.label); + const { roundedTimestamp } = annotation; return ( ) : undefined @@ -185,7 +185,9 @@ export const Annotations = ({ markerBody={ !hide ? ( ) : undefined @@ -202,19 +204,19 @@ export const Annotations = ({ ).valueOf(), header: formatter?.convert(roundedTimestamp) || moment(roundedTimestamp).toISOString(), - details: config.label, + details: annotation.label, }, ]} - customTooltipDetails={config.customTooltipDetails} + customTooltipDetails={annotation.customTooltipDetails} style={{ line: { - strokeWidth: config.lineWidth || 1, - stroke: config.color || defaultAnnotationColor, + strokeWidth: annotation.lineWidth || 1, + stroke: annotation.color || defaultAnnotationColor, dash: - config.lineStyle === 'dashed' - ? [(config.lineWidth || 1) * 3, config.lineWidth || 1] - : config.lineStyle === 'dotted' - ? [config.lineWidth || 1, config.lineWidth || 1] + annotation.lineStyle === 'dashed' + ? [(annotation.lineWidth || 1) * 3, annotation.lineWidth || 1] + : annotation.lineStyle === 'dotted' + ? [annotation.lineWidth || 1, annotation.lineWidth || 1] : undefined, opacity: 1, }, diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index 93bad6f63f009..6d4b7a575258d 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -114,14 +114,14 @@ export const setAnnotationsDimension: Visualization['setDimension'] = ( const dataLayers = getDataLayers(prevState.layers); const newLayer = { ...foundLayer } as XYAnnotationLayerConfig; - const hasConfig = newLayer.config?.some(({ id }) => id === columnId); + const hasConfig = newLayer.annotations?.some(({ id }) => id === columnId); const previousConfig = previousColumn - ? newLayer.config?.find(({ id }) => id === previousColumn) + ? newLayer.annotations?.find(({ id }) => id === previousColumn) : false; if (!hasConfig) { const newTimestamp = getStaticDate(dataLayers, frame?.activeData); - newLayer.config = [ - ...(newLayer.config || []), + newLayer.annotations = [ + ...(newLayer.annotations || []), { label: defaultAnnotationLabel, key: { @@ -141,11 +141,11 @@ export const setAnnotationsDimension: Visualization['setDimension'] = ( }; export const getAnnotationsAccessorColorConfig = (layer: XYAnnotationLayerConfig) => { - return layer.config.map((config) => { + return layer.annotations.map((annotation) => { return { - columnId: config.id, - triggerIcon: config.isHidden ? ('invisible' as const) : ('color' as const), - color: config?.color || defaultAnnotationColor, + columnId: annotation.id, + triggerIcon: annotation.isHidden ? ('invisible' as const) : ('color' as const), + color: annotation?.color || defaultAnnotationColor, }; }); }; @@ -210,10 +210,10 @@ export const getUniqueLabels = (layers: XYLayerConfig[]) => { }; annotationLayers.forEach((layer) => { - if (!layer.config) { + if (!layer.annotations) { return; } - layer.config.forEach((l) => { + layer.annotations.forEach((l) => { columnLabelMap[l.id] = makeUnique(l.label); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 540b27c6c0668..5193cc2a58f1a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -81,7 +81,7 @@ import { ReferenceLineAnnotations } from './expression_reference_lines'; import { computeChartMargins, getLinesCausedPaddings } from './annotations_helpers'; -import { Annotations, getCollectiveConfigsByInterval } from './annotations/expression'; +import { Annotations, getAnnotationsGroupedByInterval } from './annotations/expression'; import { computeOverallDataDomain } from './reference_line_helpers'; import { getReferenceLayers, @@ -371,7 +371,7 @@ export function XYChart({ const xColumnId = firstTable.columns.find((col) => col.id === filteredLayers[0].xAccessor)?.id; - const collectiveAnnotationConfigs = getCollectiveConfigsByInterval( + const groupedAnnotations = getAnnotationsGroupedByInterval( annotationsLayers, minInterval, xColumnId ? firstTable.rows[0]?.[xColumnId] : undefined, @@ -379,7 +379,7 @@ export function XYChart({ ); const visualConfigs = [ ...referenceLineLayers.flatMap(({ yConfig }) => yConfig), - ...collectiveAnnotationConfigs, + ...groupedAnnotations, ].filter(Boolean); const linesPaddings = getLinesCausedPaddings(visualConfigs, yAxesMap); @@ -594,7 +594,7 @@ export function XYChart({ !chartHasMoreThanOneBarSeries) ); - const bottomAnnotationsExist = collectiveAnnotationConfigs.length && linesPaddings.bottom; + const bottomAnnotationsExist = groupedAnnotations.length && linesPaddings.bottom; const shouldUseNewTimeAxis = isTimeViz && @@ -1017,10 +1017,10 @@ export function XYChart({ paddingMap={linesPaddings} /> ) : null} - {collectiveAnnotationConfigs.length ? ( + {groupedAnnotations.length ? ( { if (isAnnotationsLayer(layer)) { - return layer?.config?.find((config) => config.id === accessor)?.color || null; + return layer?.annotations?.find((ann) => ann.id === accessor)?.color || null; } if (isDataLayer(layer) && layer.splitAccessor) { return null; diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 45f54c559f400..7a300f85747be 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -84,8 +84,8 @@ const simplifiedLayerExpression = { [layerTypes.REFERENCELINE]: (layer: XYReferenceLineLayerConfig) => ({ ...layer, hide: true, - yConfig: layer.yConfig?.map(({ lineWidth, ...config }) => ({ - ...config, + yConfig: layer.yConfig?.map(({ lineWidth, ...rest }) => ({ + ...rest, lineWidth: 1, icon: undefined, textVisibility: false, @@ -94,8 +94,8 @@ const simplifiedLayerExpression = { [layerTypes.ANNOTATIONS]: (layer: XYAnnotationLayerConfig) => ({ ...layer, hide: true, - config: layer.config?.map(({ lineWidth, ...config }) => ({ - ...config, + annotations: layer.annotations?.map(({ lineWidth, ...rest }) => ({ + ...rest, lineWidth: 1, icon: undefined, textVisibility: false, @@ -175,11 +175,11 @@ export const buildExpression = ( const uniqueLabels = getUniqueLabels(state.layers); const validAnnotationsLayers = getAnnotationsLayers(state.layers) - .filter((layer) => Boolean(layer.config.length)) + .filter((layer) => Boolean(layer.annotations.length)) .map((layer) => { return { ...layer, - config: layer.config.map((c) => ({ + annotations: layer.annotations.map((c) => ({ ...c, label: uniqueLabels[c.id], })), @@ -417,19 +417,19 @@ const annotationLayerToExpression = ( hide: [Boolean(layer.hide)], layerId: [layer.layerId], layerType: [layerTypes.ANNOTATIONS], - config: layer.config - ? layer.config.map( - (config): Ast => + annotations: layer.annotations + ? layer.annotations.map( + (ann): Ast => eventAnnotationService.toExpression({ - time: config.key.timestamp, - label: config.label || defaultAnnotationLabel, - textVisibility: config.textVisibility, - icon: config.icon, - iconPosition: config.iconPosition, - lineStyle: config.lineStyle, - lineWidth: config.lineWidth, - color: config.color, - isHidden: Boolean(config.isHidden), + time: ann.key.timestamp, + label: ann.label || defaultAnnotationLabel, + textVisibility: ann.textVisibility, + icon: ann.icon, + iconPosition: ann.iconPosition, + lineStyle: ann.lineStyle, + lineWidth: ann.lineWidth, + color: ann.color, + isHidden: Boolean(ann.isHidden), }) ) : [], diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 931a3b54282ca..1e76872d456bf 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -404,9 +404,8 @@ export const getXyVisualization = ({ } if (isAnnotationsLayer(foundLayer)) { const newLayer = { ...foundLayer }; - if ('config' in newLayer) { - newLayer.config = newLayer.config.filter(({ id }) => id !== columnId); - } + newLayer.annotations = newLayer.annotations.filter(({ id }) => id !== columnId); + const newLayers = prevState.layers.map((l) => (l.layerId === layerId ? newLayer : l)); return { ...prevState, diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx index b93c3dec601ae..23c2446ca2363 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization_helpers.tsx @@ -290,7 +290,7 @@ const newLayerFn = { [layerTypes.ANNOTATIONS]: ({ layerId }: { layerId: string }): XYAnnotationLayerConfig => ({ layerId, layerType: layerTypes.ANNOTATIONS, - config: [], + annotations: [], }), }; From 653a2262d017ca55cfbb42c27b389652b4204e41 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Sat, 19 Mar 2022 10:01:00 +0100 Subject: [PATCH 21/47] ea service adding help descriptions --- src/plugins/event_annotation/README.md | 4 ++-- .../common/event_annotation_group/index.ts | 4 ++-- src/plugins/event_annotation/common/index.ts | 6 ++++-- .../common/manual_event_annotation/index.ts | 9 ++++----- src/plugins/event_annotation/common/types.ts | 1 - .../public/event_annotation_service/README.md | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/plugins/event_annotation/README.md b/src/plugins/event_annotation/README.md index 6d6ef8192b4e7..a7a85d3ab3641 100644 --- a/src/plugins/event_annotation/README.md +++ b/src/plugins/event_annotation/README.md @@ -1,3 +1,3 @@ -# Annotation service +# Event Annotation service -// TODO \ No newline at end of file +The Event Annotation service contains expressions for event annotations diff --git a/src/plugins/event_annotation/common/event_annotation_group/index.ts b/src/plugins/event_annotation/common/event_annotation_group/index.ts index 4bbfb0237ce12..49a2194350d83 100644 --- a/src/plugins/event_annotation/common/event_annotation_group/index.ts +++ b/src/plugins/event_annotation/common/event_annotation_group/index.ts @@ -7,7 +7,7 @@ */ import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -import { EventAnnotationOutput } from '../manual_event_annotation/types'; +import type { EventAnnotationOutput } from '../manual_event_annotation/types'; export interface EventAnnotationGroupOutput { type: 'event_annotation_group'; @@ -33,7 +33,7 @@ export function eventAnnotationGroup(): ExpressionFunctionDefinition< args: { annotations: { types: ['manual_event_annotation'], - help: '', + help: 'Annotation configs', multi: true, }, }, diff --git a/src/plugins/event_annotation/common/index.ts b/src/plugins/event_annotation/common/index.ts index d923251bd0fd8..332fa19150aad 100644 --- a/src/plugins/event_annotation/common/index.ts +++ b/src/plugins/event_annotation/common/index.ts @@ -6,6 +6,8 @@ * Side Public License, v 1. */ -export { EventAnnotationArgs, manualEventAnnotation } from './manual_event_annotation'; -export { eventAnnotationGroup, EventAnnotationGroupArgs } from './event_annotation_group'; +export type { EventAnnotationArgs, EventAnnotationOutput } from './manual_event_annotation/types'; +export { manualEventAnnotation } from './manual_event_annotation'; +export { eventAnnotationGroup } from './event_annotation_group'; +export type { EventAnnotationGroupArgs } from './event_annotation_group'; export type { EventAnnotationConfig } from './types'; diff --git a/src/plugins/event_annotation/common/manual_event_annotation/index.ts b/src/plugins/event_annotation/common/manual_event_annotation/index.ts index 166b39da4b47f..038bd170d7867 100644 --- a/src/plugins/event_annotation/common/manual_event_annotation/index.ts +++ b/src/plugins/event_annotation/common/manual_event_annotation/index.ts @@ -7,8 +7,7 @@ */ import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -import { EventAnnotationArgs, EventAnnotationOutput } from './types'; -export { EventAnnotationArgs } from './types'; +import type { EventAnnotationArgs, EventAnnotationOutput } from './types'; export const manualEventAnnotation: ExpressionFunctionDefinition< 'manual_event_annotation', null, @@ -23,15 +22,15 @@ export const manualEventAnnotation: ExpressionFunctionDefinition< args: { time: { types: ['string'], - help: 'The name', + help: 'Timestamp for annotation', }, label: { types: ['string'], - help: 'The name', + help: 'The name of the annotation', }, color: { types: ['string'], - help: 'The color of the series', + help: 'The color of the line', }, lineStyle: { types: ['string'], diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts index 7b3d21a1bcc69..53b93a8866f36 100644 --- a/src/plugins/event_annotation/common/types.ts +++ b/src/plugins/event_annotation/common/types.ts @@ -22,7 +22,6 @@ export interface StyleProps { isHidden?: boolean; } -// Lens state export type EventAnnotationConfig = { id: string; key: { diff --git a/src/plugins/event_annotation/public/event_annotation_service/README.md b/src/plugins/event_annotation/public/event_annotation_service/README.md index dcc8d87018e34..a7a85d3ab3641 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/README.md +++ b/src/plugins/event_annotation/public/event_annotation_service/README.md @@ -1,3 +1,3 @@ -# Annotation Service +# Event Annotation service -TBD \ No newline at end of file +The Event Annotation service contains expressions for event annotations From 3cca99a16d1ca665a7f4f582e9cbcb2eab9a6143 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Sat, 19 Mar 2022 10:01:51 +0100 Subject: [PATCH 22/47] rotate icon --- .../annotations/expression.scss | 10 ++++ .../annotations/expression.tsx | 20 ++++--- .../xy_visualization/annotations_helpers.tsx | 60 ++++++++++++++----- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss index 659b92b60a2e6..f50f17fb113d3 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss @@ -30,3 +30,13 @@ letter-spacing: -.5px; line-height: 11px; } + +.lnsXyAnnotationIcon_rotate90 { + transform: rotate(90deg); +} +.lnsXyAnnotationIcon_rotate180 { + transform: rotate(180deg); +} +.lnsXyAnnotationIcon_rotate270 { + transform: rotate(270deg); +} diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index 7a8c8f954c5c5..acc54a0949d3c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -15,7 +15,7 @@ import { Position, } from '@elastic/charts'; import type { FieldFormat } from 'src/plugins/field_formats/common'; -import { EventAnnotationArgs } from 'src/plugins/event_annotation/common'; +import type { EventAnnotationArgs } from 'src/plugins/event_annotation/common'; import moment from 'moment'; import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; import type { AnnotationLayerArgs, IconPosition } from '../../../common/expressions'; @@ -27,6 +27,7 @@ import { MarkerBody, Marker, AnnotationIcon, + getIconRotationClass, } from '../annotations_helpers'; const getRoundedTimestamp = (timestamp: number, firstTimestamp?: number, minInterval?: number) => { @@ -81,14 +82,14 @@ const createCustomTooltipDetails = ): AnnotationTooltipFormatter | undefined => () => { return ( -
+
{config.map(({ icon, label, time, color }) => ( -
+
{hasIcon(icon) ? : null} {label} - {formatter?.convert(time) || String(time)} + {formatter?.convert(time) || String(time)}
))}
@@ -162,9 +163,13 @@ export const Annotations = ({ <> {groupedAnnotations.map((annotation) => { const markerPositionVertical = getBaseIconPlacement(annotation.iconPosition); + const markerPosition = isHorizontal + ? mapVerticalToHorizontalPlacement(markerPositionVertical) + : markerPositionVertical; const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE; const id = snakeCase(annotation.label); const { roundedTimestamp } = annotation; + return ( ) : undefined @@ -192,11 +198,7 @@ export const Annotations = ({ /> ) : undefined } - markerPosition={ - isHorizontal - ? mapVerticalToHorizontalPlacement(markerPositionVertical) - : markerPositionVertical - } + markerPosition={markerPosition} dataValues={[ { dataValue: moment( diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx index bc086034344c6..b571d84dfd437 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx @@ -7,7 +7,7 @@ import './expression_reference_lines.scss'; import React from 'react'; -import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText } from '@elastic/eui'; +import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText, IconType } from '@elastic/eui'; import { Position } from '@elastic/charts'; import type { IconPosition, YAxisMode, YConfig } from '../../common/expressions'; import { hasIcon } from './xy_config_panel/shared/icon_select'; @@ -167,17 +167,17 @@ export function MarkerBody({ const isNumericalString = (value: string) => !isNaN(Number(value)); -const shapesIconMap = { - circle: IconCircle, - hexagon: IconHexagon, - triangle: IconTriangle, - square: IconSquare, +const shapes = ['circle', 'hexagon', 'triangle', 'square'] as const; +type Shape = typeof shapes[number]; + +const shapesIconMap: Record = { + triangle: { icon: IconTriangle, shouldRotate: true }, + circle: { icon: IconCircle }, + hexagon: { icon: IconHexagon, shouldRotate: true }, + square: { icon: IconSquare }, }; -const isCustomAnnotationShape = ( - value: string -): value is 'circle' | 'hexagon' | 'triangle' | 'square' => - ['circle', 'hexagon', 'triangle', 'square'].includes(value); +const isCustomAnnotationShape = (value: string): value is Shape => shapes.includes(value as Shape); function NumberIcon({ number }: { number: number }) { return ( @@ -198,14 +198,41 @@ interface MarkerConfig { axisMode?: YAxisMode; icon?: string; textVisibility?: boolean; + iconPosition?: IconPosition; } -export const AnnotationIcon = ({ type, ...rest }: { type: string } & EuiIconProps) => { +export const getIconRotationClass = (markerPosition?: string) => { + if (markerPosition === 'left') { + return 'lnsXyAnnotationIcon_rotate270'; + } + if (markerPosition === 'right') { + return 'lnsXyAnnotationIcon_rotate90'; + } + if (markerPosition === 'bottom') { + return 'lnsXyAnnotationIcon_rotate180'; + } +}; + +export const AnnotationIcon = ({ + type, + rotationClass = '', + isHorizontal, + ...rest +}: { + type: string; + rotationClass?: string; + isHorizontal?: boolean; +} & EuiIconProps) => { if (isNumericalString(type)) { return ; } - const iconType = isCustomAnnotationShape(type) ? shapesIconMap[type] : type; - return ; + const isCustom = isCustomAnnotationShape(type); + if (!isCustom) { + return ; + } + + const rotationClassName = shapesIconMap[type].shouldRotate ? rotationClass : ''; + return ; }; export function Marker({ @@ -213,15 +240,16 @@ export function Marker({ isHorizontal, hasReducedPadding, label, + rotationClass, }: { config: MarkerConfig; isHorizontal: boolean; hasReducedPadding: boolean; label?: string; + rotationClass?: string; }) { - const { icon } = config; - if (hasIcon(icon)) { - return ; + if (hasIcon(config.icon)) { + return ; } // if there's some text, check whether to show it as marker, or just show some padding for the icon From b0504f73ccc9be7391de8ef51feefa20d6bd480a Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Sat, 19 Mar 2022 10:02:31 +0100 Subject: [PATCH 23/47] added tests --- .../__snapshots__/expression.test.tsx.snap | 215 +++++++++++++++++ .../annotations/helpers.test.ts | 10 +- .../xy_visualization/annotations/helpers.tsx | 24 +- .../xy_visualization/expression.test.tsx | 142 +++++++++++- .../public/xy_visualization/expression.tsx | 1 - .../xy_visualization/visualization.test.ts | 217 ++++++++++++++++++ .../public/xy_visualization/visualization.tsx | 2 +- .../xy_visualization/xy_suggestions.test.ts | 56 ++++- 8 files changed, 647 insertions(+), 20 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index b34d5e8639382..eb98ce09e3bb5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -1,5 +1,220 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`xy_expression XYChart component annotations should render basic annotation 1`] = ` + + } + markerBody={ + + } + markerPosition="top" + style={ + Object { + "line": Object { + "dash": undefined, + "opacity": 1, + "stroke": "#f04e98", + "strokeWidth": 1, + }, + } + } +/> +`; + +exports[`xy_expression XYChart component annotations should render grouped annotations preserving the shared styles 1`] = ` + + } + markerBody={ + + } + markerPosition="bottom" + style={ + Object { + "line": Object { + "dash": Array [ + 9, + 3, + ], + "opacity": 1, + "stroke": "red", + "strokeWidth": 3, + }, + } + } +/> +`; + +exports[`xy_expression XYChart component annotations should render grouped annotations with default styles 1`] = ` + + } + markerBody={ + + } + markerPosition="bottom" + style={ + Object { + "line": Object { + "dash": undefined, + "opacity": 1, + "stroke": "#f04e98", + "strokeWidth": 1, + }, + } + } +/> +`; + +exports[`xy_expression XYChart component annotations should render simplified annotation when hide is true 1`] = ` + + } + markerBody={ + + } + markerPosition="top" + style={ + Object { + "line": Object { + "dash": undefined, + "opacity": 1, + "stroke": "#f04e98", + "strokeWidth": 1, + }, + } + } +/> +`; + exports[`xy_expression XYChart component it renders area 1`] = ` { describe('getStaticDate', () => { it('should return `now` value on when nothing is configured', () => { jest.spyOn(Date, 'now').mockReturnValue(new Date('2022-04-08T11:01:58.135Z').valueOf()); - expect(getStaticDate([], undefined)).toBe(1649415718135); + expect(getStaticDate([], undefined)).toBe('2022-04-08T11:01:58.135Z'); }); it('should return `now` value on when there is no active data', () => { expect( @@ -28,7 +28,7 @@ describe('annotations helpers', () => { ], undefined ) - ).toBe(1649415718135); + ).toBe('2022-04-08T11:01:58.135Z'); }); it('should return timestamp value for single active data point', () => { @@ -68,7 +68,7 @@ describe('annotations helpers', () => { ], activeData as FramePublicAPI['activeData'] ) - ).toBe(1646002800000); + ).toBe('2022-02-27T23:00:00.000Z'); }); it('should correctly calculate middle value for active data', () => { @@ -120,7 +120,7 @@ describe('annotations helpers', () => { ], activeData as FramePublicAPI['activeData'] ) - ).toBe(1648270800000); + ).toBe('2022-03-26T05:00:00.000Z'); }); it('should calculate middle date point correctly for multiple layers', () => { @@ -204,7 +204,7 @@ describe('annotations helpers', () => { ], activeData as FramePublicAPI['activeData'] ) - ).toBe(1598270800000); + ).toBe('2020-08-24T12:06:40.000Z'); }); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index 6d4b7a575258d..8e6c9f6484e5d 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -68,12 +68,14 @@ export const getAnnotationsSupportedLayer = ( ) => { const dataLayers = getDataLayers(state?.layers || []); - const hasDateHistogram = dataLayers.every( - (dataLayer) => - dataLayer.xAccessor && - checkScaleOperation('interval', 'date', frame?.datasourceLayers || {})(dataLayer) + const hasDateHistogram = Boolean( + dataLayers.length && + dataLayers.every( + (dataLayer) => + dataLayer.xAccessor && + checkScaleOperation('interval', 'date', frame?.datasourceLayers || {})(dataLayer) + ) ); - const initialDimensions = state && hasDateHistogram ? [ @@ -161,11 +163,15 @@ export const getAnnotationsConfiguration = ({ }) => { const dataLayers = getDataLayers(state.layers); - const hasDateHistogram = dataLayers.every( - (dataLayer) => - dataLayer.xAccessor && - checkScaleOperation('interval', 'date', frame?.datasourceLayers || {})(dataLayer) + const hasDateHistogram = Boolean( + dataLayers.length && + dataLayers.every( + (dataLayer) => + dataLayer.xAccessor && + checkScaleOperation('interval', 'date', frame?.datasourceLayers || {})(dataLayer) + ) ); + return { noDatasource: true, groups: [ diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 41cddda65bf17..b49ca6cd5dafa 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -20,12 +20,13 @@ import { HorizontalAlignment, VerticalAlignment, LayoutDirection, + LineAnnotation, } from '@elastic/charts'; import { PaletteOutput } from 'src/plugins/charts/public'; import { calculateMinInterval, XYChart, XYChartRenderProps } from './expression'; import type { LensMultiTable } from '../../common'; import { layerTypes } from '../../common'; -import { xyChart } from '../../common/expressions'; +import { AnnotationLayerArgs, xyChart } from '../../common/expressions'; import { dataLayerConfig, legendConfig, @@ -41,13 +42,14 @@ import { } from '../../common/expressions'; import { Datatable, DatatableRow } from '../../../../../src/plugins/expressions/public'; import React from 'react'; -import { shallow } from 'enzyme'; +import { mount, shallow } from 'enzyme'; import { createMockExecutionContext } from '../../../../../src/plugins/expressions/common/mocks'; import { mountWithIntl } from '@kbn/test-jest-helpers'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; import { EmptyPlaceholder } from '../../../../../src/plugins/charts/public'; import { XyEndzones } from './x_domain'; -import { eventAnnotationServiceMock } from 'src/plugins/event_annotation/public/mocks'; +import { eventAnnotationServiceMock } from '../../../../../src/plugins/event_annotation/public/mocks'; +import { EventAnnotationOutput } from 'src/plugins/event_annotation/common'; const onClickValue = jest.fn(); const onSelectRange = jest.fn(); @@ -2959,6 +2961,140 @@ describe('xy_expression', () => { }, ]); }); + + describe('annotations', () => { + const sampleStyledAnnotation: EventAnnotationOutput = { + time: '2022-03-18T08:25:00.000Z', + label: 'Event 1', + icon: 'triangle', + iconPosition: 'below', + type: 'manual_event_annotation', + color: 'red', + lineStyle: 'dashed', + lineWidth: 3, + }; + const sampleAnnotationLayers: AnnotationLayerArgs[] = [ + { + layerType: layerTypes.ANNOTATIONS, + layerId: 'annotation', + annotations: [ + { + time: '2022-03-18T08:25:17.140Z', + label: 'Annotation', + type: 'manual_event_annotation', + }, + ], + }, + ]; + function sampleArgsWithAnnotation(annotationLayers = sampleAnnotationLayers) { + const { args } = sampleArgs(); + return { + data: dateHistogramData, + args: { + ...args, + layers: [dateHistogramLayer, ...annotationLayers], + } as XYArgs, + }; + } + test('should render basic annotation', () => { + const { data, args } = sampleArgsWithAnnotation(); + const component = mount(); + expect(component.find('LineAnnotation')).toMatchSnapshot(); + }); + test('should render simplified annotation when hide is true', () => { + const { data, args } = sampleArgsWithAnnotation(); + args.layers[0].hide = true; + const component = mount(); + expect(component.find('LineAnnotation')).toMatchSnapshot(); + }); + + test('should render grouped annotations preserving the shared styles', () => { + const { data, args } = sampleArgsWithAnnotation([ + { + layerType: layerTypes.ANNOTATIONS, + layerId: 'annotation', + annotations: [ + sampleStyledAnnotation, + { ...sampleStyledAnnotation, time: '2022-03-18T08:25:00.020Z', label: 'Event 2' }, + { + ...sampleStyledAnnotation, + time: '2022-03-18T08:25:00.001Z', + label: 'Event 3', + }, + ], + }, + ]); + const component = mount(); + const groupedAnnotation = component.find(LineAnnotation); + + expect(groupedAnnotation.length).toEqual(1); + // styles are passed because they are shared, dataValues & header is rounded to the interval + expect(groupedAnnotation).toMatchSnapshot(); + // renders numeric icon for grouped annotations + const marker = mount(
{groupedAnnotation.prop('marker')}
); + const numberIcon = marker.find('NumberIcon'); + expect(numberIcon.length).toEqual(1); + expect(numberIcon.text()).toEqual('3'); + + // checking tooltip + const renderLinks = mount(
{groupedAnnotation.prop('customTooltipDetails')!()}
); + expect(renderLinks.text()).toEqual( + 'Event 1 2022-03-18T08:25:00.000ZEvent 2 2022-03-18T08:25:00.020ZEvent 3 2022-03-18T08:25:00.001Z' + ); + }); + test('should render grouped annotations with default styles', () => { + const { data, args } = sampleArgsWithAnnotation([ + { + layerType: layerTypes.ANNOTATIONS, + layerId: 'annotation', + annotations: [sampleStyledAnnotation], + }, + { + layerType: layerTypes.ANNOTATIONS, + layerId: 'annotation', + annotations: [ + { + ...sampleStyledAnnotation, + icon: 'square', + color: 'blue', + lineStyle: 'dotted', + lineWidth: 10, + time: '2022-03-18T08:25:00.001Z', + label: 'Event 2', + }, + ], + }, + ]); + const component = mount(); + const groupedAnnotation = component.find(LineAnnotation); + + expect(groupedAnnotation.length).toEqual(1); + // styles are default because they are different for both annotations + expect(groupedAnnotation).toMatchSnapshot(); + }); + test('should not render hidden annotations', () => { + const { data, args } = sampleArgsWithAnnotation([ + { + layerType: layerTypes.ANNOTATIONS, + layerId: 'annotation', + annotations: [ + sampleStyledAnnotation, + { ...sampleStyledAnnotation, time: '2022-03-18T08:30:00.020Z', label: 'Event 2' }, + { + ...sampleStyledAnnotation, + time: '2022-03-18T08:35:00.001Z', + label: 'Event 3', + isHidden: true, + }, + ], + }, + ]); + const component = mount(); + const annotations = component.find(LineAnnotation); + + expect(annotations.length).toEqual(2); + }); + }); }); describe('calculateMinInterval', () => { diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 5193cc2a58f1a..4e3d2ca467714 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -366,7 +366,6 @@ export function XYChart({ const referenceLineLayers = getReferenceLayers(layers); const annotationsLayers = getAnnotationsLayersArgs(layers); - const firstTable = data.tables[filteredLayers[0].layerId]; const xColumnId = firstTable.columns.find((col) => col.id === filteredLayers[0].xAccessor)?.id; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 93f29925ea7dd..07ccbb2d05fa5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -24,6 +24,17 @@ import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_format import { Datatable } from 'src/plugins/expressions'; import { themeServiceMock } from '../../../../../src/core/public/mocks'; import { eventAnnotationServiceMock } from 'src/plugins/event_annotation/public/mocks'; +import { EventAnnotationConfig } from 'src/plugins/event_annotation/common'; + +const exampleAnnotation: EventAnnotationConfig = { + id: 'an1', + label: 'Event 1', + key: { + type: 'point_in_time', + timestamp: '2022-03-18T08:25:17.140Z', + }, + icon: 'circle', +}; function exampleState(): State { return { @@ -235,6 +246,56 @@ describe('xy_visualization', () => { it('should return the icon for the visualization type', () => { expect(xyVisualization.getSupportedLayers()[0].icon).not.toBeUndefined(); }); + describe('annotations', () => { + let mockDatasource: ReturnType; + let frame: ReturnType; + beforeEach(() => { + frame = createMockFramePublicAPI(); + mockDatasource = createMockDatasource('testDatasource'); + + frame.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; + frame.datasourceLayers.first.getOperationForColumnId = jest.fn((accessor) => { + if (accessor === 'a') { + return { + dataType: 'date', + isBucketed: true, + scale: 'interval', + label: 'date_histogram', + isStaticValue: false, + hasTimeShift: false, + }; + } + return null; + }); + + frame.activeData = { + first: { + type: 'datatable', + rows: [], + columns: [], + }, + }; + }); + it('when there is no date histogram annotation layer is disabled', () => { + const supportedAnnotationLayer = xyVisualization + .getSupportedLayers(exampleState()) + .find((a) => a.type === 'annotations'); + expect(supportedAnnotationLayer?.disabled).toBeTruthy(); + }); + it('for data with date histogram annotation layer is enabled and calculates initial dimensions', () => { + const supportedAnnotationLayer = xyVisualization + .getSupportedLayers(exampleState(), frame) + .find((a) => a.type === 'annotations'); + expect(supportedAnnotationLayer?.disabled).toBeFalsy(); + expect(supportedAnnotationLayer?.initialDimensions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ groupId: 'xAnnotations', columnId: expect.any(String) }), + ]) + ); + }); + }); }); describe('#getLayerType', () => { @@ -360,6 +421,45 @@ describe('xy_visualization', () => { ], }); }); + + describe('annotations', () => { + it('should add a dimension to a annotation layer', () => { + jest.spyOn(Date, 'now').mockReturnValue(new Date('2022-04-18T11:01:58.135Z').valueOf()); + expect( + xyVisualization.setDimension({ + frame, + prevState: { + ...exampleState(), + layers: [ + { + layerId: 'annotation', + layerType: layerTypes.ANNOTATIONS, + annotations: [exampleAnnotation], + }, + ], + }, + layerId: 'annotation', + groupId: 'xAnnotation', + columnId: 'newCol', + }).layers[0] + ).toEqual({ + layerId: 'annotation', + layerType: layerTypes.ANNOTATIONS, + annotations: [ + exampleAnnotation, + { + icon: 'triangle', + id: 'newCol', + key: { + timestamp: '2022-04-18T11:01:58.135Z', + type: 'point_in_time', + }, + label: 'Event', + }, + ], + }); + }); + }); }); describe('#updateLayersConfigurationFromContext', () => { @@ -698,6 +798,45 @@ describe('xy_visualization', () => { accessors: [], }); }); + it('removes annotation dimension', () => { + expect( + xyVisualization.removeDimension({ + frame, + prevState: { + ...exampleState(), + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'area', + xAccessor: 'a', + accessors: [], + }, + { + layerId: 'ann', + layerType: layerTypes.ANNOTATIONS, + annotations: [exampleAnnotation, { ...exampleAnnotation, id: 'an2' }], + }, + ], + }, + layerId: 'ann', + columnId: 'an2', + }).layers + ).toEqual([ + { + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'area', + xAccessor: 'a', + accessors: [], + }, + { + layerId: 'ann', + layerType: layerTypes.ANNOTATIONS, + annotations: [exampleAnnotation], + }, + ]); + }); }); describe('#getConfiguration', () => { @@ -1361,6 +1500,84 @@ describe('xy_visualization', () => { }); }); + describe('annotations', () => { + beforeEach(() => { + frame = createMockFramePublicAPI(); + mockDatasource = createMockDatasource('testDatasource'); + + frame.datasourceLayers = { + first: mockDatasource.publicAPIMock, + }; + frame.datasourceLayers.first.getOperationForColumnId = jest.fn((accessor) => { + if (accessor === 'a') { + return { + dataType: 'date', + isBucketed: true, + scale: 'interval', + label: 'date_histogram', + isStaticValue: false, + hasTimeShift: false, + }; + } + return null; + }); + + frame.activeData = { + first: { + type: 'datatable', + rows: [], + columns: [], + }, + }; + }); + + function getStateWithAnnotationLayer(): State { + return { + ...exampleState(), + layers: [ + { + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'area', + splitAccessor: undefined, + xAccessor: 'a', + accessors: ['b'], + }, + { + layerId: 'annotations', + layerType: layerTypes.ANNOTATIONS, + annotations: [exampleAnnotation], + }, + ], + }; + } + + it('returns configuration correctly', () => { + const state = getStateWithAnnotationLayer(); + const config = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'annotations', + }); + expect(config.noDatasource).toBeTruthy(); + expect(config.groups[0].accessors).toEqual([ + { color: '#f04e98', columnId: 'an1', triggerIcon: 'color' }, + ]); + expect(config.groups[0].invalid).toEqual(false); + }); + + it('When data layer is empty, should return invalid state', () => { + const state = getStateWithAnnotationLayer(); + (state.layers[0] as XYDataLayerConfig).xAccessor = undefined; + const config = xyVisualization.getConfiguration({ + state, + frame, + layerId: 'annotations', + }); + expect(config.groups[0].invalid).toEqual(true); + }); + }); + describe('color assignment', () => { function callConfig(layerConfigOverride: Partial) { const baseState = exampleState(); diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 1e76872d456bf..d4f067b9f4d52 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -30,7 +30,7 @@ import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expr import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; import { getColumnToLabelMap } from './state_helpers'; import { - getGroupsAvailableInData, // TODO - see how we should handle this in annotations + getGroupsAvailableInData, getReferenceConfiguration, getReferenceSupportedLayer, setReferenceDimension, diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts index 9b2a8d0118406..2c3483e40b955 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.test.ts @@ -16,7 +16,7 @@ import { PaletteOutput } from 'src/plugins/charts/public'; import { layerTypes } from '../../common'; import { fieldFormatsServiceMock } from '../../../../../src/plugins/field_formats/public/mocks'; import { themeServiceMock } from '../../../../../src/core/public/mocks'; -import { XYDataLayerConfig } from '../../common/expressions'; +import { XYAnnotationLayerConfig, XYDataLayerConfig } from '../../common/expressions'; jest.mock('../id_generator'); @@ -535,6 +535,60 @@ describe('xy_suggestions', () => { ); }); + test('passes annotation layer without modifying it', () => { + const annotationLayer: XYAnnotationLayerConfig = { + layerId: 'second', + layerType: layerTypes.ANNOTATIONS, + annotations: [ + { + id: '1', + key: { + type: 'point_in_time', + timestamp: '2020-20-22', + }, + label: 'annotation', + }, + ], + }; + const currentState: XYState = { + legend: { isVisible: true, position: 'bottom' }, + valueLabels: 'hide', + preferredSeriesType: 'bar', + fittingFunction: 'None', + layers: [ + { + accessors: ['price'], + layerId: 'first', + layerType: layerTypes.DATA, + seriesType: 'bar', + splitAccessor: 'date', + xAccessor: 'product', + }, + annotationLayer, + ], + }; + const suggestions = getSuggestions({ + table: { + isMultiRow: true, + columns: [numCol('price'), dateCol('date'), strCol('product')], + layerId: 'first', + changeType: 'unchanged', + }, + state: currentState, + keptLayerIds: [], + }); + + suggestions.every((suggestion) => + expect(suggestion.state.layers).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + layerType: layerTypes.ANNOTATIONS, + }), + ]) + ) + ); + }); + test('includes passed in palette for split charts if specified', () => { const mainPalette: PaletteOutput = { type: 'palette', name: 'mock' }; const [suggestion] = getSuggestions({ From 91cf292bb9d6bc85ae4c60bf6f7cf4b13b2c2e5a Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Sat, 19 Mar 2022 10:03:23 +0100 Subject: [PATCH 24/47] fix button problem --- .../config_panel/buttons/empty_dimension_button.tsx | 8 +++++--- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx index 543ab841934d2..f3b15cb106488 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx @@ -50,11 +50,13 @@ const defaultButtonLabels = { const noDndButtonLabels = { ariaLabel: (group: VisualizationDimensionGroupConfig) => - i18n.translate('xpack.lens.indexPattern.addColumnAriaLabel', { + i18n.translate('xpack.lens.indexPattern.addColumnAriaLabelClick', { defaultMessage: 'Click to add to {groupLabel}', values: { groupLabel: group.groupLabel }, }), - label: , + label: ( + + ), }; const DefaultEmptyButton = ({ @@ -187,7 +189,7 @@ export function EmptyDimensionButton({ group, }; - if (!dropProps) { + if (!layerDatasource) { buttonProps.labels = noDndButtonLabels; } diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 0843867d79222..4856255d34cd0 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -530,7 +530,6 @@ "xpack.lens.indexPattern.ranges.lessThanTooltip": "Inférieur à", "xpack.lens.indexPattern.records": "Enregistrements", "xpack.lens.indexPattern.referenceFunctionPlaceholder": "Sous-fonction", - "xpack.lens.indexPattern.removeColumnAriaLabel": "Ajouter ou glisser-déposer un champ dans {groupLabel}", "xpack.lens.indexPattern.removeColumnLabel": "Retirer la configuration de \"{groupLabel}\"", "xpack.lens.indexPattern.removeFieldLabel": "Retirer le champ du modèle d'indexation", "xpack.lens.indexPattern.sortField.invalid": "Champ non valide. Vérifiez votre modèle d'indexation ou choisissez un autre champ.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 8b080e3cc8787..b3ed4c0ff45d9 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -611,7 +611,6 @@ "xpack.lens.indexPattern.rareTermsOf": "{name}の希少な値", "xpack.lens.indexPattern.records": "記録", "xpack.lens.indexPattern.referenceFunctionPlaceholder": "サブ関数", - "xpack.lens.indexPattern.removeColumnAriaLabel": "フィールドを追加するか、{groupLabel}までドラッグアンドドロップします", "xpack.lens.indexPattern.removeColumnLabel": "「{groupLabel}」から構成を削除", "xpack.lens.indexPattern.removeFieldLabel": "データビューフィールドを削除", "xpack.lens.indexPattern.sortField.invalid": "無効なフィールドです。データビューを確認するか、別のフィールドを選択してください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 8c7dbd7b9df35..e82cf43f3bab1 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -617,7 +617,6 @@ "xpack.lens.indexPattern.rareTermsOf": "{name} 的稀有值", "xpack.lens.indexPattern.records": "记录", "xpack.lens.indexPattern.referenceFunctionPlaceholder": "子函数", - "xpack.lens.indexPattern.removeColumnAriaLabel": "将字段添加或拖放到 {groupLabel}", "xpack.lens.indexPattern.removeColumnLabel": "从“{groupLabel}”中删除配置", "xpack.lens.indexPattern.removeFieldLabel": "移除数据视图字段", "xpack.lens.indexPattern.sortField.invalid": "字段无效。检查数据视图或选取其他字段。", From 30276fe9f5897f14dc55ba4c273f207804bba088 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Sat, 19 Mar 2022 10:03:52 +0100 Subject: [PATCH 25/47] dnd problem --- .../editor_frame/config_panel/config_panel.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index c3f196c8e285a..9f034d22c69fd 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -27,7 +27,6 @@ import { } from '../../../state_management'; import { AddLayerButton } from './add_layer'; import { getRemoveOperation } from '../../../utils'; -import { layerTypes } from '../../..'; export const ConfigPanelWrapper = memo(function ConfigPanelWrapper(props: ConfigPanelWrapperProps) { const visualization = useLensSelector(selectVisualization); @@ -193,9 +192,7 @@ export function LayerPanels( layersMeta={props.framePublicAPI} onAddLayerClick={(layerType, noDatasource) => { const layerId = generateId(); - dispatchLens( - addLayer({ layerId, layerType, noDatasource: layerType === layerTypes.ANNOTATIONS }) - ); + dispatchLens(addLayer({ layerId, layerType, noDatasource })); trackUiEvent('layer_added'); setNextFocusedLayerId(layerId); }} From 7d0221a2ebd390afb2e35aca8b49d898ec3738bb Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Sun, 20 Mar 2022 19:27:00 +0100 Subject: [PATCH 26/47] dnd fix --- .../editor_frame/config_panel/add_layer.tsx | 28 ++++++++++--------- x-pack/plugins/lens/public/types.ts | 1 + .../xy_visualization/annotations/helpers.tsx | 1 + 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx index 753db499ac2a4..87e93d0577f53 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx @@ -109,19 +109,21 @@ export function AddLayerButton({ title: i18n.translate('xpack.lens.configPanel.selectLayerType', { defaultMessage: 'Select layer type', }), - items: supportedLayers.map(({ type, label, icon, disabled, toolTipContent }) => { - return { - toolTipContent, - disabled, - name: label, - icon: icon && , - ['data-test-subj']: `lnsLayerAddButton-${type}`, - onClick: () => { - onAddLayerClick(type, noDatasource); - toggleLayersChoice(false); - }, - }; - }), + items: supportedLayers.map( + ({ type, label, icon, disabled, toolTipContent, noDatasource }) => { + return { + toolTipContent, + disabled, + name: label, + icon: icon && , + ['data-test-subj']: `lnsLayerAddButton-${type}`, + onClick: () => { + onAddLayerClick(type, noDatasource); + toggleLayersChoice(false); + }, + }; + } + ), }, ]} /> diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 51b482e21fc0f..712485005b71a 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -788,6 +788,7 @@ export interface Visualization { type: LayerType; label: string; icon?: IconType; + noDatasource?: boolean; disabled?: boolean; toolTipContent?: string; initialDimensions?: Array<{ diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index 8e6c9f6484e5d..990aedbd232ee 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -99,6 +99,7 @@ export const getAnnotationsSupportedLayer = ( }) : undefined, initialDimensions, + noDatasource: true, }; }; From 21b32f8d10f688c3105307ac49aa392c9df34915 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 21 Mar 2022 10:03:44 +0100 Subject: [PATCH 27/47] tests for dimension trigger --- .../editor_frame/config_panel/add_layer.tsx | 4 +- .../config_panel/layer_panel.test.tsx | 74 +++++++++++++------ .../editor_frame/config_panel/layer_panel.tsx | 10 +-- .../__snapshots__/expression.test.tsx.snap | 2 + 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx index 87e93d0577f53..880c7d06de208 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx @@ -17,7 +17,6 @@ interface AddLayerButtonProps { visualizationState: unknown; onAddLayerClick: (layerType: LayerType, noDatasource?: boolean) => void; layersMeta: Pick; - noDatasource?: boolean; } export function getLayerType(visualization: Visualization, state: unknown, layerId: string) { @@ -29,7 +28,6 @@ export function AddLayerButton({ visualizationState, onAddLayerClick, layersMeta, - noDatasource, }: AddLayerButtonProps) { const [showLayersChoice, toggleLayersChoice] = useState(false); @@ -65,7 +63,7 @@ export function AddLayerButton({ })} fill color="text" - onClick={() => onAddLayerClick(supportedLayers[0].type, noDatasource)} + onClick={() => onAddLayerClick(supportedLayers[0].type, supportedLayers[0].noDatasource)} iconType="layers" > {i18n.translate('xpack.lens.configPanel.addLayerButton', { diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index a6be4acfbbcf1..356188fc7d62e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -51,11 +51,23 @@ const defaultContext = { registerDropTarget: jest.fn(), }; +const draggingField = { + field: { name: 'dragged' }, + indexPatternId: 'a', + id: '1', + humanData: { label: 'Label' }, + ghost: { + children: , + style: {}, + }, +}; + describe('LayerPanel', () => { let mockVisualization: jest.Mocked; let mockVisualization2: jest.Mocked; let mockDatasource: DatasourceMock; + mockDatasource = createMockDatasource('testDatasource'); let frame: FramePublicAPI; function getDefaultProps() { @@ -611,17 +623,6 @@ describe('LayerPanel', () => { nextLabel: '', }); - const draggingField = { - field: { name: 'dragged' }, - indexPatternId: 'a', - id: '1', - humanData: { label: 'Label' }, - ghost: { - children: , - style: {}, - }, - }; - const { instance } = await mountWithProvider( @@ -666,17 +667,6 @@ describe('LayerPanel', () => { columnId !== 'a' ? { dropTypes: ['field_replace'], nextLabel: '' } : undefined ); - const draggingField = { - field: { name: 'dragged' }, - indexPatternId: 'a', - id: '1', - humanData: { label: 'Label' }, - ghost: { - children: , - style: {}, - }, - }; - const { instance } = await mountWithProvider( @@ -985,4 +975,44 @@ describe('LayerPanel', () => { ); }); }); + describe('dimension trigger', () => { + it('should render datasource dimension trigger if there is layer datasource', async () => { + mockVisualization.getConfiguration.mockReturnValue({ + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [{ columnId: 'x' }], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + ], + }); + await mountWithProvider(); + expect(mockDatasource.renderDimensionTrigger).toHaveBeenCalled(); + }); + + it('should render visualization dimension trigger if there is no layer datasource', async () => { + mockVisualization.getConfiguration.mockReturnValue({ + noDatasource: true, + groups: [ + { + groupLabel: 'A', + groupId: 'a', + accessors: [{ columnId: 'x' }], + filterOperations: () => true, + supportsMoreColumns: false, + dataTestSubj: 'lnsGroup', + }, + ], + }); + + mockVisualization.renderDimensionTrigger = jest.fn(); + + await mountWithProvider(); + expect(mockDatasource.renderDimensionTrigger).not.toHaveBeenCalled(); + expect(mockVisualization.renderDimensionTrigger).toHaveBeenCalled(); + }); + }); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index b3b937b09fd41..1b8036dcf607f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -81,7 +81,6 @@ export function LayerPanel( updateDatasourceAsync, visualizationState, } = props; - const datasourcePublicAPI = framePublicAPI.datasourceLayers?.[layerId]; const datasourceStates = useLensSelector(selectDatasourceStates); const isFullscreen = useLensSelector(selectIsFullscreenDatasource); const dateRange = useLensSelector(selectResolvedDateRange); @@ -104,8 +103,10 @@ export function LayerPanel( activeData: props.framePublicAPI.activeData, }; + const datasourcePublicAPI = framePublicAPI.datasourceLayers?.[layerId]; const datasourceId = datasourcePublicAPI?.datasourceId; const layerDatasourceState = datasourceStates?.[datasourceId]?.state; + const layerDatasource = props.datasourceMap[datasourceId]; const layerDatasourceDropProps = useMemo( () => ({ @@ -118,13 +119,12 @@ export function LayerPanel( [layerId, layerDatasourceState, datasourceId, updateDatasource] ); - const layerDatasource = props.datasourceMap[datasourceId]; - const layerDatasourceConfigProps = { ...layerDatasourceDropProps, frame: props.framePublicAPI, dateRange, }; + const columnLabelMap = layerDatasource?.uniqueLabels?.(layerDatasourceConfigProps?.state); const { groups, noDatasource } = useMemo( () => activeVisualization.getConfiguration(layerVisualizationConfigProps), @@ -139,8 +139,6 @@ export function LayerPanel( const isEmptyLayer = !groups.some((d) => d.accessors.length > 0); const { activeId, activeGroup } = activeDimension; - const columnLabelMap = layerDatasource?.uniqueLabels?.(layerDatasourceConfigProps?.state); - const { setDimension, removeDimension } = activeVisualization; const allAccessors = groups.flatMap((group) => @@ -403,7 +401,6 @@ export function LayerPanel( : i18n.translate('xpack.lens.editorFrame.requiresFieldWarningLabel', { defaultMessage: 'Requires field', }); - const isOptional = !group.required && !group.suggestedValue; return ( {group.accessors.map((accessorConfig, accessorIndex) => { const { columnId } = accessorConfig; - return ( } markerBody={ @@ -145,6 +146,7 @@ exports[`xy_expression XYChart component annotations should render grouped annot hasReducedPadding={true} isHorizontal={true} label="" + rotationClass="lnsXyAnnotationIcon_rotate180" /> } markerBody={ From 0e3bd111b9d1ac8397bdc5f51acbbefd3fb7a1f4 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 21 Mar 2022 10:40:40 +0100 Subject: [PATCH 28/47] tests for unique labels --- .../config_panel/layer_panel.test.tsx | 1 + .../editor_frame/config_panel/layer_panel.tsx | 10 ++- x-pack/plugins/lens/public/types.ts | 9 +- .../xy_visualization/visualization.test.ts | 85 ++++++++++++++++++- .../public/xy_visualization/visualization.tsx | 14 +-- 5 files changed, 105 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 356188fc7d62e..71cf62da29a4d 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -20,6 +20,7 @@ import { DatasourceMock, mountWithProvider, } from '../../../mocks'; +import { getUniqueLabels } from '../../../xy_visualization/annotations/helpers'; jest.mock('../../../id_generator'); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 1b8036dcf607f..9c6feebc9632b 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -124,7 +124,6 @@ export function LayerPanel( frame: props.framePublicAPI, dateRange, }; - const columnLabelMap = layerDatasource?.uniqueLabels?.(layerDatasourceConfigProps?.state); const { groups, noDatasource } = useMemo( () => activeVisualization.getConfiguration(layerVisualizationConfigProps), @@ -136,6 +135,12 @@ export function LayerPanel( activeVisualization, ] ); + + const columnLabelMap = + noDatasource && activeVisualization.getUniqueLabels + ? activeVisualization.getUniqueLabels(props.visualizationState) + : layerDatasource?.uniqueLabels?.(layerDatasourceConfigProps?.state); + const isEmptyLayer = !groups.some((d) => d.accessors.length > 0); const { activeId, activeGroup } = activeDimension; @@ -516,8 +521,7 @@ export function LayerPanel( <> {activeVisualization?.renderDimensionTrigger?.({ columnId, - layerId, - state: props.visualizationState, + label: columnLabelMap[columnId], hideTooltip, invalid: group.invalid, invalidMessage: group.invalidMessage, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 712485005b71a..f9d1730a03ee3 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -861,16 +861,19 @@ export interface Visualization { props: VisualizationDimensionEditorProps ) => ((cleanupElement: Element) => void) | void; /** - * TODO: used only for vis-only annotations + * Renders dimension trigger. Used only for noDatasource layers */ renderDimensionTrigger?: (props: { - layerId: string; columnId: string; - state: T; + label: string; hideTooltip?: boolean; invalid?: boolean; invalidMessage?: string; }) => JSX.Element | null; + /** + * Creates map of columns ids and unique lables. Used only for noDatasource layers + */ + getUniqueLabels?: (state: T) => Record; /** * The frame will call this function on all visualizations at different times. The * main use cases where visualization suggestions are requested are: diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index 07ccbb2d05fa5..abaddeec4213a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -8,7 +8,7 @@ import { getXyVisualization } from './visualization'; import { Position } from '@elastic/charts'; import { Operation, VisualizeEditorContext, Suggestion, OperationDescriptor } from '../types'; -import type { State, XYSuggestion } from './types'; +import type { State, XYState, XYSuggestion } from './types'; import type { SeriesType, XYDataLayerConfig, @@ -2174,4 +2174,87 @@ describe('xy_visualization', () => { `); }); }); + describe('#getUniqueLabels', () => { + it('creates unique labels for single annotations layer with repeating labels', async () => { + const xyState = { + layers: [ + { + layerId: 'layerId', + layerType: 'annotations', + annotations: [ + { + label: 'Event', + id: '1', + }, + { + label: 'Event', + id: '2', + }, + { + label: 'Custom', + id: '3', + }, + ], + }, + ], + } as XYState; + + expect(xyVisualization.getUniqueLabels!(xyState)).toEqual({ + '1': 'Event', + '2': 'Event [1]', + '3': 'Custom', + }); + }); + it('creates unique labels for multiple annotations layers with repeating labels', async () => { + const xyState = { + layers: [ + { + layerId: 'layer1', + layerType: 'annotations', + annotations: [ + { + label: 'Event', + id: '1', + }, + { + label: 'Event', + id: '2', + }, + { + label: 'Custom', + id: '3', + }, + ], + }, + { + layerId: 'layer2', + layerType: 'annotations', + annotations: [ + { + label: 'Event', + id: '4', + }, + { + label: 'Event [1]', + id: '5', + }, + { + label: 'Custom', + id: '6', + }, + ], + }, + ], + } as XYState; + + expect(xyVisualization.getUniqueLabels!(xyState)).toEqual({ + '1': 'Event', + '2': 'Event [1]', + '3': 'Custom', + '4': 'Event [2]', + '5': 'Event [1] [1]', + '6': 'Custom [1]', + }); + }); + }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index d4f067b9f4d52..4767c067e2c6c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -610,30 +610,30 @@ export const getXyVisualization = ({ /> )); }, + getUniqueLabels(state) { + return getUniqueLabels(state.layers); + }, renderDimensionTrigger({ columnId, - layerId, - state, + label, hideTooltip, invalid, invalidMessage, }: { columnId: string; - layerId: string; - state: XYState; + label?: string; hideTooltip?: boolean; invalid?: boolean; invalidMessage?: string; }) { - const layer = state.layers.find((l) => l.layerId === layerId); - if (layer && isAnnotationsLayer(layer)) { + if (label) { return ( ); } From 9b5c0347bea705cd33f06e5d6450ed386f19907b Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 21 Mar 2022 09:10:35 +0000 Subject: [PATCH 29/47] [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' --- docs/developer/plugin-list.asciidoc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index bf81ab1e0bec4..aefaf4eab40fa 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -94,6 +94,10 @@ This API doesn't support angular, for registering angular dev tools, bootstrap a |This plugin contains reusable code in the form of self-contained modules (or libraries). Each of these modules exports a set of functionality relevant to the domain of the module. +|{kib-repo}blob/{branch}/src/plugins/event_annotation/README.md[eventAnnotation] +|The Event Annotation service contains expressions for event annotations + + |{kib-repo}blob/{branch}/src/plugins/expression_error/README.md[expressionError] |Expression Error plugin adds an error renderer to the expression plugin. The renderer will display the error image. From 471d207f3729ade6a0dd94c2828184955e8b85f2 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 21 Mar 2022 10:50:43 +0100 Subject: [PATCH 30/47] type --- .../editor_frame/config_panel/layer_panel.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 71cf62da29a4d..356188fc7d62e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -20,7 +20,6 @@ import { DatasourceMock, mountWithProvider, } from '../../../mocks'; -import { getUniqueLabels } from '../../../xy_visualization/annotations/helpers'; jest.mock('../../../id_generator'); From 8a201b9ec35cec72e1279333de3a42f4c6c0429c Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 21 Mar 2022 11:08:23 +0100 Subject: [PATCH 31/47] add new button test --- .../config_panel/config_panel.test.tsx | 65 ++++++++++++++++++- .../public/state_management/lens_slice.ts | 4 +- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx index 9ba47804230f5..b234b18f5262f 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.test.tsx @@ -20,7 +20,7 @@ import { LayerPanel } from './layer_panel'; import { coreMock } from 'src/core/public/mocks'; import { generateId } from '../../../id_generator'; import { mountWithProvider } from '../../../mocks'; -import { layerTypes } from '../../../../common'; +import { LayerType, layerTypes } from '../../../../common'; import { ReactWrapper } from 'enzyme'; import { addLayer } from '../../../state_management'; @@ -231,14 +231,17 @@ describe('ConfigPanel', () => { }); describe('initial default value', () => { - function clickToAddLayer(instance: ReactWrapper) { + function clickToAddLayer( + instance: ReactWrapper, + layerType: LayerType = layerTypes.REFERENCELINE + ) { act(() => { instance.find('[data-test-subj="lnsLayerAddButton"]').first().simulate('click'); }); instance.update(); act(() => { instance - .find(`[data-test-subj="lnsLayerAddButton-${layerTypes.REFERENCELINE}"]`) + .find(`[data-test-subj="lnsLayerAddButton-${layerType}"]`) .first() .simulate('click'); }); @@ -370,5 +373,61 @@ describe('ConfigPanel', () => { } ); }); + + it('When visualization is `noDatasource` should not run datasource methods', async () => { + const datasourceMap = mockDatasourceMap(); + + const visualizationMap = mockVisualizationMap(); + visualizationMap.testVis.setDimension = jest.fn(); + visualizationMap.testVis.getSupportedLayers = jest.fn(() => [ + { + type: layerTypes.DATA, + label: 'Data Layer', + initialDimensions: [ + { + groupId: 'testGroup', + columnId: 'myColumn', + staticValue: 100, + }, + ], + }, + { + type: layerTypes.REFERENCELINE, + label: 'Reference layer', + }, + { + type: layerTypes.ANNOTATIONS, + label: 'Annotations Layer', + noDatasource: true, + initialDimensions: [ + { + groupId: 'a', + columnId: 'newId', + staticValue: 100, + }, + ], + }, + ]); + + datasourceMap.testDatasource.initializeDimension = jest.fn(); + const props = getDefaultProps({ visualizationMap, datasourceMap }); + const { instance, lensStore } = await prepareAndMountComponent(props); + await clickToAddLayer(instance, layerTypes.ANNOTATIONS); + expect(lensStore.dispatch).toHaveBeenCalledTimes(1); + + expect(visualizationMap.testVis.setDimension).toHaveBeenCalledWith({ + columnId: 'newId', + frame: { + activeData: undefined, + datasourceLayers: { + a: expect.anything(), + }, + }, + groupId: 'a', + layerId: 'newId', + prevState: undefined, + }); + expect(datasourceMap.testDatasource.initializeDimension).not.toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 506d8f1c79df4..66bb449535caf 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -724,7 +724,7 @@ function addInitialValueIfAvailable({ .find(({ type }) => type === layerType); if ( - layerType !== 'annotations' && + !layerInfo?.noDatasource && layerInfo?.initialDimensions && activeDatasource?.initializeDimension ) { @@ -749,7 +749,7 @@ function addInitialValueIfAvailable({ }; } } - if (layerType === 'annotations' && layerInfo?.initialDimensions) { + if (layerInfo?.noDatasource && layerInfo?.initialDimensions) { const info = groupId ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) : // pick the first available one if not passed From 2a96147468ee485dca0a503b1dd330b8e196ee5d Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Mon, 21 Mar 2022 17:06:40 +0100 Subject: [PATCH 32/47] remove noDatasource from config (only needed when initializing a layer or dimension in getSupportedLayers) --- .../editor_frame/config_panel/add_layer.tsx | 32 +++-- .../config_panel/config_panel.tsx | 4 +- .../config_panel/layer_panel.test.tsx | 12 +- .../editor_frame/config_panel/layer_panel.tsx | 40 +++---- .../public/state_management/lens_slice.ts | 110 +++++++++--------- x-pack/plugins/lens/public/types.ts | 3 - .../xy_visualization/annotations/helpers.tsx | 1 - .../xy_visualization/visualization.test.ts | 2 +- 8 files changed, 101 insertions(+), 103 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx index 880c7d06de208..0e4340300f8b2 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/add_layer.tsx @@ -15,7 +15,7 @@ import type { FramePublicAPI, Visualization } from '../../../types'; interface AddLayerButtonProps { visualization: Visualization; visualizationState: unknown; - onAddLayerClick: (layerType: LayerType, noDatasource?: boolean) => void; + onAddLayerClick: (layerType: LayerType) => void; layersMeta: Pick; } @@ -63,7 +63,7 @@ export function AddLayerButton({ })} fill color="text" - onClick={() => onAddLayerClick(supportedLayers[0].type, supportedLayers[0].noDatasource)} + onClick={() => onAddLayerClick(supportedLayers[0].type)} iconType="layers" > {i18n.translate('xpack.lens.configPanel.addLayerButton', { @@ -107,21 +107,19 @@ export function AddLayerButton({ title: i18n.translate('xpack.lens.configPanel.selectLayerType', { defaultMessage: 'Select layer type', }), - items: supportedLayers.map( - ({ type, label, icon, disabled, toolTipContent, noDatasource }) => { - return { - toolTipContent, - disabled, - name: label, - icon: icon && , - ['data-test-subj']: `lnsLayerAddButton-${type}`, - onClick: () => { - onAddLayerClick(type, noDatasource); - toggleLayersChoice(false); - }, - }; - } - ), + items: supportedLayers.map(({ type, label, icon, disabled, toolTipContent }) => { + return { + toolTipContent, + disabled, + name: label, + icon: icon && , + ['data-test-subj']: `lnsLayerAddButton-${type}`, + onClick: () => { + onAddLayerClick(type); + toggleLayersChoice(false); + }, + }; + }), }, ]} /> diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx index 9f034d22c69fd..163d1b8ce8e61 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/config_panel.tsx @@ -190,9 +190,9 @@ export function LayerPanels( visualization={activeVisualization} visualizationState={visualization.state} layersMeta={props.framePublicAPI} - onAddLayerClick={(layerType, noDatasource) => { + onAddLayerClick={(layerType) => { const layerId = generateId(); - dispatchLens(addLayer({ layerId, layerType, noDatasource })); + dispatchLens(addLayer({ layerId, layerType })); trackUiEvent('layer_added'); setNextFocusedLayerId(layerId); }} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx index 356188fc7d62e..0f28e54df17d9 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.test.tsx @@ -995,7 +995,6 @@ describe('LayerPanel', () => { it('should render visualization dimension trigger if there is no layer datasource', async () => { mockVisualization.getConfiguration.mockReturnValue({ - noDatasource: true, groups: [ { groupLabel: 'A', @@ -1008,9 +1007,18 @@ describe('LayerPanel', () => { ], }); + const props = getDefaultProps(); + const propsWithVisOnlyLayer = { + ...props, + framePublicAPI: { ...props.framePublicAPI, datasourceLayers: {} }, + }; + mockVisualization.renderDimensionTrigger = jest.fn(); + mockVisualization.getUniqueLabels = jest.fn(() => ({ + x: 'A', + })); - await mountWithProvider(); + await mountWithProvider(); expect(mockDatasource.renderDimensionTrigger).not.toHaveBeenCalled(); expect(mockVisualization.renderDimensionTrigger).toHaveBeenCalled(); }); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 9c6feebc9632b..3e738a7b3afc6 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -125,7 +125,7 @@ export function LayerPanel( dateRange, }; - const { groups, noDatasource } = useMemo( + const { groups } = useMemo( () => activeVisualization.getConfiguration(layerVisualizationConfigProps), // eslint-disable-next-line react-hooks/exhaustive-deps [ @@ -137,7 +137,7 @@ export function LayerPanel( ); const columnLabelMap = - noDatasource && activeVisualization.getUniqueLabels + !layerDatasource && activeVisualization.getUniqueLabels ? activeVisualization.getUniqueLabels(props.visualizationState) : layerDatasource?.uniqueLabels?.(layerDatasourceConfigProps?.state); @@ -182,7 +182,7 @@ export function LayerPanel( const filterOperations = group?.filterOperations || (() => false); - const dropResult = !noDatasource + const dropResult = layerDatasource ? layerDatasourceOnDrop({ ...layerDatasourceDropProps, droppedItem, @@ -245,7 +245,7 @@ export function LayerPanel( removeDimension, layerDatasourceDropProps, setNextFocusedButtonId, - noDatasource, + layerDatasource, ]); const isDimensionPanelOpen = Boolean(activeId); @@ -346,7 +346,7 @@ export function LayerPanel( - {layerDatasource && !noDatasource && ( + {layerDatasource && ( - {noDatasource ? ( - <> - {activeVisualization?.renderDimensionTrigger?.({ - columnId, - label: columnLabelMap[columnId], - hideTooltip, - invalid: group.invalid, - invalidMessage: group.invalidMessage, - })} - - ) : ( + {layerDatasource ? ( + ) : ( + <> + {activeVisualization?.renderDimensionTrigger?.({ + columnId, + label: columnLabelMap[columnId], + hideTooltip, + invalid: group.invalid, + invalidMessage: group.invalidMessage, + })} + )}
@@ -563,7 +563,7 @@ export function LayerPanel( setActiveDimension({ activeGroup: group, activeId: id, - isNew: !group.supportStaticValue && !noDatasource, + isNew: !group.supportStaticValue && Boolean(layerDatasource), }); }} onDrop={onDrop} @@ -609,7 +609,7 @@ export function LayerPanel( }} panel={
- {activeGroup && activeId && !noDatasource && ( + {activeGroup && activeId && layerDatasource && ( diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 66bb449535caf..935c60a9a7c84 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -154,7 +154,6 @@ export const removeOrClearLayer = createAction<{ export const addLayer = createAction<{ layerId: string; layerType: LayerType; - noDatasource?: boolean; }>('lens/addLayer'); export const setLayerDefaultDimension = createAction<{ @@ -608,12 +607,11 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { [addLayer.type]: ( state, { - payload: { layerId, layerType, noDatasource }, + payload: { layerId, layerType }, }: { payload: { layerId: string; layerType: LayerType; - noDatasource?: boolean; }; } ) => { @@ -628,23 +626,32 @@ export const makeLensReducer = (storeDeps: LensStoreDeps) => { layerType ); - const activeDatasource = noDatasource ? undefined : datasourceMap[state.activeDatasourceId]; + const framePublicAPI = { + // any better idea to avoid `as`? + activeData: state.activeData + ? (current(state.activeData) as TableInspectorAdapter) + : undefined, + datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), + }; - const { activeDatasourceState, activeVisualizationState } = addInitialValueIfAvailable({ - datasourceState: activeDatasource + const activeDatasource = datasourceMap[state.activeDatasourceId]; + const { noDatasource } = + activeVisualization + .getSupportedLayers(visualizationState, framePublicAPI) + .find(({ type }) => type === layerType) || {}; + + const datasourceState = + !noDatasource && activeDatasource ? activeDatasource.insertLayer( state.datasourceStates[state.activeDatasourceId].state, layerId ) - : state.datasourceStates[state.activeDatasourceId].state, + : state.datasourceStates[state.activeDatasourceId].state; + + const { activeDatasourceState, activeVisualizationState } = addInitialValueIfAvailable({ + datasourceState, visualizationState, - framePublicAPI: { - // any better idea to avoid `as`? - activeData: state.activeData - ? (current(state.activeData) as TableInspectorAdapter) - : undefined, - datasourceLayers: getDatasourceLayers(state.datasourceStates, datasourceMap), - }, + framePublicAPI, activeVisualization, activeDatasource, layerId, @@ -719,55 +726,44 @@ function addInitialValueIfAvailable({ columnId?: string; groupId?: string; }) { - const layerInfo = activeVisualization - .getSupportedLayers(visualizationState, framePublicAPI) - .find(({ type }) => type === layerType); - - if ( - !layerInfo?.noDatasource && - layerInfo?.initialDimensions && - activeDatasource?.initializeDimension - ) { - const info = groupId - ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) - : // pick the first available one if not passed - layerInfo.initialDimensions[0]; + const { initialDimensions, noDatasource } = + activeVisualization + .getSupportedLayers(visualizationState, framePublicAPI) + .find(({ type }) => type === layerType) || {}; - if (info) { - return { - activeDatasourceState: activeDatasource.initializeDimension(datasourceState, layerId, { - ...info, - columnId: columnId || info.columnId, - }), - activeVisualizationState: activeVisualization.setDimension({ - groupId: info.groupId, - layerId, - columnId: columnId || info.columnId, - prevState: visualizationState, - frame: framePublicAPI, - }), - }; - } - } - if (layerInfo?.noDatasource && layerInfo?.initialDimensions) { + if (initialDimensions) { const info = groupId - ? layerInfo.initialDimensions.find(({ groupId: id }) => id === groupId) - : // pick the first available one if not passed - layerInfo.initialDimensions[0]; + ? initialDimensions.find(({ groupId: id }) => id === groupId) + : initialDimensions[0]; // pick the first available one if not passed if (info) { - return { - activeDatasourceState: datasourceState, - activeVisualizationState: activeVisualization.setDimension({ - groupId: info.groupId, - layerId, - columnId: columnId || info.columnId, - prevState: visualizationState, - frame: framePublicAPI, - }), - }; + const activeVisualizationState = activeVisualization.setDimension({ + groupId: info.groupId, + layerId, + columnId: columnId || info.columnId, + prevState: visualizationState, + frame: framePublicAPI, + }); + + if (!noDatasource && activeDatasource?.initializeDimension) { + return { + activeDatasourceState: activeDatasource.initializeDimension(datasourceState, layerId, { + ...info, + columnId: columnId || info.columnId, + }), + activeVisualizationState, + }; + } + + if (noDatasource) { + return { + activeDatasourceState: datasourceState, + activeVisualizationState, + }; + } } } + return { activeDatasourceState: datasourceState, activeVisualizationState: visualizationState, diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index f9d1730a03ee3..941cc7866676a 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -433,7 +433,6 @@ export type DatasourceDimensionEditorProps = DatasourceDimensionPro isFullscreen: boolean; layerType: LayerType | undefined; supportStaticValue: boolean; - noDatasource?: boolean; paramEditorCustomProps?: ParamEditorCustomProps; supportFieldFormat?: boolean; }; @@ -584,7 +583,6 @@ export type VisualizationDimensionGroupConfig = SharedDimensionProps & { // need a special flag to know when to pass the previous column on duplicating requiresPreviousColumnOnDuplicate?: boolean; supportStaticValue?: boolean; - noDatasource?: boolean; paramEditorCustomProps?: ParamEditorCustomProps; supportFieldFormat?: boolean; }; @@ -805,7 +803,6 @@ export interface Visualization { * For consistency across different visualizations, the dimension configuration UI is standardized */ getConfiguration: (props: VisualizationConfigProps) => { - noDatasource?: boolean; groups: VisualizationDimensionGroupConfig[]; }; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index 990aedbd232ee..2d4f34079e6b2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -174,7 +174,6 @@ export const getAnnotationsConfiguration = ({ ); return { - noDatasource: true, groups: [ { groupId: 'xAnnotations', diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index abaddeec4213a..b93cf317e1b2f 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -289,6 +289,7 @@ describe('xy_visualization', () => { .getSupportedLayers(exampleState(), frame) .find((a) => a.type === 'annotations'); expect(supportedAnnotationLayer?.disabled).toBeFalsy(); + expect(supportedAnnotationLayer?.noDatasource).toBeTruthy(); expect(supportedAnnotationLayer?.initialDimensions).toEqual( expect.arrayContaining([ expect.objectContaining({ groupId: 'xAnnotations', columnId: expect.any(String) }), @@ -1559,7 +1560,6 @@ describe('xy_visualization', () => { frame, layerId: 'annotations', }); - expect(config.noDatasource).toBeTruthy(); expect(config.groups[0].accessors).toEqual([ { color: '#f04e98', columnId: 'an1', triggerIcon: 'color' }, ]); From a87c8a3c742b3dc78c33a09cfcd3f1942b88c52f Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 22 Mar 2022 10:40:50 +0100 Subject: [PATCH 33/47] addressing Joe's and Michael comments --- .../event_annotation_service/service.tsx | 3 - .../components/dimension_editor.tsx | 5 +- .../buttons/empty_dimension_button.tsx | 8 +- .../config_panel/layer_panel.scss | 4 - .../editor_frame/config_panel/layer_panel.tsx | 93 ++++++------ .../dimension_editor.tsx | 137 +++++++++--------- x-pack/plugins/lens/public/index.ts | 1 + .../metric_visualization/dimension_editor.tsx | 5 +- .../lens/public/pie_visualization/toolbar.tsx | 13 +- .../shared_components/dimension_section.scss | 8 + .../shared_components/dimension_section.tsx | 28 ++++ .../lens/public/shared_components/index.ts | 1 + .../public/state_management/lens_slice.ts | 4 +- .../visualizations/gauge/dimension_editor.tsx | 5 +- .../__snapshots__/expression.test.tsx.snap | 4 +- .../annotations/config_panel/icon_set.ts | 73 ++++++++-- .../annotations/config_panel/index.scss | 3 + .../annotations/config_panel/index.tsx | 123 +++++++++------- .../annotations/expression.tsx | 27 ++-- .../xy_visualization/expression.test.tsx | 2 +- .../public/xy_visualization/visualization.tsx | 1 - .../xy_config_panel/dimension_editor.tsx | 10 +- .../xy_config_panel/reference_line_panel.tsx | 6 +- .../shared/line_style_settings.tsx | 2 +- .../choropleth_chart/region_key_editor.tsx | 5 +- 25 files changed, 349 insertions(+), 222 deletions(-) create mode 100644 x-pack/plugins/lens/public/shared_components/dimension_section.scss create mode 100644 x-pack/plugins/lens/public/shared_components/dimension_section.tsx create mode 100644 x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.scss diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index 2d1aa201146bf..b5e125e45f773 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -6,9 +6,6 @@ * Side Public License, v 1. */ -// @ts-ignore -import chroma from 'chroma-js'; - import { EventAnnotationServiceType } from './types'; import { defaultAnnotationColor } from '..'; diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index fc0fa9b5d8ac6..559712697700e 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -30,6 +30,7 @@ import { useDebouncedValue, PalettePanelContainer, findMinMaxByColumnId, + DimensionEditorSection, } from '../../shared_components/'; import type { ColumnState } from '../../../common/expressions'; import { @@ -128,7 +129,7 @@ export function TableDimensionEditor( const displayStops = applyPaletteParams(props.paletteService, activePalette, currentMinMax); return ( - <> + )} - + ); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx index f3b15cb106488..2201ca2511fa3 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx @@ -48,14 +48,18 @@ const defaultButtonLabels = { ), }; +// todo: come back to it to make the name flexible const noDndButtonLabels = { ariaLabel: (group: VisualizationDimensionGroupConfig) => i18n.translate('xpack.lens.indexPattern.addColumnAriaLabelClick', { - defaultMessage: 'Click to add to {groupLabel}', + defaultMessage: 'Add an annotation to {groupLabel}', values: { groupLabel: group.groupLabel }, }), label: ( - + ), }; diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss index 343cd746ba2ac..3458232181439 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss @@ -117,10 +117,6 @@ color: $euiTextSubduedColor; } -.lnsLayerPanel__styleEditor { - padding: $euiSize; -} - .lnsLayerPanel__colorIndicator { margin-left: $euiSizeS; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 3e738a7b3afc6..8f73dda517e12 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -81,6 +81,7 @@ export function LayerPanel( updateDatasourceAsync, visualizationState, } = props; + const datasourceStates = useLensSelector(selectDatasourceStates); const isFullscreen = useLensSelector(selectIsFullscreenDatasource); const dateRange = useLensSelector(selectResolvedDateRange); @@ -193,7 +194,7 @@ export function LayerPanel( groupId, dropType, }) - : true; + : false; if (dropResult) { let previousColumn = typeof droppedItem.column === 'string' ? droppedItem.column : undefined; @@ -345,43 +346,45 @@ export function LayerPanel( /> - {layerDatasource && ( - { - const newState = - typeof updater === 'function' ? updater(layerDatasourceState) : updater; - // Look for removed columns - const nextPublicAPI = layerDatasource.getPublicAPI({ - state: newState, - layerId, - }); - const nextTable = new Set( - nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) - ); - const removed = datasourcePublicAPI - .getTableSpec() - .map(({ columnId }) => columnId) - .filter((columnId) => !nextTable.has(columnId)); - let nextVisState = props.visualizationState; - removed.forEach((columnId) => { - nextVisState = activeVisualization.removeDimension({ + <> + + { + const newState = + typeof updater === 'function' ? updater(layerDatasourceState) : updater; + // Look for removed columns + const nextPublicAPI = layerDatasource.getPublicAPI({ + state: newState, layerId, - columnId, - prevState: nextVisState, - frame: framePublicAPI, }); - }); + const nextTable = new Set( + nextPublicAPI.getTableSpec().map(({ columnId }) => columnId) + ); + const removed = datasourcePublicAPI + .getTableSpec() + .map(({ columnId }) => columnId) + .filter((columnId) => !nextTable.has(columnId)); + let nextVisState = props.visualizationState; + removed.forEach((columnId) => { + nextVisState = activeVisualization.removeDimension({ + layerId, + columnId, + prevState: nextVisState, + frame: framePublicAPI, + }); + }); - props.updateAll(datasourceId, newState, nextVisState); - }, - }} - /> + props.updateAll(datasourceId, newState, nextVisState); + }, + }} + /> + )} @@ -636,18 +639,16 @@ export function LayerPanel( !activeDimension.isNew && activeVisualization.renderDimensionEditor && activeGroup?.enableDimensionEditor && ( -
- -
+ )}
} diff --git a/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx b/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx index 78f10056ac60c..6a4cf91c47fd4 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx @@ -20,6 +20,7 @@ import { CustomizablePalette, FIXED_PROGRESSION, PalettePanelContainer, + DimensionEditorSection, } from '../shared_components/'; import './dimension_editor.scss'; import type { HeatmapVisualizationState } from './types'; @@ -46,73 +47,75 @@ export function HeatmapDimensionEditor( ); return ( - - + - - color)} - type={FIXED_PROGRESSION} - onClick={() => { - setIsPaletteOpen(!isPaletteOpen); - }} - /> - - - { - setIsPaletteOpen(!isPaletteOpen); - }} - size="xs" - flush="both" - > - {i18n.translate('xpack.lens.paletteHeatmapGradient.customize', { - defaultMessage: 'Edit', - })} - - setIsPaletteOpen(!isPaletteOpen)} - > - {activePalette && ( - { - // make sure to always have a list of stops - if (newPalette.params && !newPalette.params.stops) { - newPalette.params.stops = displayStops; - } - (newPalette as HeatmapVisualizationState['palette'])!.accessor = accessor; - setState({ - ...state, - palette: newPalette as HeatmapVisualizationState['palette'], - }); - }} - /> - )} - - - - + + + color)} + type={FIXED_PROGRESSION} + onClick={() => { + setIsPaletteOpen(!isPaletteOpen); + }} + /> + + + { + setIsPaletteOpen(!isPaletteOpen); + }} + size="xs" + flush="both" + > + {i18n.translate('xpack.lens.paletteHeatmapGradient.customize', { + defaultMessage: 'Edit', + })} + + setIsPaletteOpen(!isPaletteOpen)} + > + {activePalette && ( + { + // make sure to always have a list of stops + if (newPalette.params && !newPalette.params.stops) { + newPalette.params.stops = displayStops; + } + (newPalette as HeatmapVisualizationState['palette'])!.accessor = accessor; + setState({ + ...state, + palette: newPalette as HeatmapVisualizationState['palette'], + }); + }} + /> + )} + + + + + ); } diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index a86ba194bf4db..2ee1c43ac608e 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -72,6 +72,7 @@ export type { } from './indexpattern_datasource/types'; export type { LensEmbeddableInput } from './embeddable'; export { layerTypes } from '../common'; +export { DimensionEditorSection } from './shared_components'; export type { LensPublicStart, LensPublicSetup } from './plugin'; diff --git a/x-pack/plugins/lens/public/metric_visualization/dimension_editor.tsx b/x-pack/plugins/lens/public/metric_visualization/dimension_editor.tsx index 83faac5cc8026..d05ed14815795 100644 --- a/x-pack/plugins/lens/public/metric_visualization/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/dimension_editor.tsx @@ -23,6 +23,7 @@ import { applyPaletteParams, CustomizablePalette, CUSTOM_PALETTE, + DimensionEditorSection, FIXED_PROGRESSION, PalettePanelContainer, } from '../shared_components'; @@ -76,7 +77,7 @@ export function MetricDimensionEditor( const displayStops = applyPaletteParams(props.paletteService, activePalette, currentMinMax); return ( - <> + )} - + ); } diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index 2c038b0937999..1552f8bccddd0 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -22,8 +22,13 @@ import { DEFAULT_PERCENT_DECIMALS } from './constants'; import { PartitionChartsMeta } from './partition_charts_meta'; import { LegendDisplay, PieVisualizationState, SharedPieLayerState } from '../../common'; import { VisualizationDimensionEditorProps, VisualizationToolbarProps } from '../types'; -import { ToolbarPopover, LegendSettingsPopover, useDebouncedValue } from '../shared_components'; -import { PalettePicker } from '../shared_components'; +import { + ToolbarPopover, + LegendSettingsPopover, + useDebouncedValue, + DimensionEditorSection, + PalettePicker, +} from '../shared_components'; import { getDefaultVisualValuesForLayer } from '../shared_components/datasource_default_values'; import { shouldShowValuesInLegend } from './render_helpers'; @@ -298,7 +303,7 @@ export function DimensionEditor( } ) { return ( - <> + - + ); } diff --git a/x-pack/plugins/lens/public/shared_components/dimension_section.scss b/x-pack/plugins/lens/public/shared_components/dimension_section.scss new file mode 100644 index 0000000000000..f9242cf686604 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/dimension_section.scss @@ -0,0 +1,8 @@ +.lnsDimensionEditorSection { + padding: $euiSize; + } + + .lnsDimensionEditorSection--hasBorder { + border-top: 1px solid #D3DAE6; + } + \ No newline at end of file diff --git a/x-pack/plugins/lens/public/shared_components/dimension_section.tsx b/x-pack/plugins/lens/public/shared_components/dimension_section.tsx new file mode 100644 index 0000000000000..bbe6ac0b12ba9 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/dimension_section.tsx @@ -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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import classNames from 'classnames'; +import './dimension_section.scss'; + +export const DimensionEditorSection = ({ + children, + hasBorder, +}: { + children?: React.ReactNode | React.ReactNode[]; + hasBorder?: boolean; +}) => { + return ( +
+ {children} +
+ ); +}; diff --git a/x-pack/plugins/lens/public/shared_components/index.ts b/x-pack/plugins/lens/public/shared_components/index.ts index 6140e54b43dc7..b2428532a72c9 100644 --- a/x-pack/plugins/lens/public/shared_components/index.ts +++ b/x-pack/plugins/lens/public/shared_components/index.ts @@ -17,5 +17,6 @@ export { LegendActionPopover } from './legend_action_popover'; export { NameInput } from './name_input'; export { ValueLabelsSettings } from './value_labels_settings'; export { AxisTitleSettings } from './axis_title_settings'; +export { DimensionEditorSection } from './dimension_section'; export * from './static_header'; export * from './vis_label'; diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 935c60a9a7c84..959db8ca006fe 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -753,9 +753,7 @@ function addInitialValueIfAvailable({ }), activeVisualizationState, }; - } - - if (noDatasource) { + } else { return { activeDatasourceState: datasourceState, activeVisualizationState, diff --git a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx index 1a400d803fe9d..b8193588be639 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx @@ -31,6 +31,7 @@ import { applyPaletteParams, CustomizablePalette, CUSTOM_PALETTE, + DimensionEditorSection, FIXED_PROGRESSION, PalettePanelContainer, TooltipWrapper, @@ -84,7 +85,7 @@ export function GaugeDimensionEditor( const togglePalette = () => setIsPaletteOpen(!isPaletteOpen); return ( - <> + )} - + ); } diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index 06e5a18d427cc..56930209706cc 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -7,7 +7,7 @@ exports[`xy_expression XYChart component annotations should render basic annotat Object { "dataValue": 1647591917125, "details": "Annotation", - "header": 1647591917100, + "header": "2022-03-18T08:25:17.140Z", }, ] } @@ -175,7 +175,7 @@ exports[`xy_expression XYChart component annotations should render simplified an Object { "dataValue": 1647591917125, "details": "Annotation", - "header": 1647591917100, + "header": "2022-03-18T08:25:17.140Z", }, ] } diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts index 40a07ab382d6f..93ab897b5fb9a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts @@ -11,22 +11,43 @@ import { IconHexagon, IconCircle, } from '../../../assets/annotation_icons'; -import { euiIconsSet } from '../../xy_config_panel/shared/icon_select'; export const annotationsIconSet = [ { - value: 'triangle', - label: i18n.translate('xpack.lens.xyChart.iconSelect.triangleIconLabel', { - defaultMessage: 'Triangle', + value: 'empty', + label: i18n.translate('xpack.lens.xyChart.iconSelect.noIconLabel', { + defaultMessage: 'None', }), - icon: IconTriangle, }, { - value: 'square', - label: i18n.translate('xpack.lens.xyChart.iconSelect.squareIconLabel', { - defaultMessage: 'Square', + value: 'asterisk', + label: i18n.translate('xpack.lens.xyChart.iconSelect.asteriskIconLabel', { + defaultMessage: 'Asterisk', + }), + }, + { + value: 'alert', + label: i18n.translate('xpack.lens.xyChart.iconSelect.alertIconLabel', { + defaultMessage: 'Alert', + }), + }, + { + value: 'bell', + label: i18n.translate('xpack.lens.xyChart.iconSelect.bellIconLabel', { + defaultMessage: 'Bell', + }), + }, + { + value: 'bolt', + label: i18n.translate('xpack.lens.xyChart.iconSelect.boltIconLabel', { + defaultMessage: 'Bolt', + }), + }, + { + value: 'bug', + label: i18n.translate('xpack.lens.xyChart.iconSelect.bugIconLabel', { + defaultMessage: 'Bug', }), - icon: IconSquare, }, { value: 'circle', @@ -35,6 +56,19 @@ export const annotationsIconSet = [ }), icon: IconCircle, }, + + { + value: 'editorComment', + label: i18n.translate('xpack.lens.xyChart.iconSelect.commentIconLabel', { + defaultMessage: 'Comment', + }), + }, + { + value: 'flag', + label: i18n.translate('xpack.lens.xyChart.iconSelect.flagIconLabel', { + defaultMessage: 'Flag', + }), + }, { value: 'hexagon', label: i18n.translate('xpack.lens.xyChart.iconSelect.hexagonIconLabel', { @@ -42,5 +76,24 @@ export const annotationsIconSet = [ }), icon: IconHexagon, }, - ...euiIconsSet, + { + value: 'square', + label: i18n.translate('xpack.lens.xyChart.iconSelect.squareIconLabel', { + defaultMessage: 'Square', + }), + icon: IconSquare, + }, + { + value: 'tag', + label: i18n.translate('xpack.lens.xyChart.iconSelect.tagIconLabel', { + defaultMessage: 'Tag', + }), + }, + { + value: 'triangle', + label: i18n.translate('xpack.lens.xyChart.iconSelect.triangleIconLabel', { + defaultMessage: 'Triangle', + }), + icon: IconTriangle, + }, ]; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.scss b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.scss new file mode 100644 index 0000000000000..d84543e4b881b --- /dev/null +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.scss @@ -0,0 +1,3 @@ +.lnsXyConfigHeading { + padding-bottom: 16px; +} diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx index fff9b8fecc471..7a634f81357be 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx @@ -6,8 +6,9 @@ */ import React, { useCallback } from 'react'; +import './index.scss'; import { i18n } from '@kbn/i18n'; -import { EuiDatePicker, EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; +import { EuiDatePicker, EuiFormRow, EuiSwitch, EuiSwitchEvent, EuiTitle } from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import moment from 'moment'; import { EventAnnotationConfig } from 'src/plugins/event_annotation/common/types'; @@ -16,7 +17,7 @@ import { State, XYState } from '../../types'; import { FormatFactory } from '../../../../common'; import { XYAnnotationLayerConfig } from '../../../../common/expressions'; import { ColorPicker } from '../../xy_config_panel/color_picker'; -import { NameInput, useDebouncedValue } from '../../../shared_components'; +import { DimensionEditorSection, NameInput, useDebouncedValue } from '../../../shared_components'; import { isHorizontalChart } from '../../state_helpers'; import { MarkerDecorationSettings } from '../../xy_config_panel/shared/marker_decoration_settings'; import { LineStyleSettings } from '../../xy_config_panel/shared/line_style_settings'; @@ -67,55 +68,73 @@ export const AnnotationsPanel = ( return ( <> - { - if (date) { - setAnnotations({ - key: { - ...(currentAnnotations?.key || { type: 'point_in_time' }), - timestamp: date.toISOString(), - }, - }); - } - }} - label={i18n.translate('xpack.lens.xyChart.annotationDate', { - defaultMessage: 'Annotation date', - })} - /> - { - setAnnotations({ label: value }); - }} - /> - - - - setAnnotations({ isHidden: ev.target.checked })} - /> + + +

+ {i18n.translate('xpack.lens.xyChart.placement', { + defaultMessage: 'Placement', + })} +

+
+ { + if (date) { + setAnnotations({ + key: { + ...(currentAnnotations?.key || { type: 'point_in_time' }), + timestamp: date.toISOString(), + }, + }); + } + }} + label={i18n.translate('xpack.lens.xyChart.annotationDate', { + defaultMessage: 'Annotation date', + })} + /> +
+ + +

+ {i18n.translate('xpack.lens.xyChart.appearance', { + defaultMessage: 'Appearance', + })} +

+
+ { + setAnnotations({ label: value }); + }} + /> + + + + setAnnotations({ isHidden: ev.target.checked })} + /> +
); }; @@ -132,6 +151,7 @@ const ConfigPanelDatePicker = ({ return ( {config.map(({ icon, label, time, color }) => (
- - {hasIcon(icon) ? : null} - {label} - + + {hasIcon(icon) && ( + + + + )} + {label} + {formatter?.convert(time) || String(time)}
))} @@ -168,8 +171,11 @@ export const Annotations = ({ : markerPositionVertical; const hasReducedPadding = paddingMap[markerPositionVertical] === LINES_MARKER_SIZE; const id = snakeCase(annotation.label); - const { roundedTimestamp } = annotation; - + const { roundedTimestamp, time: exactTimestamp } = annotation; + const isGrouped = Boolean(annotation.customTooltipDetails); + const header = + formatter?.convert(isGrouped ? roundedTimestamp : exactTimestamp) || + moment(isGrouped ? roundedTimestamp : exactTimestamp).toISOString(); return ( { // checking tooltip const renderLinks = mount(
{groupedAnnotation.prop('customTooltipDetails')!()}
); expect(renderLinks.text()).toEqual( - 'Event 1 2022-03-18T08:25:00.000ZEvent 2 2022-03-18T08:25:00.020ZEvent 3 2022-03-18T08:25:00.001Z' + ' Event 1 2022-03-18T08:25:00.000Z Event 2 2022-03-18T08:25:00.020Z Event 3 2022-03-18T08:25:00.001Z' ); }); test('should render grouped annotations with default styles', () => { diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 4767c067e2c6c..78fd50f7cfece 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -396,7 +396,6 @@ export const getXyVisualization = ({ return suggestion; }, - // todo: annotation layer types diff removeDimension({ prevState, layerId, columnId, frame }) { const foundLayer = prevState.layers.find((l) => l.layerId === layerId); if (!foundLayer) { diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/dimension_editor.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/dimension_editor.tsx index 1618bd4d0e540..2689742e6f9bb 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/dimension_editor.tsx @@ -15,7 +15,7 @@ import { FormatFactory } from '../../../common'; import { XYDataLayerConfig, YAxisMode, YConfig } from '../../../common/expressions'; import { isHorizontalChart } from '../state_helpers'; import { ColorPicker } from './color_picker'; -import { PalettePicker, useDebouncedValue } from '../../shared_components'; +import { DimensionEditorSection, PalettePicker, useDebouncedValue } from '../../shared_components'; type UnwrapArray = T extends Array ? P : T; @@ -79,7 +79,7 @@ export function DimensionEditor( if (props.groupId === 'breakdown') { return ( - <> + - + ); } const isHorizontal = isHorizontalChart(state.layers); return ( - <> + - + ); } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx index f00d60b0dc814..b8417c8079524 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx @@ -17,7 +17,7 @@ import { FillStyle, XYReferenceLineLayerConfig } from '../../../common/expressio import { ColorPicker } from './color_picker'; import { updateLayer } from '.'; -import { useDebouncedValue } from '../../shared_components'; +import { DimensionEditorSection, useDebouncedValue } from '../../shared_components'; import { idPrefix } from './dimension_editor'; import { isHorizontalChart } from '../state_helpers'; import { MarkerDecorationSettings } from './shared/marker_decoration_settings'; @@ -69,7 +69,7 @@ export const ReferenceLinePanel = ( ); return ( - <> + - + ); }; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx index db01a027d8fec..05acfc629ab62 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx @@ -40,7 +40,7 @@ export const LineStyleSettings = ({ defaultMessage: 'Line', })} > - + + {emsFieldSelect} - + ); } From f7e5c92ffbeae57c4675835b204e352bb0e80645 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 22 Mar 2022 12:59:32 +0100 Subject: [PATCH 34/47] remove hexagon and square, address Stratoula's feedback --- .../common/event_annotation_group/index.ts | 9 +++- .../common/manual_event_annotation/index.ts | 41 ++++++++++++++----- src/plugins/event_annotation/kibana.json | 1 + .../assets/annotation_icons/hexagon.tsx | 26 ------------ .../public/assets/annotation_icons/index.tsx | 2 - .../public/assets/annotation_icons/square.tsx | 22 ---------- .../shared_components/dimension_section.scss | 11 +++-- .../annotations/config_panel/icon_set.ts | 21 +--------- .../xy_visualization/annotations_helpers.tsx | 6 +-- .../public/xy_visualization/xy_suggestions.ts | 3 +- 10 files changed, 49 insertions(+), 93 deletions(-) delete mode 100644 x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx delete mode 100644 x-pack/plugins/lens/public/assets/annotation_icons/square.tsx diff --git a/src/plugins/event_annotation/common/event_annotation_group/index.ts b/src/plugins/event_annotation/common/event_annotation_group/index.ts index 49a2194350d83..0c0fdc911e049 100644 --- a/src/plugins/event_annotation/common/event_annotation_group/index.ts +++ b/src/plugins/event_annotation/common/event_annotation_group/index.ts @@ -7,6 +7,7 @@ */ import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { i18n } from '@kbn/i18n'; import type { EventAnnotationOutput } from '../manual_event_annotation/types'; export interface EventAnnotationGroupOutput { @@ -29,11 +30,15 @@ export function eventAnnotationGroup(): ExpressionFunctionDefinition< aliases: [], type: 'event_annotation_group', inputTypes: ['null'], - help: '', + help: i18n.translate('event_annotation.event_annotation_group.description', { + defaultMessage: 'Event annotation group', + }), args: { annotations: { types: ['manual_event_annotation'], - help: 'Annotation configs', + help: i18n.translate('event_annotation.event_annotation_group.args.annotationConfigs', { + defaultMessage: 'Annotation configs', + }), multi: true, }, }, diff --git a/src/plugins/event_annotation/common/manual_event_annotation/index.ts b/src/plugins/event_annotation/common/manual_event_annotation/index.ts index 038bd170d7867..07e6c5dcaf43d 100644 --- a/src/plugins/event_annotation/common/manual_event_annotation/index.ts +++ b/src/plugins/event_annotation/common/manual_event_annotation/index.ts @@ -7,6 +7,7 @@ */ import type { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; +import { i18n } from '@kbn/i18n'; import type { EventAnnotationArgs, EventAnnotationOutput } from './types'; export const manualEventAnnotation: ExpressionFunctionDefinition< 'manual_event_annotation', @@ -17,46 +18,66 @@ export const manualEventAnnotation: ExpressionFunctionDefinition< name: 'manual_event_annotation', aliases: [], type: 'manual_event_annotation', - help: `Configure manual annotation`, + help: i18n.translate('event_annotation.manual_annotation_group.description', { + defaultMessage: `Configure manual annotation`, + }), inputTypes: ['null'], args: { time: { types: ['string'], - help: 'Timestamp for annotation', + help: i18n.translate('event_annotation.manual_annotation_group.args.time', { + defaultMessage: `Timestamp for annotation`, + }), }, label: { types: ['string'], - help: 'The name of the annotation', + help: i18n.translate('event_annotation.manual_annotation_group.args.label', { + defaultMessage: `The name of the annotation`, + }), }, color: { types: ['string'], - help: 'The color of the line', + help: i18n.translate('event_annotation.manual_annotation_group.args.color', { + defaultMessage: 'The color of the line', + }), }, lineStyle: { types: ['string'], options: ['solid', 'dotted', 'dashed'], - help: 'The style of the annotation line', + help: i18n.translate('event_annotation.manual_annotation_group.args.lineStyle', { + defaultMessage: 'The style of the annotation line', + }), }, lineWidth: { types: ['number'], - help: 'The width of the annotation line', + help: i18n.translate('event_annotation.manual_annotation_group.args.lineWidth', { + defaultMessage: 'The width of the annotation line', + }), }, icon: { types: ['string'], - help: 'An optional icon used for annotation lines', + help: i18n.translate('event_annotation.manual_annotation_group.args.icon', { + defaultMessage: 'An optional icon used for annotation lines', + }), }, iconPosition: { types: ['string'], options: ['auto', 'above', 'below', 'left', 'right'], - help: 'The placement of the icon for the annotation line', + help: i18n.translate('event_annotation.manual_annotation_group.args.iconPosition', { + defaultMessage: 'The placement of the icon for the annotation line', + }), }, textVisibility: { types: ['boolean'], - help: 'Visibility of the label on the annotation line', + help: i18n.translate('event_annotation.manual_annotation_group.args.textVisibility', { + defaultMessage: 'Visibility of the label on the annotation line', + }), }, isHidden: { types: ['boolean'], - help: 'is hidden?', + help: i18n.translate('event_annotation.manual_annotation_group.args.isHidden', { + defaultMessage: `Switch to hide annotation`, + }), }, }, fn: function fn(input: unknown, args: EventAnnotationArgs) { diff --git a/src/plugins/event_annotation/kibana.json b/src/plugins/event_annotation/kibana.json index 47af30fb40a40..5a0c49be09ba3 100644 --- a/src/plugins/event_annotation/kibana.json +++ b/src/plugins/event_annotation/kibana.json @@ -3,6 +3,7 @@ "version": "kibana", "server": true, "ui": true, + "description": "The Event Annotation service contains expressions for event annotations", "extraPublicDirs": [ "common" ], diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx deleted file mode 100644 index 6ff52856cf558..0000000000000 --- a/x-pack/plugins/lens/public/assets/annotation_icons/hexagon.tsx +++ /dev/null @@ -1,26 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as React from 'react'; -import { EuiIconProps } from '@elastic/eui'; - -export const IconHexagon = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - -); diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx index 05f9514312d46..9e641d495582f 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/index.tsx @@ -6,6 +6,4 @@ */ export { IconCircle } from './circle'; -export { IconHexagon } from './hexagon'; -export { IconSquare } from './square'; export { IconTriangle } from './triangle'; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx deleted file mode 100644 index 1f5d7697cb32c..0000000000000 --- a/x-pack/plugins/lens/public/assets/annotation_icons/square.tsx +++ /dev/null @@ -1,22 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import * as React from 'react'; -import { EuiIconProps } from '@elastic/eui'; -export const IconSquare = ({ title, titleId, ...props }: Omit) => ( - - {title ? {title} : null} - - -); diff --git a/x-pack/plugins/lens/public/shared_components/dimension_section.scss b/x-pack/plugins/lens/public/shared_components/dimension_section.scss index f9242cf686604..445eab84c9ca6 100644 --- a/x-pack/plugins/lens/public/shared_components/dimension_section.scss +++ b/x-pack/plugins/lens/public/shared_components/dimension_section.scss @@ -1,8 +1,7 @@ .lnsDimensionEditorSection { - padding: $euiSize; - } + padding: $euiSize; +} - .lnsDimensionEditorSection--hasBorder { - border-top: 1px solid #D3DAE6; - } - \ No newline at end of file +.lnsDimensionEditorSection--hasBorder { + border-top: 1px solid $euiColorLightShade; +} diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts index 93ab897b5fb9a..d10beaa9ba2a2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts @@ -5,12 +5,7 @@ * 2.0. */ import { i18n } from '@kbn/i18n'; -import { - IconSquare, - IconTriangle, - IconHexagon, - IconCircle, -} from '../../../assets/annotation_icons'; +import { IconTriangle, IconCircle } from '../../../assets/annotation_icons'; export const annotationsIconSet = [ { @@ -69,20 +64,6 @@ export const annotationsIconSet = [ defaultMessage: 'Flag', }), }, - { - value: 'hexagon', - label: i18n.translate('xpack.lens.xyChart.iconSelect.hexagonIconLabel', { - defaultMessage: 'Hexagon', - }), - icon: IconHexagon, - }, - { - value: 'square', - label: i18n.translate('xpack.lens.xyChart.iconSelect.squareIconLabel', { - defaultMessage: 'Square', - }), - icon: IconSquare, - }, { value: 'tag', label: i18n.translate('xpack.lens.xyChart.iconSelect.tagIconLabel', { diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx index b571d84dfd437..33e5d688064d7 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx @@ -11,7 +11,7 @@ import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText, IconType } from '@elastic import { Position } from '@elastic/charts'; import type { IconPosition, YAxisMode, YConfig } from '../../common/expressions'; import { hasIcon } from './xy_config_panel/shared/icon_select'; -import { IconCircle, IconHexagon, IconSquare, IconTriangle } from '../assets/annotation_icons'; +import { IconCircle, IconTriangle } from '../assets/annotation_icons'; export const LINES_MARKER_SIZE = 20; @@ -167,14 +167,12 @@ export function MarkerBody({ const isNumericalString = (value: string) => !isNaN(Number(value)); -const shapes = ['circle', 'hexagon', 'triangle', 'square'] as const; +const shapes = ['circle', 'triangle'] as const; type Shape = typeof shapes[number]; const shapesIconMap: Record = { triangle: { icon: IconTriangle, shouldRotate: true }, circle: { icon: IconCircle }, - hexagon: { icon: IconHexagon, shouldRotate: true }, - square: { icon: IconSquare }, }; const isCustomAnnotationShape = (value: string): value is Shape => shapes.includes(value as Shape); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index c3dc556068e50..bd5a37c206c6c 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -522,7 +522,8 @@ function buildSuggestion({ ? currentState.layers // Remove layers that aren't being suggested .filter( - (layer) => keptLayerIds.includes(layer.layerId) || layer.layerType === 'annotations' + (layer) => + keptLayerIds.includes(layer.layerId) || layer.layerType === layerTypes.ANNOTATIONS ) // Update in place .map((layer) => (layer.layerId === layerId ? newLayer : layer)) From 0c126c43268cf2c780456c6265a3dddd6b84db7d Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 22 Mar 2022 14:09:12 +0100 Subject: [PATCH 35/47] stroke for icons & icon fill --- .../plugins/lens/public/app_plugin/app.scss | 4 ++++ .../public/assets/annotation_icons/circle.tsx | 12 ++++++++---- .../assets/annotation_icons/triangle.tsx | 11 ++++++++++- .../annotations/expression.tsx | 8 +++++--- .../xy_visualization/annotations_helpers.tsx | 19 +++++++++++++++++-- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.scss b/x-pack/plugins/lens/public/app_plugin/app.scss index 83b0a39be9229..39eee969f488d 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.scss +++ b/x-pack/plugins/lens/public/app_plugin/app.scss @@ -39,6 +39,10 @@ } } +.lensAnnotationIconFill { + fill: $euiColorLightestShade; +} + // Less-than-ideal styles to add a vertical divider after this button. Consider restructuring markup for better semantics and styling options in the future. .lnsNavItem__goBack { @include euiBreakpoint('m', 'l', 'xl') { diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx index 326c51ff0890f..6e1395e5d977e 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx @@ -18,10 +18,14 @@ export const IconCircle = ({ title, titleId, ...props }: Omit {title ? {title} : null} - ); diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx index af0b7a67c729f..7e3dee6db821c 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx @@ -19,6 +19,15 @@ export const IconTriangle = ({ title, titleId, ...props }: Omit {title ? {title} : null} - + + + + ); diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index 9dae0c83cbc4c..60b7a2a88d8b5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -176,6 +176,7 @@ export const Annotations = ({ const header = formatter?.convert(isGrouped ? roundedTimestamp : exactTimestamp) || moment(isGrouped ? roundedTimestamp : exactTimestamp).toISOString(); + const strokeWidth = annotation.lineWidth || 1; return ( ) : undefined @@ -217,13 +219,13 @@ export const Annotations = ({ customTooltipDetails={annotation.customTooltipDetails} style={{ line: { - strokeWidth: annotation.lineWidth || 1, + strokeWidth, stroke: annotation.color || defaultAnnotationColor, dash: annotation.lineStyle === 'dashed' - ? [(annotation.lineWidth || 1) * 3, annotation.lineWidth || 1] + ? [strokeWidth * 3, strokeWidth] : annotation.lineStyle === 'dotted' - ? [annotation.lineWidth || 1, annotation.lineWidth || 1] + ? [strokeWidth, strokeWidth] : undefined, opacity: 1, }, diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx index 33e5d688064d7..7bc9e8960af6b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx @@ -9,6 +9,7 @@ import './expression_reference_lines.scss'; import React from 'react'; import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText, IconType } from '@elastic/eui'; import { Position } from '@elastic/charts'; +import classnames from 'classnames'; import type { IconPosition, YAxisMode, YConfig } from '../../common/expressions'; import { hasIcon } from './xy_config_panel/shared/icon_select'; import { IconCircle, IconTriangle } from '../assets/annotation_icons'; @@ -215,11 +216,13 @@ export const AnnotationIcon = ({ type, rotationClass = '', isHorizontal, + strokeWidth, ...rest }: { type: string; rotationClass?: string; isHorizontal?: boolean; + strokeWidth?: number; } & EuiIconProps) => { if (isNumericalString(type)) { return ; @@ -230,7 +233,15 @@ export const AnnotationIcon = ({ } const rotationClassName = shapesIconMap[type].shouldRotate ? rotationClass : ''; - return ; + return ( + + ); }; export function Marker({ @@ -239,15 +250,19 @@ export function Marker({ hasReducedPadding, label, rotationClass, + strokeWidth, }: { config: MarkerConfig; isHorizontal: boolean; hasReducedPadding: boolean; label?: string; rotationClass?: string; + strokeWidth?: number; }) { if (hasIcon(config.icon)) { - return ; + return ( + + ); } // if there's some text, check whether to show it as marker, or just show some padding for the icon From fd789e4c4e06a5b864856c04a34b7dd227352203 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 22 Mar 2022 17:18:38 +0100 Subject: [PATCH 36/47] fix tests --- .../__snapshots__/expression.test.tsx.snap | 8 ++++++-- .../xy_visualization/annotations/config_panel/index.tsx | 4 ++-- .../xy_config_panel/shared/line_style_settings.tsx | 5 ++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index 56930209706cc..425c8356a8aa6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -28,6 +28,7 @@ exports[`xy_expression XYChart component annotations should render basic annotat hasReducedPadding={false} isHorizontal={true} label="Annotation" + strokeWidth={1} /> } markerBody={ @@ -85,7 +86,8 @@ exports[`xy_expression XYChart component annotations should render grouped annot hasReducedPadding={true} isHorizontal={true} label="" - rotationClass="lnsXyAnnotationIcon_rotate180" + rotationClassName="lnsXyAnnotationIcon_rotate180" + strokeWidth={3} /> } markerBody={ @@ -146,7 +148,8 @@ exports[`xy_expression XYChart component annotations should render grouped annot hasReducedPadding={true} isHorizontal={true} label="" - rotationClass="lnsXyAnnotationIcon_rotate180" + rotationClassName="lnsXyAnnotationIcon_rotate180" + strokeWidth={1} /> } markerBody={ @@ -196,6 +199,7 @@ exports[`xy_expression XYChart component annotations should render simplified an hasReducedPadding={false} isHorizontal={true} label="Annotation" + strokeWidth={1} /> } markerBody={ diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx index 7a634f81357be..6d5e0915a86c2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx @@ -69,7 +69,7 @@ export const AnnotationsPanel = ( return ( <> - +

{i18n.translate('xpack.lens.xyChart.placement', { defaultMessage: 'Placement', @@ -94,7 +94,7 @@ export const AnnotationsPanel = ( /> - +

{i18n.translate('xpack.lens.xyChart.appearance', { defaultMessage: 'Appearance', diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx index 05acfc629ab62..766d5462db787 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/line_style_settings.tsx @@ -41,7 +41,7 @@ export const LineStyleSettings = ({ })} > - + { @@ -49,9 +49,8 @@ export const LineStyleSettings = ({ }} /> - + Date: Tue, 22 Mar 2022 21:18:06 +0100 Subject: [PATCH 37/47] fix small things --- .../common/manual_event_annotation/index.ts | 7 --- src/plugins/event_annotation/common/types.ts | 2 - .../event_annotation_service/service.tsx | 4 +- .../plugins/lens/public/app_plugin/app.scss | 3 + .../public/assets/annotation_icons/circle.tsx | 10 +-- .../assets/annotation_icons/triangle.tsx | 15 ++--- .../__snapshots__/expression.test.tsx.snap | 32 ++++------ .../annotations/config_panel/icon_set.ts | 9 +-- .../annotations/expression.scss | 9 +-- .../annotations/expression.tsx | 11 +--- .../xy_visualization/annotations_helpers.tsx | 61 ++++++------------- .../xy_visualization/expression.test.tsx | 1 - .../public/xy_visualization/expression.tsx | 8 +-- .../public/xy_visualization/to_expression.ts | 1 - .../shared/marker_decoration_settings.tsx | 3 +- 15 files changed, 57 insertions(+), 119 deletions(-) diff --git a/src/plugins/event_annotation/common/manual_event_annotation/index.ts b/src/plugins/event_annotation/common/manual_event_annotation/index.ts index 07e6c5dcaf43d..eda7e1a84cc44 100644 --- a/src/plugins/event_annotation/common/manual_event_annotation/index.ts +++ b/src/plugins/event_annotation/common/manual_event_annotation/index.ts @@ -60,13 +60,6 @@ export const manualEventAnnotation: ExpressionFunctionDefinition< defaultMessage: 'An optional icon used for annotation lines', }), }, - iconPosition: { - types: ['string'], - options: ['auto', 'above', 'below', 'left', 'right'], - help: i18n.translate('event_annotation.manual_annotation_group.args.iconPosition', { - defaultMessage: 'The placement of the icon for the annotation line', - }), - }, textVisibility: { types: ['boolean'], help: i18n.translate('event_annotation.manual_annotation_group.args.textVisibility', { diff --git a/src/plugins/event_annotation/common/types.ts b/src/plugins/event_annotation/common/types.ts index 53b93a8866f36..95275804d1d1f 100644 --- a/src/plugins/event_annotation/common/types.ts +++ b/src/plugins/event_annotation/common/types.ts @@ -7,7 +7,6 @@ */ export type LineStyle = 'solid' | 'dashed' | 'dotted'; -export type IconPosition = 'auto' | 'left' | 'right' | 'above' | 'below'; export type AnnotationType = 'manual'; export type KeyType = 'point_in_time'; @@ -17,7 +16,6 @@ export interface StyleProps { icon?: string; lineWidth?: number; lineStyle?: LineStyle; - iconPosition?: IconPosition; textVisibility?: boolean; isHidden?: boolean; } diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index b5e125e45f773..a851644d42cb3 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -22,7 +22,6 @@ export function getEventAnnotationService(): EventAnnotationServiceType { lineStyle, lineWidth, icon, - iconPosition, textVisibility, time, }) => { @@ -38,8 +37,7 @@ export function getEventAnnotationService(): EventAnnotationServiceType { color: [color || defaultAnnotationColor], lineWidth: [lineWidth || 1], lineStyle: [lineStyle || 'solid'], - icon: hasIcon(icon) ? [icon] : ['empty'], - iconPosition: hasIcon(icon) || textVisibility ? [iconPosition || 'auto'] : ['auto'], + icon: hasIcon(icon) ? [icon] : ['triangle'], textVisibility: [textVisibility || false], isHidden: [Boolean(isHidden)], }, diff --git a/x-pack/plugins/lens/public/app_plugin/app.scss b/x-pack/plugins/lens/public/app_plugin/app.scss index 39eee969f488d..64437fb92a16d 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.scss +++ b/x-pack/plugins/lens/public/app_plugin/app.scss @@ -38,6 +38,9 @@ fill: makeGraphicContrastColor($euiColorVis0, $euiColorDarkShade); } } +.lensAnnotationIconNoFill { + fill: none; +} .lensAnnotationIconFill { fill: $euiColorLightestShade; diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx index 6e1395e5d977e..fe19dc7e4c8fc 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/circle.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import { EuiIconProps } from '@elastic/eui'; +import classnames from 'classnames'; export const IconCircle = ({ title, titleId, ...props }: Omit) => ( {title ? {title} : null} ); diff --git a/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx b/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx index 7e3dee6db821c..9924c049004cf 100644 --- a/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx +++ b/x-pack/plugins/lens/public/assets/annotation_icons/triangle.tsx @@ -7,27 +7,24 @@ import * as React from 'react'; import { EuiIconProps } from '@elastic/eui'; +import classnames from 'classnames'; export const IconTriangle = ({ title, titleId, ...props }: Omit) => ( {title ? {title} : null} - - - ); diff --git a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap index 425c8356a8aa6..0f3bf59bfb0ab 100644 --- a/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap +++ b/x-pack/plugins/lens/public/xy_visualization/__snapshots__/expression.test.tsx.snap @@ -28,7 +28,6 @@ exports[`xy_expression XYChart component annotations should render basic annotat hasReducedPadding={false} isHorizontal={true} label="Annotation" - strokeWidth={1} /> } markerBody={ @@ -57,14 +56,14 @@ exports[`xy_expression XYChart component annotations should render grouped annot Array [ Object { "dataValue": 1647591900025, - "details": "", + "details": "Event 1", "header": 1647591900000, }, ] } domainType="xDomain" - id="" - key="" + id="event_1" + key="event_1" marker={ } markerBody={ @@ -95,7 +91,7 @@ exports[`xy_expression XYChart component annotations should render grouped annot isHorizontal={true} /> } - markerPosition="bottom" + markerPosition="top" style={ Object { "line": Object { @@ -119,14 +115,14 @@ exports[`xy_expression XYChart component annotations should render grouped annot Array [ Object { "dataValue": 1647591900025, - "details": "", + "details": "Event 1", "header": 1647591900000, }, ] } domainType="xDomain" - id="" - key="" + id="event_1" + key="event_1" marker={ } markerBody={ @@ -157,7 +150,7 @@ exports[`xy_expression XYChart component annotations should render grouped annot isHorizontal={true} /> } - markerPosition="bottom" + markerPosition="top" style={ Object { "line": Object { @@ -199,7 +192,6 @@ exports[`xy_expression XYChart component annotations should render simplified an hasReducedPadding={false} isHorizontal={true} label="Annotation" - strokeWidth={1} /> } markerBody={ diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts index d10beaa9ba2a2..5f739bdf0eec8 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts @@ -8,12 +8,6 @@ import { i18n } from '@kbn/i18n'; import { IconTriangle, IconCircle } from '../../../assets/annotation_icons'; export const annotationsIconSet = [ - { - value: 'empty', - label: i18n.translate('xpack.lens.xyChart.iconSelect.noIconLabel', { - defaultMessage: 'None', - }), - }, { value: 'asterisk', label: i18n.translate('xpack.lens.xyChart.iconSelect.asteriskIconLabel', { @@ -50,6 +44,7 @@ export const annotationsIconSet = [ defaultMessage: 'Circle', }), icon: IconCircle, + canFill: true, }, { @@ -76,5 +71,7 @@ export const annotationsIconSet = [ defaultMessage: 'Triangle', }), icon: IconTriangle, + shouldRotate: true, + canFill: true, }, ]; diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss index f50f17fb113d3..fc2b1204bb1d0 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.scss @@ -32,11 +32,6 @@ } .lnsXyAnnotationIcon_rotate90 { - transform: rotate(90deg); -} -.lnsXyAnnotationIcon_rotate180 { - transform: rotate(180deg); -} -.lnsXyAnnotationIcon_rotate270 { - transform: rotate(270deg); + transform: rotate(45deg); + transform-origin: center; } diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx index 60b7a2a88d8b5..c36488f29d238 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/expression.tsx @@ -19,16 +19,14 @@ import type { EventAnnotationArgs } from 'src/plugins/event_annotation/common'; import moment from 'moment'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; -import type { AnnotationLayerArgs, IconPosition } from '../../../common/expressions'; +import type { AnnotationLayerArgs } from '../../../common/expressions'; import { hasIcon } from '../xy_config_panel/shared/icon_select'; import { - getBaseIconPlacement, mapVerticalToHorizontalPlacement, LINES_MARKER_SIZE, MarkerBody, Marker, AnnotationIcon, - getIconRotationClass, } from '../annotations_helpers'; const getRoundedTimestamp = (timestamp: number, firstTimestamp?: number, minInterval?: number) => { @@ -120,8 +118,6 @@ const getCommonStyles = (configArr: EventAnnotationArgs[]) => { ), lineWidth: getCommonProperty(configArr, 'lineWidth', 1), lineStyle: getCommonProperty(configArr, 'lineStyle', 'solid'), - iconPosition: getCommonProperty(configArr, 'iconPosition', Position.Top as IconPosition), - label: getCommonProperty(configArr, 'label', ''), textVisibility: getCommonProperty(configArr, 'textVisibility', false), }; }; @@ -165,7 +161,7 @@ export const Annotations = ({ return ( <> {groupedAnnotations.map((annotation) => { - const markerPositionVertical = getBaseIconPlacement(annotation.iconPosition); + const markerPositionVertical = Position.Top; const markerPosition = isHorizontal ? mapVerticalToHorizontalPlacement(markerPositionVertical) : markerPositionVertical; @@ -190,8 +186,7 @@ export const Annotations = ({ isHorizontal: !isHorizontal, hasReducedPadding, label: annotation.label, - rotationClass: getIconRotationClass(markerPosition), - strokeWidth, + rotateClassName: isHorizontal ? 'lnsXyAnnotationIcon_rotate90' : undefined, }} /> ) : undefined diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx index 7bc9e8960af6b..ddbdfc91f4a3e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations_helpers.tsx @@ -7,12 +7,12 @@ import './expression_reference_lines.scss'; import React from 'react'; -import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText, IconType } from '@elastic/eui'; +import { EuiFlexGroup, EuiIcon, EuiIconProps, EuiText } from '@elastic/eui'; import { Position } from '@elastic/charts'; import classnames from 'classnames'; import type { IconPosition, YAxisMode, YConfig } from '../../common/expressions'; import { hasIcon } from './xy_config_panel/shared/icon_select'; -import { IconCircle, IconTriangle } from '../assets/annotation_icons'; +import { annotationsIconSet } from './annotations/config_panel/icon_set'; export const LINES_MARKER_SIZE = 20; @@ -168,16 +168,6 @@ export function MarkerBody({ const isNumericalString = (value: string) => !isNaN(Number(value)); -const shapes = ['circle', 'triangle'] as const; -type Shape = typeof shapes[number]; - -const shapesIconMap: Record = { - triangle: { icon: IconTriangle, shouldRotate: true }, - circle: { icon: IconCircle }, -}; - -const isCustomAnnotationShape = (value: string): value is Shape => shapes.includes(value as Shape); - function NumberIcon({ number }: { number: number }) { return ( { - if (markerPosition === 'left') { - return 'lnsXyAnnotationIcon_rotate270'; - } - if (markerPosition === 'right') { - return 'lnsXyAnnotationIcon_rotate90'; - } - if (markerPosition === 'bottom') { - return 'lnsXyAnnotationIcon_rotate180'; - } -}; - export const AnnotationIcon = ({ type, - rotationClass = '', + rotateClassName = '', isHorizontal, - strokeWidth, + renderedInChart, ...rest }: { type: string; - rotationClass?: string; + rotateClassName?: string; isHorizontal?: boolean; - strokeWidth?: number; + renderedInChart?: boolean; } & EuiIconProps) => { if (isNumericalString(type)) { return ; } - const isCustom = isCustomAnnotationShape(type); - if (!isCustom) { - return ; + const iconConfig = annotationsIconSet.find((i) => i.value === type); + if (!iconConfig) { + return null; } - - const rotationClassName = shapesIconMap[type].shouldRotate ? rotationClass : ''; return ( ); }; @@ -249,19 +228,17 @@ export function Marker({ isHorizontal, hasReducedPadding, label, - rotationClass, - strokeWidth, + rotateClassName, }: { config: MarkerConfig; isHorizontal: boolean; hasReducedPadding: boolean; label?: string; - rotationClass?: string; - strokeWidth?: number; + rotateClassName?: string; }) { if (hasIcon(config.icon)) { return ( - + ); } diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx index 25eb1849594b6..03a180cc20a08 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.test.tsx @@ -2967,7 +2967,6 @@ describe('xy_expression', () => { time: '2022-03-18T08:25:00.000Z', label: 'Event 1', icon: 'triangle', - iconPosition: 'below', type: 'manual_event_annotation', color: 'red', lineStyle: 'dashed', diff --git a/x-pack/plugins/lens/public/xy_visualization/expression.tsx b/x-pack/plugins/lens/public/xy_visualization/expression.tsx index 4e3d2ca467714..d23833677400a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/expression.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/expression.tsx @@ -593,14 +593,8 @@ export function XYChart({ !chartHasMoreThanOneBarSeries) ); - const bottomAnnotationsExist = groupedAnnotations.length && linesPaddings.bottom; - const shouldUseNewTimeAxis = - isTimeViz && - isHistogramModeEnabled && - !useLegacyTimeAxis && - !shouldRotate && - !bottomAnnotationsExist; + isTimeViz && isHistogramModeEnabled && !useLegacyTimeAxis && !shouldRotate; const gridLineStyle = { visible: gridlinesVisibilitySettings?.x, diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index 7a300f85747be..c48711ddb29de 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -425,7 +425,6 @@ const annotationLayerToExpression = ( label: ann.label || defaultAnnotationLabel, textVisibility: ann.textVisibility, icon: ann.icon, - iconPosition: ann.iconPosition, lineStyle: ann.lineStyle, lineWidth: ann.lineWidth, color: ann.color, diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx index 919df17447b98..134d72b7de64b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/marker_decoration_settings.tsx @@ -143,7 +143,8 @@ export const MarkerDecorationSettings = ({ }} /> - {hasIcon(currentConfig?.icon) || currentConfig?.textVisibility ? ( + {currentConfig?.iconPosition && + (hasIcon(currentConfig?.icon) || currentConfig?.textVisibility) ? ( Date: Tue, 22 Mar 2022 21:46:54 +0100 Subject: [PATCH 38/47] align the set with tsvb --- .../annotations/config_panel/icon_set.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts index 5f739bdf0eec8..87813ec12913e 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/icon_set.ts @@ -59,6 +59,26 @@ export const annotationsIconSet = [ defaultMessage: 'Flag', }), }, + { + value: 'heart', + label: i18n.translate('xpack.lens.xyChart.iconSelect.heartLabel', { defaultMessage: 'Heart' }), + }, + { + value: 'mapMarker', + label: i18n.translate('xpack.lens.xyChart.iconSelect.mapMarkerLabel', { + defaultMessage: 'Map Marker', + }), + }, + { + value: 'pinFilled', + label: i18n.translate('xpack.lens.xyChart.iconSelect.mapPinLabel', { + defaultMessage: 'Map Pin', + }), + }, + { + value: 'starEmpty', + label: i18n.translate('xpack.lens.xyChart.iconSelect.starLabel', { defaultMessage: 'Star' }), + }, { value: 'tag', label: i18n.translate('xpack.lens.xyChart.iconSelect.tagIconLabel', { From b9271eea552aa4fdcddecc7ead726f3dc9dcd3a6 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Tue, 22 Mar 2022 21:55:19 +0100 Subject: [PATCH 39/47] align IconSelect --- x-pack/plugins/lens/public/index.ts | 2 +- .../shared_components/dimension_section.tsx | 15 ++++----------- .../xy_config_panel/shared/icon_select.tsx | 12 +++++++----- 3 files changed, 12 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 2ee1c43ac608e..cb18cfc2c6a26 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -72,7 +72,7 @@ export type { } from './indexpattern_datasource/types'; export type { LensEmbeddableInput } from './embeddable'; export { layerTypes } from '../common'; -export { DimensionEditorSection } from './shared_components'; +export { DimensionEditorSection } from './shared_components/dimension_section'; export type { LensPublicStart, LensPublicSetup } from './plugin'; diff --git a/x-pack/plugins/lens/public/shared_components/dimension_section.tsx b/x-pack/plugins/lens/public/shared_components/dimension_section.tsx index bbe6ac0b12ba9..cfc6b2e20f0a1 100644 --- a/x-pack/plugins/lens/public/shared_components/dimension_section.tsx +++ b/x-pack/plugins/lens/public/shared_components/dimension_section.tsx @@ -5,7 +5,6 @@ * 2.0. */ import React from 'react'; -import classNames from 'classnames'; import './dimension_section.scss'; export const DimensionEditorSection = ({ @@ -15,14 +14,8 @@ export const DimensionEditorSection = ({ children?: React.ReactNode | React.ReactNode[]; hasBorder?: boolean; }) => { - return ( -
- {children} -
- ); + const classNames = hasBorder + ? 'lnsDimensionEditorSection--hasBorder lnsDimensionEditorSection' + : 'lnsDimensionEditorSection'; + return
{children}
; }; diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx index d813730a74df0..e2b5923e81031 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/shared/icon_select.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiComboBox, EuiIcon, IconType } from '@elastic/eui'; +import { EuiComboBox, EuiFlexGroup, EuiFlexItem, EuiIcon, IconType } from '@elastic/eui'; export function hasIcon(icon: string | undefined): icon is string { return icon != null && icon !== 'empty'; @@ -75,10 +75,12 @@ export const euiIconsSet = [ const IconView = (props: { value?: string; label: string; icon?: IconType }) => { if (!props.value) return null; return ( - - - {` ${props.label}`} - + + + + + {props.label} + ); }; From 94484dcd0b10fd2e24b8eafb3fe37d3c02c5b213 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 23 Mar 2022 08:18:10 +0100 Subject: [PATCH 40/47] fix i18nrc --- .i18nrc.json | 17 +++++++++++++---- .../common/event_annotation_group/index.ts | 4 ++-- .../common/manual_event_annotation/index.ts | 18 +++++++++--------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.i18nrc.json b/.i18nrc.json index 402932902f249..71b68d2c51d85 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -31,6 +31,7 @@ "expressions": "src/plugins/expressions", "expressionShape": "src/plugins/expression_shape", "expressionTagcloud": "src/plugins/chart_expressions/expression_tagcloud", + "eventAnnotation": "src/plugins/event_annotation", "fieldFormats": "src/plugins/field_formats", "flot": "packages/kbn-flot-charts/lib", "home": "src/plugins/home", @@ -50,7 +51,10 @@ "kibana-react": "src/plugins/kibana_react", "kibanaOverview": "src/plugins/kibana_overview", "lists": "packages/kbn-securitysolution-list-utils/src", - "management": ["src/legacy/core_plugins/management", "src/plugins/management"], + "management": [ + "src/legacy/core_plugins/management", + "src/plugins/management" + ], "monaco": "packages/kbn-monaco/src", "navigation": "src/plugins/navigation", "newsfeed": "src/plugins/newsfeed", @@ -62,8 +66,13 @@ "sharedUX": "src/plugins/shared_ux", "sharedUXComponents": "packages/kbn-shared-ux-components/src", "statusPage": "src/legacy/core_plugins/status_page", - "telemetry": ["src/plugins/telemetry", "src/plugins/telemetry_management_section"], - "timelion": ["src/plugins/vis_types/timelion"], + "telemetry": [ + "src/plugins/telemetry", + "src/plugins/telemetry_management_section" + ], + "timelion": [ + "src/plugins/vis_types/timelion" + ], "uiActions": "src/plugins/ui_actions", "uiActionsExamples": "examples/ui_action_examples", "usageCollection": "src/plugins/usage_collection", @@ -83,4 +92,4 @@ "visualizations": "src/plugins/visualizations" }, "translations": [] -} +} \ No newline at end of file diff --git a/src/plugins/event_annotation/common/event_annotation_group/index.ts b/src/plugins/event_annotation/common/event_annotation_group/index.ts index 0c0fdc911e049..85f1d9dff900c 100644 --- a/src/plugins/event_annotation/common/event_annotation_group/index.ts +++ b/src/plugins/event_annotation/common/event_annotation_group/index.ts @@ -30,13 +30,13 @@ export function eventAnnotationGroup(): ExpressionFunctionDefinition< aliases: [], type: 'event_annotation_group', inputTypes: ['null'], - help: i18n.translate('event_annotation.event_annotation_group.description', { + help: i18n.translate('eventAnnotation.group.description', { defaultMessage: 'Event annotation group', }), args: { annotations: { types: ['manual_event_annotation'], - help: i18n.translate('event_annotation.event_annotation_group.args.annotationConfigs', { + help: i18n.translate('eventAnnotation.group.args.annotationConfigs', { defaultMessage: 'Annotation configs', }), multi: true, diff --git a/src/plugins/event_annotation/common/manual_event_annotation/index.ts b/src/plugins/event_annotation/common/manual_event_annotation/index.ts index eda7e1a84cc44..108df93b34180 100644 --- a/src/plugins/event_annotation/common/manual_event_annotation/index.ts +++ b/src/plugins/event_annotation/common/manual_event_annotation/index.ts @@ -18,57 +18,57 @@ export const manualEventAnnotation: ExpressionFunctionDefinition< name: 'manual_event_annotation', aliases: [], type: 'manual_event_annotation', - help: i18n.translate('event_annotation.manual_annotation_group.description', { + help: i18n.translate('eventAnnotation.manualAnnotation.description', { defaultMessage: `Configure manual annotation`, }), inputTypes: ['null'], args: { time: { types: ['string'], - help: i18n.translate('event_annotation.manual_annotation_group.args.time', { + help: i18n.translate('eventAnnotation.manualAnnotation.args.time', { defaultMessage: `Timestamp for annotation`, }), }, label: { types: ['string'], - help: i18n.translate('event_annotation.manual_annotation_group.args.label', { + help: i18n.translate('eventAnnotation.manualAnnotation.args.label', { defaultMessage: `The name of the annotation`, }), }, color: { types: ['string'], - help: i18n.translate('event_annotation.manual_annotation_group.args.color', { + help: i18n.translate('eventAnnotation.manualAnnotation.args.color', { defaultMessage: 'The color of the line', }), }, lineStyle: { types: ['string'], options: ['solid', 'dotted', 'dashed'], - help: i18n.translate('event_annotation.manual_annotation_group.args.lineStyle', { + help: i18n.translate('eventAnnotation.manualAnnotation.args.lineStyle', { defaultMessage: 'The style of the annotation line', }), }, lineWidth: { types: ['number'], - help: i18n.translate('event_annotation.manual_annotation_group.args.lineWidth', { + help: i18n.translate('eventAnnotation.manualAnnotation.args.lineWidth', { defaultMessage: 'The width of the annotation line', }), }, icon: { types: ['string'], - help: i18n.translate('event_annotation.manual_annotation_group.args.icon', { + help: i18n.translate('eventAnnotation.manualAnnotation.args.icon', { defaultMessage: 'An optional icon used for annotation lines', }), }, textVisibility: { types: ['boolean'], - help: i18n.translate('event_annotation.manual_annotation_group.args.textVisibility', { + help: i18n.translate('eventAnnotation.manualAnnotation.args.textVisibility', { defaultMessage: 'Visibility of the label on the annotation line', }), }, isHidden: { types: ['boolean'], - help: i18n.translate('event_annotation.manual_annotation_group.args.isHidden', { + help: i18n.translate('eventAnnotation.manualAnnotation.args.isHidden', { defaultMessage: `Switch to hide annotation`, }), }, From 269e876bdb7f9cf241ca5aefb9fd43a17ed2e6cd Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 23 Mar 2022 10:37:04 +0100 Subject: [PATCH 41/47] Update src/plugins/event_annotation/public/event_annotation_service/index.tsx Co-authored-by: Alexey Antonov --- .../event_annotation/public/event_annotation_service/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/event_annotation/public/event_annotation_service/index.tsx b/src/plugins/event_annotation/public/event_annotation_service/index.tsx index bc102d8c33a93..e967a7cb0f0a2 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/index.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/index.tsx @@ -9,7 +9,7 @@ import { EventAnnotationServiceType } from './types'; export class EventAnnotationService { - private eventAnnotationService: EventAnnotationServiceType | undefined = undefined; + private eventAnnotationService?: EventAnnotationServiceType; public async getService() { if (!this.eventAnnotationService) { const { getEventAnnotationService } = await import('./service'); From 115aca76ab9187923fbccee215da98e9f9ec7004 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 23 Mar 2022 11:10:13 +0100 Subject: [PATCH 42/47] refactor empty button --- .../buttons/empty_dimension_button.tsx | 67 +++++++------------ x-pack/plugins/lens/public/types.ts | 1 + .../xy_visualization/annotations/helpers.tsx | 15 ++++- 3 files changed, 38 insertions(+), 45 deletions(-) diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx index 2201ca2511fa3..f2118bda216b8 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/buttons/empty_dimension_button.tsx @@ -29,16 +29,16 @@ interface EmptyButtonProps { onClick: (id: string) => void; group: VisualizationDimensionGroupConfig; labels?: { - ariaLabel: (group: VisualizationDimensionGroupConfig) => string; + ariaLabel: (label: string) => string; label: JSX.Element | string; }; } const defaultButtonLabels = { - ariaLabel: (group: VisualizationDimensionGroupConfig) => + ariaLabel: (l: string) => i18n.translate('xpack.lens.indexPattern.addColumnAriaLabel', { defaultMessage: 'Add or drag-and-drop a field to {groupLabel}', - values: { groupLabel: group.groupLabel }, + values: { groupLabel: l }, }), label: ( - i18n.translate('xpack.lens.indexPattern.addColumnAriaLabelClick', { - defaultMessage: 'Add an annotation to {groupLabel}', - values: { groupLabel: group.groupLabel }, - }), - label: ( - - ), +const DefaultEmptyButton = ({ columnId, group, onClick }: EmptyButtonProps) => { + const { buttonAriaLabel, buttonLabel } = group.labels || {}; + return ( + { + onClick(columnId); + }} + > + {buttonLabel || defaultButtonLabels.label} + + ); }; -const DefaultEmptyButton = ({ - columnId, - group, - onClick, - labels = defaultButtonLabels, -}: EmptyButtonProps) => ( - { - onClick(columnId); - }} - > - {labels.label} - -); - const SuggestedValueButton = ({ columnId, group, onClick }: EmptyButtonProps) => ( { diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index 2d4f34079e6b2..e64b1c31b4331 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -173,11 +173,23 @@ export const getAnnotationsConfiguration = ({ ) ); + const groupLabel = getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }); + + const emptyButtonLabels = { + buttonAriaLabel: i18n.translate('xpack.lens.indexPattern.addColumnAriaLabelClick', { + defaultMessage: 'Add an annotation to {groupLabel}', + values: { groupLabel }, + }), + buttonLabel: i18n.translate('xpack.lens.configure.emptyConfigClick', { + defaultMessage: 'Add an annotation', + }), + }; + return { groups: [ { groupId: 'xAnnotations', - groupLabel: getAxisName('x', { isHorizontal: isHorizontalChart(state.layers) }), + groupLabel, accessors: getAnnotationsAccessorColorConfig(layer), dataTestSubj: 'lnsXY_xAnnotationsPanel', invalid: !hasDateHistogram, @@ -190,6 +202,7 @@ export const getAnnotationsConfiguration = ({ supportFieldFormat: false, enableDimensionEditor: true, filterOperations: () => false, + labels: emptyButtonLabels, }, ], }; From 2f04298892fdaa39c73d26ef99bc229b9d639dd9 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 23 Mar 2022 11:15:50 +0100 Subject: [PATCH 43/47] CR --- .../public/event_annotation_service/service.tsx | 2 +- src/plugins/event_annotation/public/plugin.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/event_annotation/public/event_annotation_service/service.tsx b/src/plugins/event_annotation/public/event_annotation_service/service.tsx index a851644d42cb3..3d81ea6a3e3a6 100644 --- a/src/plugins/event_annotation/public/event_annotation_service/service.tsx +++ b/src/plugins/event_annotation/public/event_annotation_service/service.tsx @@ -7,7 +7,7 @@ */ import { EventAnnotationServiceType } from './types'; -import { defaultAnnotationColor } from '..'; +import { defaultAnnotationColor } from './helpers'; export function hasIcon(icon: string | undefined): icon is string { return icon != null && icon !== 'empty'; diff --git a/src/plugins/event_annotation/public/plugin.ts b/src/plugins/event_annotation/public/plugin.ts index cfe7df4018ab5..83cdc0546a7f5 100644 --- a/src/plugins/event_annotation/public/plugin.ts +++ b/src/plugins/event_annotation/public/plugin.ts @@ -34,6 +34,6 @@ export class EventAnnotationPlugin } public start(): EventAnnotationPluginStart { - return this.eventAnnotationService!; + return this.eventAnnotationService; } } From b8f327b73bddc9688e6b094b9e43f8f5cde13c34 Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 23 Mar 2022 11:26:05 +0100 Subject: [PATCH 44/47] date cr --- .../lens/public/xy_visualization/annotations/helpers.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx index e64b1c31b4331..321090c94241a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/helpers.tsx @@ -28,8 +28,8 @@ import { generateId } from '../../id_generator'; import { defaultAnnotationColor } from '../../../../../../src/plugins/event_annotation/public'; import { defaultAnnotationLabel } from './config_panel'; -const MAX_DATE = Number(new Date(8640000000000000)); -const MIN_DATE = Number(new Date(-8640000000000000)); +const MAX_DATE = 8640000000000000; +const MIN_DATE = -8640000000000000; export function getStaticDate( dataLayers: XYDataLayerConfig[], From 0771829c6aaaba6723e2218d7ee3f11346237f0c Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 23 Mar 2022 16:39:56 +0100 Subject: [PATCH 45/47] remove DimensionEditorSection --- .../components/dimension_editor.tsx | 5 ++-- .../config_panel/layer_panel.scss | 4 +++ .../editor_frame/config_panel/layer_panel.tsx | 22 ++++++++------- .../dimension_editor.tsx | 5 ++-- x-pack/plugins/lens/public/index.ts | 1 - .../metric_visualization/dimension_editor.tsx | 5 ++-- .../lens/public/pie_visualization/toolbar.tsx | 17 +++++------ .../shared_components/dimension_section.scss | 23 +++++++++++++-- .../shared_components/dimension_section.tsx | 20 +++++++++---- .../visualizations/gauge/dimension_editor.tsx | 5 ++-- .../annotations/config_panel/index.tsx | 28 ++++++++----------- .../xy_config_panel/dimension_editor.tsx | 10 +++---- .../xy_config_panel/reference_line_panel.tsx | 7 +++-- .../choropleth_chart/region_key_editor.tsx | 5 ++-- 14 files changed, 87 insertions(+), 70 deletions(-) diff --git a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx index 559712697700e..fc0fa9b5d8ac6 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/components/dimension_editor.tsx @@ -30,7 +30,6 @@ import { useDebouncedValue, PalettePanelContainer, findMinMaxByColumnId, - DimensionEditorSection, } from '../../shared_components/'; import type { ColumnState } from '../../../common/expressions'; import { @@ -129,7 +128,7 @@ export function TableDimensionEditor( const displayStops = applyPaletteParams(props.paletteService, activePalette, currentMinMax); return ( - + <> )} - + ); } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss index 3458232181439..343cd746ba2ac 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.scss @@ -117,6 +117,10 @@ color: $euiTextSubduedColor; } +.lnsLayerPanel__styleEditor { + padding: $euiSize; +} + .lnsLayerPanel__colorIndicator { margin-left: $euiSizeS; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx index 8f73dda517e12..366d3f93bf842 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/config_panel/layer_panel.tsx @@ -639,16 +639,18 @@ export function LayerPanel( !activeDimension.isNew && activeVisualization.renderDimensionEditor && activeGroup?.enableDimensionEditor && ( - +
+ +
)}

} diff --git a/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx b/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx index 6a4cf91c47fd4..915c48a5ca97b 100644 --- a/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/heatmap_visualization/dimension_editor.tsx @@ -20,7 +20,6 @@ import { CustomizablePalette, FIXED_PROGRESSION, PalettePanelContainer, - DimensionEditorSection, } from '../shared_components/'; import './dimension_editor.scss'; import type { HeatmapVisualizationState } from './types'; @@ -47,7 +46,7 @@ export function HeatmapDimensionEditor( ); return ( - + <> - + ); } diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index cb18cfc2c6a26..a86ba194bf4db 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -72,7 +72,6 @@ export type { } from './indexpattern_datasource/types'; export type { LensEmbeddableInput } from './embeddable'; export { layerTypes } from '../common'; -export { DimensionEditorSection } from './shared_components/dimension_section'; export type { LensPublicStart, LensPublicSetup } from './plugin'; diff --git a/x-pack/plugins/lens/public/metric_visualization/dimension_editor.tsx b/x-pack/plugins/lens/public/metric_visualization/dimension_editor.tsx index d05ed14815795..83faac5cc8026 100644 --- a/x-pack/plugins/lens/public/metric_visualization/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/dimension_editor.tsx @@ -23,7 +23,6 @@ import { applyPaletteParams, CustomizablePalette, CUSTOM_PALETTE, - DimensionEditorSection, FIXED_PROGRESSION, PalettePanelContainer, } from '../shared_components'; @@ -77,7 +76,7 @@ export function MetricDimensionEditor( const displayStops = applyPaletteParams(props.paletteService, activePalette, currentMinMax); return ( - + <> )} - + ); } diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index 1552f8bccddd0..d1f16ac5f9c41 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -26,7 +26,6 @@ import { ToolbarPopover, LegendSettingsPopover, useDebouncedValue, - DimensionEditorSection, PalettePicker, } from '../shared_components'; import { getDefaultVisualValuesForLayer } from '../shared_components/datasource_default_values'; @@ -303,14 +302,12 @@ export function DimensionEditor( } ) { return ( - - { - props.setState({ ...props.state, palette: newPalette }); - }} - /> - + { + props.setState({ ...props.state, palette: newPalette }); + }} + /> ); } diff --git a/x-pack/plugins/lens/public/shared_components/dimension_section.scss b/x-pack/plugins/lens/public/shared_components/dimension_section.scss index 445eab84c9ca6..7781c91785d67 100644 --- a/x-pack/plugins/lens/public/shared_components/dimension_section.scss +++ b/x-pack/plugins/lens/public/shared_components/dimension_section.scss @@ -1,7 +1,24 @@ .lnsDimensionEditorSection { - padding: $euiSize; + padding-top: $euiSize; + padding-bottom: $euiSize; } -.lnsDimensionEditorSection--hasBorder { - border-top: 1px solid $euiColorLightShade; +.lnsDimensionEditorSection:first-child { + padding-top: 0; } + +.lnsDimensionEditorSection:first-child .lnsDimensionEditorSection__border { + display: none; +} + +.lnsDimensionEditorSection__border { + position: relative; + &:before { + content: ''; + position: absolute; + top: -$euiSize; + right: -$euiSize; + left: -$euiSize; + border-top: 1px solid $euiColorLightShade; + } +} \ No newline at end of file diff --git a/x-pack/plugins/lens/public/shared_components/dimension_section.tsx b/x-pack/plugins/lens/public/shared_components/dimension_section.tsx index cfc6b2e20f0a1..d56e08db4b037 100644 --- a/x-pack/plugins/lens/public/shared_components/dimension_section.tsx +++ b/x-pack/plugins/lens/public/shared_components/dimension_section.tsx @@ -4,18 +4,26 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import { EuiTitle } from '@elastic/eui'; import React from 'react'; import './dimension_section.scss'; export const DimensionEditorSection = ({ children, - hasBorder, + title, }: { + title?: string; children?: React.ReactNode | React.ReactNode[]; - hasBorder?: boolean; }) => { - const classNames = hasBorder - ? 'lnsDimensionEditorSection--hasBorder lnsDimensionEditorSection' - : 'lnsDimensionEditorSection'; - return
{children}
; + return ( +
+
+ {title && ( + +

{title}

+
+ )} + {children} +
+ ); }; diff --git a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx index b8193588be639..1a400d803fe9d 100644 --- a/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx +++ b/x-pack/plugins/lens/public/visualizations/gauge/dimension_editor.tsx @@ -31,7 +31,6 @@ import { applyPaletteParams, CustomizablePalette, CUSTOM_PALETTE, - DimensionEditorSection, FIXED_PROGRESSION, PalettePanelContainer, TooltipWrapper, @@ -85,7 +84,7 @@ export function GaugeDimensionEditor( const togglePalette = () => setIsPaletteOpen(!isPaletteOpen); return ( - + <> )} - + ); } diff --git a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx index 6d5e0915a86c2..4cdb2d6c7e0b9 100644 --- a/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/annotations/config_panel/index.tsx @@ -8,7 +8,7 @@ import React, { useCallback } from 'react'; import './index.scss'; import { i18n } from '@kbn/i18n'; -import { EuiDatePicker, EuiFormRow, EuiSwitch, EuiSwitchEvent, EuiTitle } from '@elastic/eui'; +import { EuiDatePicker, EuiFormRow, EuiSwitch, EuiSwitchEvent } from '@elastic/eui'; import type { PaletteRegistry } from 'src/plugins/charts/public'; import moment from 'moment'; import { EventAnnotationConfig } from 'src/plugins/event_annotation/common/types'; @@ -68,14 +68,11 @@ export const AnnotationsPanel = ( return ( <> - - -

- {i18n.translate('xpack.lens.xyChart.placement', { - defaultMessage: 'Placement', - })} -

-
+ { @@ -93,14 +90,11 @@ export const AnnotationsPanel = ( })} /> - - -

- {i18n.translate('xpack.lens.xyChart.appearance', { - defaultMessage: 'Appearance', - })} -

-
+ = T extends Array ? P : T; @@ -79,7 +79,7 @@ export function DimensionEditor( if (props.groupId === 'breakdown') { return ( - + <> - + ); } const isHorizontal = isHorizontalChart(state.layers); return ( - + <> - + ); } diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx index b8417c8079524..78020034c3d43 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel/reference_line_panel.tsx @@ -17,7 +17,7 @@ import { FillStyle, XYReferenceLineLayerConfig } from '../../../common/expressio import { ColorPicker } from './color_picker'; import { updateLayer } from '.'; -import { DimensionEditorSection, useDebouncedValue } from '../../shared_components'; +import { useDebouncedValue } from '../../shared_components'; import { idPrefix } from './dimension_editor'; import { isHorizontalChart } from '../state_helpers'; import { MarkerDecorationSettings } from './shared/marker_decoration_settings'; @@ -69,7 +69,8 @@ export const ReferenceLinePanel = ( ); return ( - + <> + {' '} - + ); }; diff --git a/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx b/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx index 14a1814ee3c34..6d0f516e5642c 100644 --- a/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx +++ b/x-pack/plugins/maps/public/lens/choropleth_chart/region_key_editor.tsx @@ -11,7 +11,6 @@ import { EuiComboBox, EuiComboBoxOptionOption, EuiFormRow } from '@elastic/eui'; import type { FileLayer } from '@elastic/ems-client'; import { ChoroplethChartState } from './types'; import { EMSFileSelect } from '../../components/ems_file_select'; -import { DimensionEditorSection } from '../../../../lens/public'; interface Props { emsFileLayers: FileLayer[]; @@ -67,14 +66,14 @@ export function RegionKeyEditor(props: Props) { ); } return ( - + <> {emsFieldSelect} - + ); } From 028886a475fe607352b6c93528e591ea2e2ba9da Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 23 Mar 2022 16:50:35 +0100 Subject: [PATCH 46/47] change to emptyShade for traingle fill --- x-pack/plugins/lens/public/app_plugin/app.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.scss b/x-pack/plugins/lens/public/app_plugin/app.scss index 64437fb92a16d..d8618bf7d9176 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.scss +++ b/x-pack/plugins/lens/public/app_plugin/app.scss @@ -43,7 +43,7 @@ } .lensAnnotationIconFill { - fill: $euiColorLightestShade; + fill: $emptyShade; } // Less-than-ideal styles to add a vertical divider after this button. Consider restructuring markup for better semantics and styling options in the future. From 53cc6327dda4fe1d43b0d85d000bded0e211cbcb Mon Sep 17 00:00:00 2001 From: Marta Bondyra Date: Wed, 23 Mar 2022 16:56:45 +0100 Subject: [PATCH 47/47] Update x-pack/plugins/lens/public/app_plugin/app.scss --- x-pack/plugins/lens/public/app_plugin/app.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/lens/public/app_plugin/app.scss b/x-pack/plugins/lens/public/app_plugin/app.scss index d8618bf7d9176..5e859c1a93818 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.scss +++ b/x-pack/plugins/lens/public/app_plugin/app.scss @@ -43,7 +43,7 @@ } .lensAnnotationIconFill { - fill: $emptyShade; + fill: $euiColorGhost; } // Less-than-ideal styles to add a vertical divider after this button. Consider restructuring markup for better semantics and styling options in the future.