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

[SecuritySolutions] Update CellActions to support all types used by Discover #160524

Merged
merged 10 commits into from
Jun 30, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,25 @@
import { createCopyToClipboardActionFactory } from './copy_to_clipboard';
import type { CellActionExecutionContext } from '../../types';
import type { NotificationsStart } from '@kbn/core/public';
import { KBN_FIELD_TYPES } from '@kbn/field-types';

const mockSuccessToast = jest.fn();
const mockWarningToast = jest.fn();

const mockCopy = jest.fn((text: string) => true);
jest.mock('copy-to-clipboard', () => (text: string) => mockCopy(text));

describe('Default createCopyToClipboardActionFactory', () => {
const copyToClipboardActionFactory = createCopyToClipboardActionFactory({
notifications: { toasts: { addSuccess: mockSuccessToast } } as unknown as NotificationsStart,
notifications: {
toasts: { addSuccess: mockSuccessToast, addWarning: mockWarningToast },
} as unknown as NotificationsStart,
});
const copyToClipboardAction = copyToClipboardActionFactory({ id: 'testAction' });
const context = {
data: [
{
field: { name: 'user.name', type: 'text' },
field: { name: 'user.name', type: 'string' },
value: 'the value',
},
],
Expand All @@ -45,6 +49,20 @@ describe('Default createCopyToClipboardActionFactory', () => {
it('should return true if everything is okay', async () => {
expect(await copyToClipboardAction.isCompatible(context)).toEqual(true);
});

it('should return false if Kbn type is unsupported', async () => {
expect(
await copyToClipboardAction.isCompatible({
...context,
data: [
{
...context.data[0],
field: { ...context.data[0].field, type: KBN_FIELD_TYPES.NUMBER_RANGE },
},
],
})
).toEqual(false);
});
});

describe('execute', () => {
Expand Down Expand Up @@ -111,5 +129,19 @@ describe('Default createCopyToClipboardActionFactory', () => {
expect(mockCopy).toHaveBeenCalledWith('user.name: true AND false AND true');
expect(mockSuccessToast).toHaveBeenCalled();
});

it('should notify the user when value type is unsupported', async () => {
await copyToClipboardAction.execute({
...context,
data: [
{
...context.data[0],
value: {},
},
],
});
expect(mockCopy).not.toHaveBeenCalled();
expect(mockWarningToast).toHaveBeenCalled();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import copy from 'copy-to-clipboard';
import { i18n } from '@kbn/i18n';
import type { NotificationsStart } from '@kbn/core/public';
import { isString } from 'lodash/fp';
import { KBN_FIELD_TYPES } from '@kbn/field-types';
import { COPY_CELL_ACTION_TYPE } from '../../constants';
import { createCellActionFactory } from '../factory';
import { isTypeSupportedByDefaultActions, isValueSupportedByDefaultActions } from '../utils';
import { ACTION_INCOMPATIBLE_VALUE_WARNING } from '../translations';
import { DefaultActionsSupportedValue } from '../types';

const ICON = 'copyClipboard';
const COPY_TO_CLIPBOARD = i18n.translate('cellActions.actions.copyToClipboard.displayName', {
Expand All @@ -37,12 +41,21 @@ export const createCopyToClipboardActionFactory = createCellActionFactory(

return (
data.length === 1 && // TODO Add support for multiple values
field.name != null
field.name != null &&
isTypeSupportedByDefaultActions(field.type as KBN_FIELD_TYPES)
);
},
execute: async ({ data }) => {
const field = data[0]?.field;
const value = data[0]?.value;
const rawValue = data[0]?.value;

if (!isValueSupportedByDefaultActions(rawValue)) {
notifications.toasts.addWarning({
title: ACTION_INCOMPATIBLE_VALUE_WARNING,
});
return;
}
const value = rawValue as DefaultActionsSupportedValue;

let textValue: undefined | string;
if (value != null) {
Expand Down
10 changes: 5 additions & 5 deletions packages/kbn-cell-actions/src/actions/filter/create_filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import {
type Filter,
} from '@kbn/es-query';
import { isArray } from 'lodash/fp';
import { CellActionFieldValue } from '../../types';
import { DefaultActionsSupportedValue, NonNullablePrimitiveValue } from '../types';

export const isEmptyFilterValue = (
value: CellActionFieldValue
value: DefaultActionsSupportedValue
): value is null | undefined | never[] =>
value == null || value === '' || (isArray(value) && value.length === 0);

Expand All @@ -31,7 +31,7 @@ const createPhraseFilter = ({
negate,
value,
}: {
value: string | number | boolean;
value: NonNullablePrimitiveValue;
key: string;
negate?: boolean;
}): PhraseFilter => ({
Expand All @@ -49,7 +49,7 @@ const createCombinedFilter = ({
key,
negate,
}: {
values: string[] | number[] | boolean[];
values: NonNullablePrimitiveValue[];
key: string;
negate: boolean;
}): CombinedFilter => ({
Expand All @@ -68,7 +68,7 @@ export const createFilter = ({
negate,
}: {
key: string;
value: CellActionFieldValue;
value: DefaultActionsSupportedValue;
negate: boolean;
}): Filter => {
if (isEmptyFilterValue(value)) {
Expand Down
37 changes: 35 additions & 2 deletions packages/kbn-cell-actions/src/actions/filter/filter_in.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { FilterManager } from '@kbn/data-plugin/public';
import { FilterManager, KBN_FIELD_TYPES } from '@kbn/data-plugin/public';
import { createFilterInActionFactory } from './filter_in';
import { makeActionContext } from '../../mocks/helpers';
import { NotificationsStart } from '@kbn/core-notifications-browser';

const mockFilterManager = { addFilters: jest.fn() } as unknown as FilterManager;

Expand All @@ -20,15 +21,18 @@ jest.mock('./create_filter', () => ({
const fieldName = 'user.name';
const value = 'the value';

const mockWarningToast = jest.fn();

describe('createFilterInActionFactory', () => {
const filterInActionFactory = createFilterInActionFactory({
filterManager: mockFilterManager,
notifications: { toasts: { addWarning: mockWarningToast } } as unknown as NotificationsStart,
});
const filterInAction = filterInActionFactory({ id: 'testAction' });
const context = makeActionContext({
data: [
{
field: { name: fieldName, type: 'text', searchable: true, aggregatable: true },
field: { name: fieldName, type: 'string', searchable: true, aggregatable: true },
value,
},
],
Expand Down Expand Up @@ -57,12 +61,27 @@ describe('createFilterInActionFactory', () => {
...context,
data: [
{
...context.data[0],
field: { ...context.data[0].field, name: '' },
},
],
})
).toEqual(false);
});

it('should return false if Kbn type is unsupported', async () => {
expect(
await filterInAction.isCompatible({
...context,
data: [
{
...context.data[0],
field: { ...context.data[0].field, type: KBN_FIELD_TYPES.MISSING },
},
],
})
).toEqual(false);
});
});

describe('execute', () => {
Expand Down Expand Up @@ -152,5 +171,19 @@ describe('createFilterInActionFactory', () => {
});
expect(mockCreateFilter).toHaveBeenCalledWith({ key: fieldName, value: [], negate: true });
});

it('should notify the user when value type is unsupported', async () => {
await filterInAction.execute({
...context,
data: [
{
...context.data[0],
value: [{}, {}, {}],
},
],
});
expect(mockCreateFilter).not.toHaveBeenCalled();
expect(mockWarningToast).toHaveBeenCalled();
});
});
});
32 changes: 25 additions & 7 deletions packages/kbn-cell-actions/src/actions/filter/filter_in.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,28 @@
* Side Public License, v 1.
*/
import { i18n } from '@kbn/i18n';
import type { FilterManager } from '@kbn/data-plugin/public';
import type { FilterManager, KBN_FIELD_TYPES } from '@kbn/data-plugin/public';
import { NotificationsStart } from '@kbn/core-notifications-browser';
import { createFilter, isEmptyFilterValue } from './create_filter';
import { FILTER_CELL_ACTION_TYPE } from '../../constants';
import { createCellActionFactory } from '../factory';
import { CellActionFieldValue } from '../../types';
import { isTypeSupportedByDefaultActions, isValueSupportedByDefaultActions } from '../utils';
import { ACTION_INCOMPATIBLE_VALUE_WARNING } from '../translations';
import { DefaultActionsSupportedValue } from '../types';

const ICON = 'plusInCircle';
const FILTER_IN = i18n.translate('cellActions.actions.filterIn', {
defaultMessage: 'Filter In',
});

export const createFilterInActionFactory = createCellActionFactory(
({ filterManager }: { filterManager: FilterManager }) => ({
({
filterManager,
notifications: { toasts },
}: {
filterManager: FilterManager;
notifications: NotificationsStart;
}) => ({
type: FILTER_CELL_ACTION_TYPE,
getIconType: () => ICON,
getDisplayName: () => FILTER_IN,
Expand All @@ -28,13 +37,22 @@ export const createFilterInActionFactory = createCellActionFactory(

return (
data.length === 1 && // TODO Add support for multiple values
!!field.name
!!field.name &&
isTypeSupportedByDefaultActions(field.type as KBN_FIELD_TYPES)
);
},
execute: async ({ data }) => {
const field = data[0]?.field;
const value = data[0]?.value;
addFilterIn({ filterManager, fieldName: field.name, value });
const rawValue = data[0]?.value;

if (isValueSupportedByDefaultActions(rawValue)) {
const value = rawValue as DefaultActionsSupportedValue;
addFilterIn({ filterManager, fieldName: field.name, value });
} else {
toasts.addWarning({
title: ACTION_INCOMPATIBLE_VALUE_WARNING,
});
}
},
})
);
Expand All @@ -46,7 +64,7 @@ export const addFilterIn = ({
}: {
filterManager: FilterManager | undefined;
fieldName: string;
value: CellActionFieldValue;
value: DefaultActionsSupportedValue;
}) => {
if (filterManager != null) {
const filter = createFilter({
Expand Down
40 changes: 37 additions & 3 deletions packages/kbn-cell-actions/src/actions/filter/filter_out.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import type { FilterManager } from '@kbn/data-plugin/public';
import { FilterManager, KBN_FIELD_TYPES } from '@kbn/data-plugin/public';
import { createFilterOutActionFactory } from './filter_out';
import { makeActionContext } from '../../mocks/helpers';
import { NotificationsStart } from '@kbn/core-notifications-browser';

const mockFilterManager = { addFilters: jest.fn() } as unknown as FilterManager;

Expand All @@ -20,13 +21,18 @@ jest.mock('./create_filter', () => ({
const fieldName = 'user.name';
const value = 'the value';

const mockWarningToast = jest.fn();

describe('createFilterOutAction', () => {
const filterOutActionFactory = createFilterOutActionFactory({ filterManager: mockFilterManager });
const filterOutActionFactory = createFilterOutActionFactory({
filterManager: mockFilterManager,
notifications: { toasts: { addWarning: mockWarningToast } } as unknown as NotificationsStart,
});
const filterOutAction = filterOutActionFactory({ id: 'testAction' });
const context = makeActionContext({
data: [
{
field: { name: fieldName, type: 'text', searchable: true, aggregatable: true },
field: { name: fieldName, type: 'string', searchable: true, aggregatable: true },
value,
},
],
Expand Down Expand Up @@ -61,6 +67,20 @@ describe('createFilterOutAction', () => {
})
).toEqual(false);
});

it('should return false if Kbn type is unsupported', async () => {
expect(
await filterOutAction.isCompatible({
...context,
data: [
{
...context.data[0],
field: { ...context.data[0].field, type: KBN_FIELD_TYPES._SOURCE },
},
],
})
).toEqual(false);
});
});

describe('execute', () => {
Expand Down Expand Up @@ -146,5 +166,19 @@ describe('createFilterOutAction', () => {
});
expect(mockCreateFilter).toHaveBeenCalledWith({ key: fieldName, value: [], negate: false });
});

it('should notify the user when value type is unsupported', async () => {
await filterOutAction.execute({
...context,
data: [
{
...context.data[0],
value: { a: {} },
},
],
});
expect(mockCreateFilter).not.toHaveBeenCalled();
expect(mockWarningToast).toHaveBeenCalled();
});
});
});
Loading