Skip to content

Commit

Permalink
PR comments
Browse files Browse the repository at this point in the history
  • Loading branch information
christineweng committed Dec 4, 2024
1 parent cf2afcf commit 9307ef7
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 111 deletions.
2 changes: 2 additions & 0 deletions packages/kbn-expandable-flyout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ To control (or mutate) flyout's layout, you can utilize [useExpandableFlyoutApi]

> The expandable flyout propagates the `onClose` callback from the EuiFlyout component. As we recommend having a single instance of the flyout in your application, it's up to the application's code to dispatch the event (through Redux, window events, observable, prop drilling...).
When calling `openFlyout`, the right panel state is automatically appended in the `history` slice in the redux context. To access the flyout's history, you can use the [useExpandableFlyoutHistory](https://github.com/elastic/kibana/blob/main/packages/kbn-expandable-flyout/src/hooks/use_expandable_flyout_history.ts) hook.

## Usage

To use the expandable flyout in your plugin, first you need wrap your code with the [context provider](https://github.com/elastic/kibana/blob/main/packages/kbn-expandable-flyout/src/context.tsx) at a high enough level as follows:
Expand Down
2 changes: 1 addition & 1 deletion packages/kbn-expandable-flyout/src/store/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export interface FlyoutPanels {
*/
preview: FlyoutPanelProps[] | undefined;
/*
* History of the right panel that were opened
* History of the right panels that were opened
*/
history: FlyoutPanelProps[];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,25 +48,3 @@ export const getEcsAllowedValueDescription = (fieldName: FieldName, value: strin
})
);
};

// mapping of event category to the field displayed as title
export const EVENT_CATEGORY_TO_FIELD: Record<string, string> = {
authentication: 'user.name',
configuration: '',
database: '',
driver: '',
email: '',
file: 'file.name',
host: 'host.name',
iam: '',
intrusion_detection: '',
malware: '',
network: '',
package: '',
process: 'process.name',
registry: '',
session: '',
threat: '',
vulnerability: '',
web: '',
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import { getField, getFieldArray, getEventTitle, getAlertTitle } from './utils';

describe('test getField', () => {
describe('getField', () => {
it('should return the string value if field is a string', () => {
expect(getField('test string')).toBe('test string');
});
Expand All @@ -29,7 +29,7 @@ describe('test getField', () => {
});
});

describe('test getFieldArray', () => {
describe('getFieldArray', () => {
it('should return the string value in an array if field is a string', () => {
expect(getFieldArray('test string')).toStrictEqual(['test string']);
});
Expand All @@ -48,8 +48,8 @@ describe('test getFieldArray', () => {
});
});

describe('test getEventTitle', () => {
it('when event kind is event, return event title based on category', () => {
describe('getEventTitle', () => {
it('should return event title based on category when event kind is event', () => {
expect(
getEventTitle({
eventKind: 'event',
Expand All @@ -59,31 +59,31 @@ describe('test getEventTitle', () => {
).toBe('process name');
});

it('when event kind is alert, return External alert details', () => {
it('should return External alert details when event kind is alert', () => {
expect(
getEventTitle({ eventKind: 'alert', eventCategory: null, getFieldsData: jest.fn() })
).toBe('External alert details');
});

it('when event kind is not event or alert, return Event kind details', () => {
it('should return generic event details when event kind is not event or alert', () => {
expect(
getEventTitle({ eventKind: 'metric', eventCategory: null, getFieldsData: jest.fn() })
).toBe('Metric details');
});

it('when event kind is null, return Event details', () => {
it('should return Event details when event kind is null', () => {
expect(getEventTitle({ eventKind: null, eventCategory: null, getFieldsData: jest.fn() })).toBe(
'Event details'
);
});
});

describe('test getAlertTitle', () => {
it('when ruleName is undefined, return Document details', () => {
describe('getAlertTitle', () => {
it('should return Document details when ruleName is undefined', () => {
expect(getAlertTitle({ ruleName: undefined })).toBe('Document details');
});

it('when ruleName is defined, return ruleName', () => {
it('should return ruleName when ruleName is defined', () => {
expect(getAlertTitle({ ruleName: 'test rule' })).toBe('test rule');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/
import { i18n } from '@kbn/i18n';
import { startCase } from 'lodash';
import { EVENT_CATEGORY_TO_FIELD } from '../right/utils/event_utils';
import type { GetFieldsData } from './hooks/use_get_fields_data';

/**
Expand Down Expand Up @@ -38,7 +37,29 @@ export const getFieldArray = (field: unknown | unknown[]) => {
return [];
};

export const getAlertTitle = ({ ruleName }: { ruleName: string | undefined }) => {
// mapping of event category to the field displayed as title
export const EVENT_CATEGORY_TO_FIELD: Record<string, string> = {
authentication: 'user.name',
configuration: '',
database: '',
driver: '',
email: '',
file: 'file.name',
host: 'host.name',
iam: '',
intrusion_detection: '',
malware: '',
network: '',
package: '',
process: 'process.name',
registry: '',
session: '',
threat: '',
vulnerability: '',
web: '',
};

export const getAlertTitle = ({ ruleName }: { ruleName?: string | null }) => {
const defaultAlertTitle = i18n.translate(
'xpack.securitySolution.flyout.right.header.headerTitle',
{ defaultMessage: 'Document details' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ export interface HistoryProps {
* A list of flyouts that have been opened
*/
history: FlyoutPanelProps[];
/**
* Maximum number of flyouts to show in history
*/
maxCount?: number;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,9 @@ import {
import { TestProviders } from '../../../common/mock';
import type { RuleResponse } from '../../../../common/api/detection_engine';
import { useExpandableFlyoutApi, type ExpandableFlyoutApi } from '@kbn/expandable-flyout';
import { useRuleWithFallback } from '../../../detection_engine/rule_management/logic/use_rule_with_fallback';
import { useRuleDetails } from '../../rule_details/hooks/use_rule_details';
import {
useBasicDataFromDetailsData,
type UseBasicDataFromDetailsDataResult,
} from '../../document_details/shared/hooks/use_basic_data_from_details_data';
import { useEventDetails } from '../../document_details/shared/hooks/use_event_details';
import { useBasicDataFromDetailsData } from '../../document_details/shared/hooks/use_basic_data_from_details_data';
import { DocumentDetailsRightPanelKey } from '../../document_details/shared/constants/panel_keys';
import { RulePanelKey } from '../../rule_details/right';
import { UserPanelKey } from '../../entity_details/user_right';
Expand All @@ -30,6 +27,9 @@ import { NetworkPanelKey } from '../../network_details';
import {
DOCUMENT_DETAILS_HISTORY_ROW_TEST_ID,
RULE_HISTORY_ROW_TEST_ID,
HOST_HISTORY_ROW_TEST_ID,
USER_HISTORY_ROW_TEST_ID,
NETWORK_HISTORY_ROW_TEST_ID,
GENERIC_HISTORY_ROW_TEST_ID,
} from './test_ids';

Expand All @@ -43,6 +43,8 @@ jest.mock('@kbn/expandable-flyout', () => ({
jest.mock('../../../detection_engine/rule_management/logic/use_rule_with_fallback');
jest.mock('../../document_details/shared/hooks/use_basic_data_from_details_data');
jest.mock('../../rule_details/hooks/use_rule_details');
jest.mock('../../document_details/shared/hooks/use_event_details');

const flyoutContextValue = {
openFlyout: jest.fn(),
} as unknown as ExpandableFlyoutApi;
Expand Down Expand Up @@ -86,20 +88,19 @@ describe('FlyoutHistoryRow', () => {
beforeEach(() => {
jest.mocked(useExpandableFlyoutApi).mockReturnValue(flyoutContextValue);
jest.mocked(useRuleDetails).mockReturnValue({
rule: null,
loading: false,
isExistingRule: false,
});
jest.mocked(useRuleWithFallback).mockReturnValue({
...mockedRuleResponse,
rule: { name: 'rule name' } as RuleResponse,
});
jest
.mocked(useBasicDataFromDetailsData)
.mockReturnValue({ isAlert: false, ruleId: 'ruleId' } as UseBasicDataFromDetailsDataResult);
(useEventDetails as jest.Mock).mockReturnValue({});
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ isAlert: false });
});

it('renders document details history row when key is alert', () => {
const mockedGetFieldsData = (field: string) =>
field === 'kibana.alert.rule.name' ? 'rule name' : null;
(useEventDetails as jest.Mock).mockReturnValue({ getFieldsData: mockedGetFieldsData });
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ isAlert: true });

const { getByTestId } = render(
<TestProviders>
<FlyoutHistoryRow item={rowItems.alert} index={0} />
Expand All @@ -123,8 +124,8 @@ describe('FlyoutHistoryRow', () => {
<FlyoutHistoryRow item={rowItems.host} index={2} />
</TestProviders>
);
expect(getByTestId(`${2}-${GENERIC_HISTORY_ROW_TEST_ID}`)).toBeInTheDocument();
expect(getByTestId(`${2}-${GENERIC_HISTORY_ROW_TEST_ID}`)).toHaveTextContent('Host: host name');
expect(getByTestId(`${2}-${HOST_HISTORY_ROW_TEST_ID}`)).toBeInTheDocument();
expect(getByTestId(`${2}-${HOST_HISTORY_ROW_TEST_ID}`)).toHaveTextContent('Host: host name');
});

it('renders generic user history row when key is user', () => {
Expand All @@ -133,8 +134,8 @@ describe('FlyoutHistoryRow', () => {
<FlyoutHistoryRow item={rowItems.user} index={3} />
</TestProviders>
);
expect(getByTestId(`${3}-${GENERIC_HISTORY_ROW_TEST_ID}`)).toBeInTheDocument();
expect(getByTestId(`${3}-${GENERIC_HISTORY_ROW_TEST_ID}`)).toHaveTextContent('User: user name');
expect(getByTestId(`${3}-${USER_HISTORY_ROW_TEST_ID}`)).toBeInTheDocument();
expect(getByTestId(`${3}-${USER_HISTORY_ROW_TEST_ID}`)).toHaveTextContent('User: user name');
});

it('renders generic network history row when key is network', () => {
Expand All @@ -143,21 +144,31 @@ describe('FlyoutHistoryRow', () => {
<FlyoutHistoryRow item={rowItems.network} index={4} />
</TestProviders>
);
expect(getByTestId(`${4}-${GENERIC_HISTORY_ROW_TEST_ID}`)).toBeInTheDocument();
expect(getByTestId(`${4}-${GENERIC_HISTORY_ROW_TEST_ID}`)).toHaveTextContent('Network: ip');
expect(getByTestId(`${4}-${NETWORK_HISTORY_ROW_TEST_ID}`)).toBeInTheDocument();
expect(getByTestId(`${4}-${NETWORK_HISTORY_ROW_TEST_ID}`)).toHaveTextContent('Network: ip');
});

it('renders null when key is not supported', () => {
const { container } = render(
<TestProviders>
<FlyoutHistoryRow item={{ id: 'key' }} index={5} />
</TestProviders>
);
expect(container).toBeEmptyDOMElement();
});
});

describe('DocumentDetailsHistoryRow', () => {
it('renders alert title when isAlert is true and rule name is defined', () => {
beforeEach(() => {
(useEventDetails as jest.Mock).mockReturnValue({});
jest.mocked(useExpandableFlyoutApi).mockReturnValue(flyoutContextValue);
jest.mocked(useRuleWithFallback).mockReturnValue({
...mockedRuleResponse,
rule: { name: 'rule name' } as RuleResponse,
});
jest
.mocked(useBasicDataFromDetailsData)
.mockReturnValue({ isAlert: true } as UseBasicDataFromDetailsDataResult);
});

it('renders alert title when isAlert is true and rule name is defined', () => {
const mockedGetFieldsData = (field: string) =>
field === 'kibana.alert.rule.name' ? 'rule name' : null;
(useEventDetails as jest.Mock).mockReturnValue({ getFieldsData: mockedGetFieldsData });
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ isAlert: true });

const { getByTestId } = render(
<TestProviders>
Expand All @@ -170,26 +181,24 @@ describe('DocumentDetailsHistoryRow', () => {
});

it('renders default alert title when isAlert is true and rule name is undefined', () => {
jest.mocked(useRuleWithFallback).mockReturnValue(mockedRuleResponse);
jest
.mocked(useBasicDataFromDetailsData)
.mockReturnValue({ isAlert: true } as UseBasicDataFromDetailsDataResult);
const mockedGetFieldsData = () => null;
(useEventDetails as jest.Mock).mockReturnValue({ getFieldsData: mockedGetFieldsData });
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ isAlert: true });

const { getByTestId } = render(
<TestProviders>
<DocumentDetailsHistoryRow item={rowItems.alert} index={0} />
</TestProviders>
);
expect(getByTestId(`${0}-${DOCUMENT_DETAILS_HISTORY_ROW_TEST_ID}`)).toHaveTextContent(
'Document details'
'Alert: Document details'
);
});

it('renders event title when isAlert is false', () => {
jest.mocked(useRuleWithFallback).mockReturnValue(mockedRuleResponse);
jest
.mocked(useBasicDataFromDetailsData)
.mockReturnValue({ isAlert: false } as UseBasicDataFromDetailsDataResult);
const mockedGetFieldsData = () => '';
(useEventDetails as jest.Mock).mockReturnValue({ getFieldsData: mockedGetFieldsData });
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ isAlert: false });

const { getByTestId } = render(
<TestProviders>
Expand All @@ -202,6 +211,11 @@ describe('DocumentDetailsHistoryRow', () => {
});

it('opens document details flyout when clicked', () => {
const mockedGetFieldsData = (field: string) =>
field === 'kibana.alert.rule.name' ? 'rule name' : null;
(useEventDetails as jest.Mock).mockReturnValue({ getFieldsData: mockedGetFieldsData });
(useBasicDataFromDetailsData as jest.Mock).mockReturnValue({ isAlert: true });

const { getByTestId } = render(
<TestProviders>
<DocumentDetailsHistoryRow item={rowItems.alert} index={0} />
Expand Down
Loading

0 comments on commit 9307ef7

Please sign in to comment.