Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Lens] Add control for global filters to the annotation layer menu #141615

Merged
merged 21 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
617472a
:sparkles: Add ignore global filters feature
dej611 Sep 8, 2022
47055d6
:white_check_mark: Fix tests
dej611 Sep 9, 2022
164fa3d
:wrench: Remove unused translations
dej611 Sep 9, 2022
c08f0bf
:recycle: Make it simpler
dej611 Sep 9, 2022
05832c8
:white_check_mark: Fix test
dej611 Sep 9, 2022
0d3ff0c
:wrench: slighlty increase bundle limit size
dej611 Sep 9, 2022
1406805
Merge remote-tracking branch 'upstream/main' into feature/140204
dej611 Sep 9, 2022
a550482
:wrench: Forward layer flag to event config
dej611 Sep 15, 2022
841a218
Merge remote-tracking branch 'upstream/main' into feature/140204-ui
dej611 Sep 23, 2022
3e724a3
:recycle: refactor actions code to include viz custom actions
dej611 Sep 23, 2022
f60d21e
:bug: Flip the logic
dej611 Sep 23, 2022
2546b16
:fire: Remove unused file
dej611 Sep 23, 2022
c5647bd
Merge branch 'main' into feature/140204-ui
dej611 Sep 23, 2022
64ece90
Merge remote-tracking branch 'upstream/main' into feature/140204-ui
dej611 Sep 28, 2022
1d786be
:sparkles: Migrate ignore flag to annotation layers
dej611 Sep 28, 2022
d8712ef
Merge branch 'feature/140204-ui' of github.com:dej611/kibana into fea…
dej611 Sep 28, 2022
9e3997f
:white_check_mark: Add unit test for default dataView
dej611 Sep 28, 2022
5030879
Merge branch 'main' into feature/140204-ui
dej611 Sep 28, 2022
6b82066
Merge branch 'main' into feature/140204-ui
dej611 Sep 29, 2022
871a603
Merge remote-tracking branch 'upstream/main' into feature/140204-ui
dej611 Sep 30, 2022
9f0dd9d
Merge branch 'feature/140204-ui' of github.com:dej611/kibana into fea…
dej611 Sep 30, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,27 @@ describe('getLayers', () => {
],
series: [createSeries({ metrics: staticValueMetric })],
});
const panelWithSingleAnnotationDefaultDataView = createPanel({
annotations: [
{
fields: 'geo.src,host',
template: 'Security Error from {{geo.src}} on {{host}}',
query_string: {
query: 'tags:error AND tags:security',
language: 'lucene',
},
id: 'ann1',
color: 'rgba(211,49,21,0.7)',
time_field: 'timestamp',
icon: 'fa-asterisk',
ignore_global_filters: 1,
ignore_panel_filters: 1,
hidden: true,
index_pattern: '',
},
],
series: [createSeries({ metrics: staticValueMetric })],
});

test.each<[string, [Record<number, Layer>, Panel], Array<Partial<XYLayerConfig>>]>([
[
Expand Down Expand Up @@ -521,6 +542,14 @@ describe('getLayers', () => {
timeField: 'timestamp',
type: 'query',
},
],
indexPatternId: 'test',
},
{
layerId: 'test-id',
layerType: 'annotations',
ignoreGlobalFilters: false,
annotations: [
{
color: '#0000FF',
filter: {
Expand Down Expand Up @@ -567,6 +596,51 @@ describe('getLayers', () => {
},
],
],
[
'annotation layer gets correct dataView when none is defined',
[dataSourceLayersWithStatic, panelWithSingleAnnotationDefaultDataView],
[
{
layerType: 'referenceLine',
accessors: ['column-id-1'],
layerId: 'test-layer-1',
yConfig: [
{
forAccessor: 'column-id-1',
axisMode: 'right',
color: '#68BC00',
fill: 'below',
},
],
},
{
layerId: 'test-id',
layerType: 'annotations',
ignoreGlobalFilters: true,
annotations: [
{
color: '#D33115',
extraFields: ['geo.src'],
filter: {
language: 'lucene',
query: 'tags:error AND tags:security',
type: 'kibana_query',
},
icon: 'asterisk',
id: 'ann1',
isHidden: true,
key: {
type: 'point_in_time',
},
label: 'Event',
timeField: 'timestamp',
type: 'query',
},
],
indexPatternId: 'default',
},
],
],
])('should return %s', async (_, input, expected) => {
const layers = await getLayers(...input, indexPatternsService as DataViewsPublicPluginStart);
expect(layers).toEqual(expected.map(expect.objectContaining));
Expand All @@ -583,8 +657,14 @@ const mockedIndices = [
] as unknown as DataView[];

const indexPatternsService = {
getDefault: jest.fn(() => Promise.resolve({ id: 'default', title: 'index' })),
get: jest.fn(() => Promise.resolve(mockedIndices[0])),
getDefault: jest.fn(() =>
Promise.resolve({
id: 'default',
title: 'index',
getFieldByName: (name: string) => ({ aggregatable: name !== 'host' }),
})
),
get: jest.fn((id) => Promise.resolve({ ...mockedIndices[0], id })),
find: jest.fn((search: string, size: number) => {
if (size !== 1) {
// shouldn't request more than one data view since there is a significant performance penalty
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ function getColor(
}

function nonNullable<T>(value: T): value is NonNullable<T> {
return value !== null && value !== undefined;
return value != null;
}

export const getLayers = async (
Expand Down Expand Up @@ -132,16 +132,22 @@ export const getLayers = async (
return nonAnnotationsLayers;
}

const annotationsByIndexPattern = groupBy(
model.annotations,
(a) => typeof a.index_pattern === 'object' && 'id' in a.index_pattern && a.index_pattern.id
);
const annotationsByIndexPatternAndIgnoreFlag = groupBy(model.annotations, (a) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😍👌🏼

const id = typeof a.index_pattern === 'object' && 'id' in a.index_pattern && a.index_pattern.id;
return `${id}-${Boolean(a.ignore_global_filters)}`;
});

try {
const annotationsLayers: Array<XYAnnotationsLayerConfig | undefined> = await Promise.all(
Object.entries(annotationsByIndexPattern).map(async ([indexPatternId, annotations]) => {
Object.values(annotationsByIndexPatternAndIgnoreFlag).map(async (annotations) => {
const [firstAnnotation] = annotations;
const indexPatternId =
typeof firstAnnotation.index_pattern === 'string'
? firstAnnotation.index_pattern
: firstAnnotation.index_pattern?.id;
const convertedAnnotations: EventAnnotationConfig[] = [];
const { indexPattern } = (await fetchIndexPattern({ id: indexPatternId }, dataViews)) || {};
const { indexPattern } =
(await fetchIndexPattern(indexPatternId && { id: indexPatternId }, dataViews)) || {};

if (indexPattern) {
annotations.forEach((a: Annotation) => {
Expand All @@ -153,9 +159,9 @@ export const getLayers = async (
return {
layerId: v4(),
layerType: 'annotations',
ignoreGlobalFilters: true,
ignoreGlobalFilters: Boolean(firstAnnotation.ignore_global_filters),
annotations: convertedAnnotations,
indexPatternId,
indexPatternId: indexPattern.id!,
};
}
})
Expand All @@ -173,7 +179,7 @@ const convertAnnotation = (
): EventAnnotationConfig | undefined => {
const extraFields = annotation.fields
?.replace(/\s/g, '')
?.split(',')
.split(',')
.map((field) => {
const dataViewField = dataView.getFieldByName(field);
return dataViewField && dataViewField.aggregatable ? field : undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
*/

import { i18n } from '@kbn/i18n';
import { Visualization } from '../../../..';
import { LayerAction } from './types';
import type { LayerAction } from '../../../../types';
import type { Visualization } from '../../../..';

interface CloneLayerAction {
execute: () => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
* 2.0.
*/

import React, { useState, useCallback, useMemo } from 'react';
import React, { useState, useCallback } from 'react';
import { i18n } from '@kbn/i18n';
import type { CoreStart } from '@kbn/core/public';
import {
EuiButtonIcon,
EuiContextMenuPanel,
Expand All @@ -18,13 +17,28 @@ import {
EuiText,
EuiOutsideClickDetector,
} from '@elastic/eui';
import type { LayerType, Visualization } from '../../../..';
import type { LayerAction } from './types';

import type { CoreStart } from '@kbn/core/public';
import type { LayerType } from '../../../..';
import type { LayerAction, Visualization } from '../../../../types';
import { getCloneLayerAction } from './clone_layer_action';
import { getRemoveLayerAction } from './remove_layer_action';

export interface LayerActionsProps {
layerIndex: number;
actions: LayerAction[];
}

/** @internal **/
export const getSharedActions = ({
core,
layerIndex,
layerType,
activeVisualization,
isOnlyLayer,
isTextBasedLanguage,
onCloneLayer,
onRemoveLayer,
}: {
onRemoveLayer: () => void;
onCloneLayer: () => void;
layerIndex: number;
Expand All @@ -33,14 +47,25 @@ export interface LayerActionsProps {
layerType?: LayerType;
isTextBasedLanguage?: boolean;
core: Pick<CoreStart, 'overlays' | 'theme'>;
}
}) => [
getCloneLayerAction({
execute: onCloneLayer,
layerIndex,
activeVisualization,
isTextBasedLanguage,
}),
getRemoveLayerAction({
execute: onRemoveLayer,
layerIndex,
activeVisualization,
layerType,
isOnlyLayer,
core,
}),
];

/** @internal **/
const InContextMenuActions = (
props: LayerActionsProps & {
actions: LayerAction[];
}
) => {
const InContextMenuActions = (props: LayerActionsProps) => {
const dataTestSubject = `lnsLayerSplitButton--${props.layerIndex}`;
const [isPopoverOpen, setPopover] = useState(false);
const splitButtonPopoverId = useGeneratedHtmlId({
Expand Down Expand Up @@ -105,47 +130,24 @@ const InContextMenuActions = (
};

export const LayerActions = (props: LayerActionsProps) => {
const compatibleActions = useMemo(
() =>
[
getCloneLayerAction({
execute: props.onCloneLayer,
layerIndex: props.layerIndex,
activeVisualization: props.activeVisualization,
isTextBasedLanguage: props.isTextBasedLanguage,
}),
getRemoveLayerAction({
execute: props.onRemoveLayer,
layerIndex: props.layerIndex,
activeVisualization: props.activeVisualization,
layerType: props.layerType,
isOnlyLayer: props.isOnlyLayer,
core: props.core,
}),
].filter((i) => i.isCompatible),
[props]
);

if (!compatibleActions.length) {
if (!props.actions.length) {
return null;
}

if (compatibleActions.length > 1) {
return <InContextMenuActions {...props} actions={compatibleActions} />;
} else {
const [{ displayName, execute, icon, color, 'data-test-subj': dataTestSubj }] =
compatibleActions;

return (
<EuiButtonIcon
size="xs"
iconType={icon}
color={color}
data-test-subj={dataTestSubj}
aria-label={displayName}
title={displayName}
onClick={execute}
/>
);
if (props.actions.length > 1) {
return <InContextMenuActions {...props} />;
}
const [{ displayName, execute, icon, color, 'data-test-subj': dataTestSubj }] = props.actions;

return (
<EuiButtonIcon
size="xs"
iconType={icon}
color={color}
data-test-subj={dataTestSubj}
aria-label={displayName}
title={displayName}
onClick={execute}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ import {
import { i18n } from '@kbn/i18n';
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { LayerAction } from './types';
import { Visualization } from '../../../../types';
import type { LayerAction, Visualization } from '../../../../types';
import { LOCAL_STORAGE_LENS_KEY } from '../../../../settings_storage';
import { LayerType, layerTypes } from '../../../..';
import { type LayerType, layerTypes } from '../../../..';

interface RemoveLayerAction {
execute: () => void;
Expand Down

This file was deleted.

Loading