diff --git a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx index 4152212e51fea..dbe6fcafd0ed0 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/form_based.tsx @@ -37,7 +37,6 @@ import type { IndexPatternField, IndexPattern, IndexPatternRef, - DatasourceLayerSettingsProps, DataSourceInfo, UserMessage, FrameDatasourceAPI, @@ -429,10 +428,7 @@ export function getFormBasedDatasource({ toExpression: (state, layerId, indexPatterns, dateRange, searchSessionId) => toExpression(state, layerId, indexPatterns, uiSettings, dateRange, searchSessionId), - renderLayerSettings( - domElement: Element, - props: DatasourceLayerSettingsProps - ) { + renderLayerSettings(domElement, props) { render( diff --git a/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx index 38da5475e22e1..c79bc41dd7dd7 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/layer_settings.tsx @@ -109,101 +109,86 @@ export function LayerSettingsPanel({ setState, layerId, }: DatasourceLayerSettingsProps) { - const { euiTheme } = useEuiTheme(); const isSamplingValueDisabled = !isSamplingValueEnabled(state.layers[layerId]); const currentValue = isSamplingValueDisabled ? samplingValues[samplingValues.length - 1] : state.layers[layerId].sampling; return ( -
- -

- {i18n.translate('xpack.lens.indexPattern.layerSettings.headingData', { - defaultMessage: 'Data', - })} -

-
- - -

- - - - ), - }} - /> -

- - } - label={ - <> - {i18n.translate('xpack.lens.indexPattern.randomSampling.label', { - defaultMessage: 'Sampling', - })}{' '} - + +

+ + + + ), + }} + /> +

+ + } + label={ + <> + {i18n.translate('xpack.lens.indexPattern.randomSampling.label', { + defaultMessage: 'Sampling', + })}{' '} + + - - - - } - > - { - setState({ - ...state, - layers: { - ...state.layers, - [layerId]: { - ...state.layers[layerId], - sampling: newSamplingValue, - }, + size="s" + /> +
+ + } + > + { + setState({ + ...state, + layers: { + ...state.layers, + [layerId]: { + ...state.layers[layerId], + sampling: newSamplingValue, }, - }); - }} - /> -
-
+ }, + }); + }} + /> + ); } diff --git a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx index 1de4d0844245f..5832f8094a856 100644 --- a/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx +++ b/x-pack/plugins/lens/public/datasources/form_based/layerpanel.tsx @@ -8,10 +8,12 @@ import React from 'react'; import { I18nProvider } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; +import { useEuiTheme } from '@elastic/eui'; import { DatasourceLayerPanelProps } from '../../types'; import { FormBasedPrivateState } from './types'; import { ChangeIndexPattern } from '../../shared_components/dataview_picker/dataview_picker'; import { getSamplingValue } from './utils'; +import { RandomSamplingIcon } from './sampling_icon'; export interface FormBasedLayerPanelProps extends DatasourceLayerPanelProps { state: FormBasedPrivateState; @@ -25,6 +27,7 @@ export function LayerPanel({ dataViews, }: FormBasedLayerPanelProps) { const layer = state.layers[layerId]; + const { euiTheme } = useEuiTheme(); const indexPattern = dataViews.indexPatterns[layer.indexPatternId]; const notFoundTitleLabel = i18n.translate('xpack.lens.layerPanel.missingDataView', { @@ -38,6 +41,26 @@ export function LayerPanel({ }; }); + const samplingValue = getSamplingValue(layer); + const extraIconLabelProps = + samplingValue !== 1 + ? { + icon: { + component: ( + + ), + value: `${samplingValue * 100}%`, + tooltipValue: i18n.translate('xpack.lens.indexPattern.randomSamplingInfo', { + defaultMessage: '{value}% sampling', + values: { + value: samplingValue * 100, + }, + }), + 'data-test-subj': 'lnsChangeIndexPatternSamplingInfo', + }, + } + : {}; + return ( + activeVisualization.hasLayerSettings?.({ + layerId, + state: visualizationState, + frame: props.framePublicAPI, + }) || { data: false, appearance: false }, + [activeVisualization, layerId, props.framePublicAPI, visualizationState] + ); + const compatibleActions = useMemo( () => [ @@ -341,11 +351,7 @@ export function LayerPanel( isOnlyLayer, isTextBasedLanguage, hasLayerSettings: Boolean( - (activeVisualization.hasLayerSettings?.({ - layerId, - state: visualizationState, - frame: props.framePublicAPI, - }) && + (Object.values(visualizationLayerSettings).some(Boolean) && activeVisualization.renderLayerSettings) || layerDatasource?.renderLayerSettings ), @@ -364,8 +370,8 @@ export function LayerPanel( layerIndex, onCloneLayer, onRemoveLayer, - props.framePublicAPI, updateVisualization, + visualizationLayerSettings, visualizationState, ] ); @@ -682,15 +688,56 @@ export function LayerPanel( >
+ {layerDatasource?.renderLayerSettings || visualizationLayerSettings.data ? ( + +

+ {i18n.translate('xpack.lens.editorFrame.layerSettings.headingData', { + defaultMessage: 'Data', + })} +

+
+ ) : null} {layerDatasource?.renderLayerSettings && ( <> - )} + {layerDatasource?.renderLayerSettings && visualizationLayerSettings.data ? ( + + ) : null} + {activeVisualization?.renderLayerSettings && visualizationLayerSettings.data ? ( + + ) : null} + {visualizationLayerSettings.appearance ? ( + +

+ {i18n.translate('xpack.lens.editorFrame.layerSettings.headingAppearance', { + defaultMessage: 'Appearance', + })} +

+
+ ) : null} {activeVisualization?.renderLayerSettings && ( )} diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx b/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx index 6467cbcb58494..9d55284cc36c8 100644 --- a/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/dataview_picker.tsx @@ -7,109 +7,10 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; -import { - EuiFlexGroup, - EuiFlexItem, - EuiPopover, - EuiPopoverTitle, - EuiSelectableProps, - EuiTextColor, - EuiToolTip, - useEuiTheme, -} from '@elastic/eui'; +import { EuiPopover, EuiPopoverTitle, EuiSelectableProps } from '@elastic/eui'; import { DataViewsList } from '@kbn/unified-search-plugin/public'; -import { css } from '@emotion/react'; import { type IndexPatternRef } from '../../types'; -import { type ToolbarButtonProps, ToolbarButton } from './toolbar_button'; -import { RandomSamplingIcon } from './sampling_icon'; - -export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & { - label: string; - title?: string; - isDisabled?: boolean; - samplingValue?: number; -}; - -function TriggerButton({ - label, - title, - togglePopover, - isMissingCurrent, - samplingValue, - ...rest -}: ChangeIndexPatternTriggerProps & - ToolbarButtonProps & { - togglePopover: () => void; - isMissingCurrent?: boolean; - }) { - const { euiTheme } = useEuiTheme(); - // be careful to only add color with a value, otherwise it will fallbacks to "primary" - const colorProp = isMissingCurrent - ? { - color: 'danger' as const, - } - : {}; - const content = - samplingValue != null && samplingValue !== 1 ? ( - - - {label} - - - - - - - - - - {samplingValue * 100}% - - - - - - - ) : ( - label - ); - return ( - togglePopover()} - fullWidth - {...colorProp} - {...rest} - textProps={{ style: { width: '100%' } }} - > - {content} - - ); -} +import { type ChangeIndexPatternTriggerProps, TriggerButton } from './trigger'; export function ChangeIndexPattern({ indexPatternRefs, diff --git a/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx b/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx new file mode 100644 index 0000000000000..038b1d5aec960 --- /dev/null +++ b/x-pack/plugins/lens/public/shared_components/dataview_picker/trigger.tsx @@ -0,0 +1,101 @@ +/* + * 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 { useEuiTheme, EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiTextColor } from '@elastic/eui'; +import { css } from '@emotion/react'; +import React from 'react'; +import { ToolbarButton, ToolbarButtonProps } from './toolbar_button'; + +interface TriggerLabelProps { + label: string; + icon?: { + component: React.ReactElement; + value?: string; + tooltipValue?: string; + 'data-test-subj': string; + }; +} + +export type ChangeIndexPatternTriggerProps = ToolbarButtonProps & + TriggerLabelProps & { + label: string; + title?: string; + isDisabled?: boolean; + }; + +function TriggerLabel({ label, icon }: TriggerLabelProps) { + const { euiTheme } = useEuiTheme(); + if (!icon) { + return <>{label}; + } + return ( + + + {label} + + + + + {icon.component} + {icon.value ? ( + + {icon.value} + + ) : null} + + + + + ); +} + +export function TriggerButton({ + label, + title, + togglePopover, + isMissingCurrent, + icon, + ...rest +}: ChangeIndexPatternTriggerProps & { + togglePopover: () => void; + isMissingCurrent?: boolean; +}) { + // be careful to only add color with a value, otherwise it will fallbacks to "primary" + const colorProp = isMissingCurrent + ? { + color: 'danger' as const, + } + : {}; + return ( + togglePopover()} + fullWidth + {...colorProp} + {...rest} + textProps={{ style: { width: '100%' } }} + > + + + ); +} diff --git a/x-pack/plugins/lens/public/types.ts b/x-pack/plugins/lens/public/types.ts index 786d75816cafa..746558b0e2560 100644 --- a/x-pack/plugins/lens/public/types.ts +++ b/x-pack/plugins/lens/public/types.ts @@ -1189,11 +1189,11 @@ export interface Visualization { /** * Allows the visualization to announce whether or not it has any settings to show */ - hasLayerSettings?: (props: VisualizationConfigProps) => boolean; + hasLayerSettings?: (props: VisualizationConfigProps) => Record<'data' | 'appearance', boolean>; renderLayerSettings?: ( domElement: Element, - props: VisualizationLayerSettingsProps + props: VisualizationLayerSettingsProps & { section: 'data' | 'appearance' } ) => ((cleanupElement: Element) => void) | void; /** diff --git a/x-pack/plugins/lens/public/visualizations/partition/layer_settings.test.tsx b/x-pack/plugins/lens/public/visualizations/partition/layer_settings.test.tsx index 9acedbc9b8dd2..2fed483cddfaf 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/layer_settings.test.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/layer_settings.test.tsx @@ -25,12 +25,15 @@ describe('layer settings', () => { }); const layerId = 'layer-id'; - const props: VisualizationLayerSettingsProps = { + const props: VisualizationLayerSettingsProps & { + section: 'data' | 'appearance'; + } = { setState: jest.fn(), layerId, state: getState(false), frame: {} as FramePublicAPI, panelRef: {} as React.MutableRefObject, + section: 'data', }; it('toggles multiple metrics', () => { @@ -90,5 +93,9 @@ describe('layer settings', () => { ).isEmptyRender() ).toBeTruthy(); }); + + test('should not render anything for the appearance section', () => { + expect(shallow().isEmptyRender()); + }); }); }); diff --git a/x-pack/plugins/lens/public/visualizations/partition/layer_settings.tsx b/x-pack/plugins/lens/public/visualizations/partition/layer_settings.tsx index 39da4fbfe0595..6f13800a91a2c 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/layer_settings.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/layer_settings.tsx @@ -12,7 +12,12 @@ import { PieChartTypes } from '../../../common/constants'; import { PieVisualizationState } from '../..'; import { VisualizationLayerSettingsProps } from '../../types'; -export function LayerSettings(props: VisualizationLayerSettingsProps) { +export function LayerSettings( + props: VisualizationLayerSettingsProps & { section: 'data' | 'appearance' } +) { + if (props.section === 'appearance') { + return null; + } if (props.state.shape === PieChartTypes.MOSAIC) { return null; } @@ -24,29 +29,33 @@ export function LayerSettings(props: VisualizationLayerSettingsProps - - { - props.setState({ - ...props.state, - layers: props.state.layers.map((layer) => - layer.layerId !== props.layerId - ? layer - : { - ...layer, - allowMultipleMetrics: !layer.allowMultipleMetrics, - } - ), - }); - }} - /> - - + + { + props.setState({ + ...props.state, + layers: props.state.layers.map((layer) => + layer.layerId !== props.layerId + ? layer + : { + ...layer, + allowMultipleMetrics: !layer.allowMultipleMetrics, + } + ), + }); + }} + /> + ); } diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts index 02932a0aa5e1c..3a4b0545a42ea 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.test.ts @@ -617,10 +617,10 @@ describe('pie_visualization', () => { }); }); - it.each(Object.values(PieChartTypes).filter((type) => type !== 'mosaic'))( + it.each(Object.values(PieChartTypes).filter((type) => type !== PieChartTypes.MOSAIC))( '%s adds fake dimension', (type) => { - const state = { ...getExampleState(), type }; + const state = { ...getExampleState(), shape: type }; state.layers[0].metrics.push('1', '2'); state.layers[0].allowMultipleMetrics = true; expect( @@ -645,4 +645,21 @@ describe('pie_visualization', () => { } ); }); + + describe('layer settings', () => { + describe('hasLayerSettings', () => { + it('should have data settings for all partition chart types but mosaic', () => { + for (const type of Object.values(PieChartTypes)) { + const state = { ...getExampleState(), shape: type }; + expect( + pieVisualization.hasLayerSettings?.({ + state, + frame: mockFrame(), + layerId: state.layers[0].layerId, + }) + ).toEqual({ data: type !== PieChartTypes.MOSAIC, appearance: false }); + } + }); + }); + }); }); diff --git a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx index 8340b5a5e254b..da52c6efc105b 100644 --- a/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/partition/visualization.tsx @@ -504,7 +504,7 @@ export const getPieVisualization = ({ }, hasLayerSettings(props) { - return props.state.shape !== 'mosaic'; + return { data: props.state.shape !== PieChartTypes.MOSAIC, appearance: false }; }, renderLayerSettings(domElement, props) { diff --git a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions.ts b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions.ts index 68938fbee5211..cb3afbcb14c91 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/annotations/actions.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/annotations/actions.ts @@ -5,13 +5,10 @@ * 2.0. */ -import { i18n } from '@kbn/i18n'; import type { LayerActionFromVisualization } from '../../../types'; import type { XYState, XYAnnotationLayerConfig } from '../types'; -export const IGNORE_GLOBAL_FILTERS_ACTION_ID = 'ignoreGlobalFilters'; -export const KEEP_GLOBAL_FILTERS_ACTION_ID = 'keepGlobalFilters'; - +// Leaving the stub for annotation groups export const createAnnotationActions = ({ state, layer, @@ -21,33 +18,5 @@ export const createAnnotationActions = ({ layer: XYAnnotationLayerConfig; layerIndex: number; }): LayerActionFromVisualization[] => { - const label = !layer.ignoreGlobalFilters - ? i18n.translate('xpack.lens.xyChart.annotations.ignoreGlobalFiltersLabel', { - defaultMessage: 'Ignore global filters', - }) - : i18n.translate('xpack.lens.xyChart.annotations.keepGlobalFiltersLabel', { - defaultMessage: 'Keep global filters', - }); - return [ - { - id: !layer.ignoreGlobalFilters - ? IGNORE_GLOBAL_FILTERS_ACTION_ID - : KEEP_GLOBAL_FILTERS_ACTION_ID, - displayName: label, - description: !layer.ignoreGlobalFilters - ? i18n.translate('xpack.lens.xyChart.annotations.ignoreGlobalFiltersDescription', { - defaultMessage: - 'All the dimensions configured in this layer ignore filters defined at kibana level.', - }) - : i18n.translate('xpack.lens.xyChart.annotations.keepGlobalFiltersDescription', { - defaultMessage: - 'All the dimensions configured in this layer respect filters defined at kibana level.', - }), - icon: !layer.ignoreGlobalFilters ? 'filterIgnore' : 'filter', - isCompatible: true, - 'data-test-subj': !layer.ignoreGlobalFilters - ? 'lnsXY_annotationLayer_ignoreFilters' - : 'lnsXY_annotationLayer_keepFilters', - }, - ]; + return []; }; diff --git a/x-pack/plugins/lens/public/visualizations/xy/layer_settings.tsx b/x-pack/plugins/lens/public/visualizations/xy/layer_settings.tsx new file mode 100644 index 0000000000000..55091aa0d1d40 --- /dev/null +++ b/x-pack/plugins/lens/public/visualizations/xy/layer_settings.tsx @@ -0,0 +1,53 @@ +/* + * 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 { EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import type { VisualizationLayerSettingsProps } from '../../types'; +import type { XYState } from './types'; +import { isAnnotationsLayer } from './visualization_helpers'; + +export function LayerSettings({ + state, + setState, + section, + layerId, +}: VisualizationLayerSettingsProps & { section: 'data' | 'appearance' }) { + if (section === 'appearance') { + return null; + } + const layer = state.layers.find((l) => l.layerId === layerId); + if (!layer || !isAnnotationsLayer(layer)) { + return null; + } + return ( + + { + const layerIndex = state.layers.findIndex((l) => l === layer); + const newLayer = { ...layer, ignoreGlobalFilters: !layer.ignoreGlobalFilters }; + const newLayers = [...state.layers]; + newLayers[layerIndex] = newLayer; + setState({ ...state, layers: newLayers }); + }} + compressed + /> + + ); +} diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts index 159014b043aec..9b82ab8c0d90e 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.test.ts @@ -37,7 +37,6 @@ import { DataViewsState } from '../../state_management'; import { createMockedIndexPattern } from '../../datasources/form_based/mocks'; import { createMockDataViewsState } from '../../data_views_service/mocks'; import { unifiedSearchPluginMock } from '@kbn/unified-search-plugin/public/mocks'; -import { KEEP_GLOBAL_FILTERS_ACTION_ID } from './annotations/actions'; import { layerTypes, Visualization } from '../..'; const DATE_HISTORGRAM_COLUMN_ID = 'date_histogram_column'; @@ -3022,7 +3021,7 @@ describe('xy_visualization', () => { ); }); - it('should return one action for an annotation layer', () => { + it('should return no action for an annotation layer', () => { const baseState = exampleState(); expect( xyVisualization.getSupportedActionsForLayer?.('annotation', { @@ -3038,53 +3037,64 @@ describe('xy_visualization', () => { }, ], }) - ).toEqual([ - expect.objectContaining({ - displayName: 'Keep global filters', - description: - 'All the dimensions configured in this layer respect filters defined at kibana level.', - icon: 'filter', - isCompatible: true, - 'data-test-subj': 'lnsXY_annotationLayer_keepFilters', - }), - ]); + ).toHaveLength(0); }); + }); - it('should handle an annotation action', () => { - const baseState = exampleState(); - const state = { - ...baseState, - layers: [ - ...baseState.layers, - { - layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, - annotations: [exampleAnnotation2], - ignoreGlobalFilters: true, - indexPatternId: 'myIndexPattern', - }, - ], - }; + describe('layer settings', () => { + describe('hasLayerSettings', () => { + it('should expose no settings for a data or reference lines layer', () => { + const baseState = exampleState(); + expect( + xyVisualization.hasLayerSettings?.({ + state: baseState, + frame: createMockFramePublicAPI(), + layerId: 'first', + }) + ).toEqual({ data: false, appearance: false }); - const newState = xyVisualization.onLayerAction!( - 'annotation', - KEEP_GLOBAL_FILTERS_ACTION_ID, - state - ); + expect( + xyVisualization.hasLayerSettings?.({ + state: { + ...baseState, + layers: [ + ...baseState.layers, + { + layerId: 'referenceLine', + layerType: layerTypes.REFERENCELINE, + accessors: [], + yConfig: [{ axisMode: 'left', forAccessor: 'a' }], + }, + ], + }, + frame: createMockFramePublicAPI(), + layerId: 'referenceLine', + }) + ).toEqual({ data: false, appearance: false }); + }); - expect(newState).toEqual( - expect.objectContaining({ - layers: expect.arrayContaining([ - { - layerId: 'annotation', - layerType: layerTypes.ANNOTATIONS, - annotations: [exampleAnnotation2], - ignoreGlobalFilters: false, - indexPatternId: 'myIndexPattern', + it('should expose data settings for an annotation layer', () => { + const baseState = exampleState(); + expect( + xyVisualization.hasLayerSettings?.({ + state: { + ...baseState, + layers: [ + ...baseState.layers, + { + layerId: 'annotation', + layerType: layerTypes.ANNOTATIONS, + annotations: [exampleAnnotation2], + ignoreGlobalFilters: true, + indexPatternId: 'myIndexPattern', + }, + ], }, - ]), - }) - ); + frame: createMockFramePublicAPI(), + layerId: 'annotation', + }) + ).toEqual({ data: true, appearance: false }); + }); }); }); }); diff --git a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx index 274b82e5a1cf4..2f5df56c7b42d 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/visualization.tsx @@ -49,7 +49,6 @@ import { type XYDataLayerConfig, type SeriesType, type PersistedState, - type XYAnnotationLayerConfig, visualizationTypes, } from './types'; import { @@ -103,12 +102,8 @@ import { AnnotationsPanel } from './xy_config_panel/annotations_config_panel'; import { DimensionTrigger } from '../../shared_components/dimension_trigger'; import { defaultAnnotationLabel } from './annotations/helpers'; import { onDropForVisualization } from '../../editor_frame_service/editor_frame/config_panel/buttons/drop_targets_utils'; -import { - createAnnotationActions, - IGNORE_GLOBAL_FILTERS_ACTION_ID, - KEEP_GLOBAL_FILTERS_ACTION_ID, -} from './annotations/actions'; import { IgnoredGlobalFiltersEntries } from './info_badges'; +import { LayerSettings } from './layer_settings'; const XY_ID = 'lnsXY'; export const getXyVisualization = ({ @@ -263,31 +258,27 @@ export const getXyVisualization = ({ ]; }, - getSupportedActionsForLayer(layerId, state) { - const layerIndex = state.layers.findIndex((l) => l.layerId === layerId); - const layer = state.layers[layerIndex]; - const actions = []; - if (isAnnotationsLayer(layer)) { - actions.push(...createAnnotationActions({ state, layerIndex, layer })); - } - return actions; + getSupportedActionsForLayer() { + return []; }, - onLayerAction(layerId, actionId, state) { - if ([IGNORE_GLOBAL_FILTERS_ACTION_ID, KEEP_GLOBAL_FILTERS_ACTION_ID].includes(actionId)) { - return { - ...state, - layers: state.layers.map((layer) => - layer.layerId === layerId - ? { - ...layer, - ignoreGlobalFilters: !(layer as XYAnnotationLayerConfig).ignoreGlobalFilters, - } - : layer - ), - }; - } + hasLayerSettings({ state, layerId: currentLayerId }) { + const layer = state.layers?.find(({ layerId }) => layerId === currentLayerId); + return { data: Boolean(layer && isAnnotationsLayer(layer)), appearance: false }; + }, + + renderLayerSettings(domElement, props) { + render( + + + + + , + domElement + ); + }, + onLayerAction(layerId, actionId, state) { return state; }, diff --git a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx index 819dfe13c2ba2..1983094f483b9 100644 --- a/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx +++ b/x-pack/plugins/lens/public/visualizations/xy/xy_config_panel/layer_header.tsx @@ -7,9 +7,17 @@ import React, { useState } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiIcon, EuiPopover, EuiSelectable, EuiText, EuiPopoverTitle } from '@elastic/eui'; +import { + EuiIcon, + EuiPopover, + EuiSelectable, + EuiText, + EuiPopoverTitle, + useEuiTheme, +} from '@elastic/eui'; import { ToolbarButton } from '@kbn/kibana-react-plugin/public'; import { IconChartBarReferenceLine, IconChartBarAnnotations } from '@kbn/chart-icons'; +import { css } from '@emotion/react'; import type { VisualizationLayerHeaderContentProps, VisualizationLayerWidgetProps, @@ -71,6 +79,7 @@ function AnnotationLayerHeaderContent({ layerId, onChangeIndexPattern, }: VisualizationLayerHeaderContentProps) { + const { euiTheme } = useEuiTheme(); const notFoundTitleLabel = i18n.translate('xpack.lens.layerPanel.missingDataView', { defaultMessage: 'Data view not found', }); @@ -78,6 +87,25 @@ function AnnotationLayerHeaderContent({ const layer = state.layers[layerIndex] as XYAnnotationLayerConfig; const currentIndexPattern = frame.dataViews.indexPatterns[layer.indexPatternId]; + const extraIconLabelProps = !layer.ignoreGlobalFilters + ? {} + : { + icon: { + component: ( + + ), + tooltipValue: i18n.translate('xpack.lens.layerPanel.ignoreGlobalFilters', { + defaultMessage: 'Ignore global filters', + }), + 'data-test-subj': 'lnsChangeIndexPatternIgnoringFilters', + }, + }; return ( { + it('should add an annotation layer and settings shoud be available with ignore filters', async () => { // configure a date histogram await PageObjects.lens.configureDimension({ dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', @@ -65,10 +65,16 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); // add annotation layer await PageObjects.lens.createLayer('annotations'); + + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(true); + await PageObjects.lens.openLayerContextMenu(1); - await testSubjects.existOrFail('lnsXY_annotationLayer_keepFilters'); - // layer settings not available - await testSubjects.missingOrFail('lnsLayerSettings'); + await testSubjects.click('lnsLayerSettings'); + // annotations settings have only ignore filters + await testSubjects.click('lnsXY-layerSettings-ignoreGlobalFilters'); + // now close the panel and check the dataView picker has no icon + await testSubjects.click('lns-indexPattern-dimensionContainerBack'); + expect(await testSubjects.exists('lnsChangeIndexPatternIgnoringFilters')).to.be(false); }); it('should add a new visualization layer and disable the sampling if max operation is chosen', async () => {