[Security Solution] [Endpoint] Event filters uses the new card design (#114126)
* Adds new card design to event filters and also adds comments list * Adds nested comments * Hides comments if there are no commentes * Fixes i18n check error because duplicated key * Fix wrong type and unit test * Fixes ts error * Address pr comments and fix unit tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
db4bcdee2c
commit
0bf0b94ee0
|
@ -25,7 +25,7 @@ describe.each([
|
|||
) => ReturnType<AppContextTestRender['render']>;
|
||||
|
||||
beforeEach(() => {
|
||||
item = generateItem();
|
||||
item = generateItem() as AnyArtifact;
|
||||
appTestContext = createAppRootMockRenderer();
|
||||
render = (props = {}) => {
|
||||
renderResult = appTestContext.render(
|
||||
|
@ -77,13 +77,31 @@ describe.each([
|
|||
expect(renderResult.getByTestId('testCard-description').textContent).toEqual(item.description);
|
||||
});
|
||||
|
||||
it("shouldn't display description", async () => {
|
||||
render({ hideDescription: true });
|
||||
expect(renderResult.queryByTestId('testCard-description')).toBeNull();
|
||||
});
|
||||
|
||||
it('should display default empty value if description does not exist', async () => {
|
||||
item.description = undefined;
|
||||
render();
|
||||
|
||||
expect(renderResult.getByTestId('testCard-description').textContent).toEqual('—');
|
||||
});
|
||||
|
||||
it('should display comments if one exists', async () => {
|
||||
render();
|
||||
if (isTrustedApp(item)) {
|
||||
expect(renderResult.queryByTestId('testCard-comments')).toBeNull();
|
||||
} else {
|
||||
expect(renderResult.queryByTestId('testCard-comments')).not.toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
it("shouldn't display comments", async () => {
|
||||
render({ hideComments: true });
|
||||
expect(renderResult.queryByTestId('testCard-comments')).toBeNull();
|
||||
});
|
||||
|
||||
it('should display OS and criteria conditions', () => {
|
||||
render();
|
||||
|
||||
|
|
|
@ -16,10 +16,11 @@ import { useNormalizedArtifact } from './hooks/use_normalized_artifact';
|
|||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
import { CardContainerPanel } from './components/card_container_panel';
|
||||
import { CardSectionPanel } from './components/card_section_panel';
|
||||
import { CardComments } from './components/card_comments';
|
||||
import { usePolicyNavLinks } from './hooks/use_policy_nav_links';
|
||||
import { MaybeImmutable } from '../../../../common/endpoint/types';
|
||||
|
||||
export interface ArtifactEntryCardProps extends CommonProps {
|
||||
export interface CommonArtifactEntryCardProps extends CommonProps {
|
||||
item: MaybeImmutable<AnyArtifact>;
|
||||
/**
|
||||
* The list of actions for the card. Will display an icon with the actions in a menu if defined.
|
||||
|
@ -34,12 +35,27 @@ export interface ArtifactEntryCardProps extends CommonProps {
|
|||
policies?: MenuItemPropsByPolicyId;
|
||||
}
|
||||
|
||||
export interface ArtifactEntryCardProps extends CommonArtifactEntryCardProps {
|
||||
// A flag to hide description section, false by default
|
||||
hideDescription?: boolean;
|
||||
// A flag to hide comments section, false by default
|
||||
hideComments?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display Artifact Items (ex. Trusted App, Event Filter, etc) as a card.
|
||||
* This component is a TS Generic that allows you to set what the Item type is
|
||||
*/
|
||||
export const ArtifactEntryCard = memo<ArtifactEntryCardProps>(
|
||||
({ item, policies, actions, 'data-test-subj': dataTestSubj, ...commonProps }) => {
|
||||
({
|
||||
item,
|
||||
policies,
|
||||
actions,
|
||||
hideDescription = false,
|
||||
hideComments = false,
|
||||
'data-test-subj': dataTestSubj,
|
||||
...commonProps
|
||||
}) => {
|
||||
const artifact = useNormalizedArtifact(item as AnyArtifact);
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
const policyNavLinks = usePolicyNavLinks(artifact, policies);
|
||||
|
@ -63,11 +79,16 @@ export const ArtifactEntryCard = memo<ArtifactEntryCardProps>(
|
|||
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiText>
|
||||
<p data-test-subj={getTestId('description')}>
|
||||
{artifact.description || getEmptyValue()}
|
||||
</p>
|
||||
</EuiText>
|
||||
{!hideDescription ? (
|
||||
<EuiText>
|
||||
<p data-test-subj={getTestId('description')}>
|
||||
{artifact.description || getEmptyValue()}
|
||||
</p>
|
||||
</EuiText>
|
||||
) : null}
|
||||
{!hideComments ? (
|
||||
<CardComments comments={artifact.comments} data-test-subj={getTestId('comments')} />
|
||||
) : null}
|
||||
</CardSectionPanel>
|
||||
|
||||
<EuiHorizontalRule margin="none" />
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import React, { memo } from 'react';
|
||||
import { EuiHorizontalRule } from '@elastic/eui';
|
||||
import { ArtifactEntryCardProps } from './artifact_entry_card';
|
||||
import { CommonArtifactEntryCardProps } from './artifact_entry_card';
|
||||
import { CardContainerPanel } from './components/card_container_panel';
|
||||
import { useNormalizedArtifact } from './hooks/use_normalized_artifact';
|
||||
import { useTestIdGenerator } from '../hooks/use_test_id_generator';
|
||||
|
@ -15,7 +15,7 @@ import { CardSectionPanel } from './components/card_section_panel';
|
|||
import { CriteriaConditions, CriteriaConditionsProps } from './components/criteria_conditions';
|
||||
import { CardCompressedHeader } from './components/card_compressed_header';
|
||||
|
||||
export interface ArtifactEntryCollapsibleCardProps extends ArtifactEntryCardProps {
|
||||
export interface ArtifactEntryCollapsibleCardProps extends CommonArtifactEntryCardProps {
|
||||
onExpandCollapse: () => void;
|
||||
expanded?: boolean;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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, { memo, useMemo, useCallback, useState } from 'react';
|
||||
import {
|
||||
CommonProps,
|
||||
EuiAccordion,
|
||||
EuiCommentList,
|
||||
EuiCommentProps,
|
||||
EuiButtonEmpty,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
|
||||
import { CardActionsFlexItemProps } from './card_actions_flex_item';
|
||||
import { ArtifactInfo } from '../types';
|
||||
import { getFormattedComments } from '../utils/get_formatted_comments';
|
||||
import { SHOW_COMMENTS_LABEL, HIDE_COMMENTS_LABEL } from './translations';
|
||||
|
||||
export interface CardCommentsProps
|
||||
extends CardActionsFlexItemProps,
|
||||
Pick<CommonProps, 'data-test-subj'> {
|
||||
comments: ArtifactInfo['comments'];
|
||||
}
|
||||
|
||||
export const CardComments = memo<CardCommentsProps>(
|
||||
({ comments, 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const [showComments, setShowComments] = useState(false);
|
||||
const onCommentsClick = useCallback((): void => {
|
||||
setShowComments(!showComments);
|
||||
}, [setShowComments, showComments]);
|
||||
const formattedComments = useMemo((): EuiCommentProps[] => {
|
||||
return getFormattedComments(comments);
|
||||
}, [comments]);
|
||||
|
||||
const buttonText = useMemo(
|
||||
() =>
|
||||
showComments ? HIDE_COMMENTS_LABEL(comments.length) : SHOW_COMMENTS_LABEL(comments.length),
|
||||
[comments.length, showComments]
|
||||
);
|
||||
|
||||
return !isEmpty(comments) ? (
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiButtonEmpty
|
||||
onClick={onCommentsClick}
|
||||
flush="left"
|
||||
size="xs"
|
||||
data-test-subj={getTestId('label')}
|
||||
>
|
||||
{buttonText}
|
||||
</EuiButtonEmpty>
|
||||
<EuiAccordion id={'1'} arrowDisplay="none" forceState={showComments ? 'open' : 'closed'}>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiCommentList comments={formattedComments} data-test-subj={getTestId('list')} />
|
||||
</EuiAccordion>
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
);
|
||||
|
||||
CardComments.displayName = 'CardComments';
|
|
@ -5,8 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { memo } from 'react';
|
||||
import { CommonProps, EuiExpression } from '@elastic/eui';
|
||||
import React, { memo, useCallback } from 'react';
|
||||
import { CommonProps, EuiExpression, EuiToken, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import {
|
||||
CONDITION_OS,
|
||||
|
@ -21,7 +22,7 @@ import {
|
|||
CONDITION_OPERATOR_TYPE_EXISTS,
|
||||
CONDITION_OPERATOR_TYPE_LIST,
|
||||
} from './translations';
|
||||
import { ArtifactInfo } from '../types';
|
||||
import { ArtifactInfo, ArtifactInfoEntry } from '../types';
|
||||
import { useTestIdGenerator } from '../../hooks/use_test_id_generator';
|
||||
|
||||
const OS_LABELS = Object.freeze({
|
||||
|
@ -39,6 +40,15 @@ const OPERATOR_TYPE_LABELS = Object.freeze({
|
|||
[ListOperatorTypeEnum.LIST]: CONDITION_OPERATOR_TYPE_LIST,
|
||||
});
|
||||
|
||||
const EuiFlexGroupNested = styled(EuiFlexGroup)`
|
||||
margin-left: ${({ theme }) => theme.eui.spacerSizes.l};
|
||||
`;
|
||||
|
||||
const EuiFlexItemNested = styled(EuiFlexItem)`
|
||||
margin-bottom: 6px !important;
|
||||
margin-top: 6px !important;
|
||||
`;
|
||||
|
||||
export type CriteriaConditionsProps = Pick<ArtifactInfo, 'os' | 'entries'> &
|
||||
Pick<CommonProps, 'data-test-subj'>;
|
||||
|
||||
|
@ -46,6 +56,44 @@ export const CriteriaConditions = memo<CriteriaConditionsProps>(
|
|||
({ os, entries, 'data-test-subj': dataTestSubj }) => {
|
||||
const getTestId = useTestIdGenerator(dataTestSubj);
|
||||
|
||||
const getNestedEntriesContent = useCallback(
|
||||
(type: string, nestedEntries: ArtifactInfoEntry[]) => {
|
||||
if (type === 'nested' && nestedEntries.length) {
|
||||
return nestedEntries.map(
|
||||
({ field: nestedField, type: nestedType, value: nestedValue }) => {
|
||||
return (
|
||||
<EuiFlexGroupNested
|
||||
data-test-subj={getTestId('nestedCondition')}
|
||||
key={nestedField + nestedType + nestedValue}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gutterSize="m"
|
||||
responsive={false}
|
||||
>
|
||||
<EuiFlexItemNested grow={false}>
|
||||
<EuiToken iconType="tokenNested" size="s" />
|
||||
</EuiFlexItemNested>
|
||||
<EuiFlexItemNested grow={false}>
|
||||
<EuiExpression description={''} value={nestedField} color="subdued" />
|
||||
</EuiFlexItemNested>
|
||||
<EuiFlexItemNested grow={false}>
|
||||
<EuiExpression
|
||||
description={
|
||||
OPERATOR_TYPE_LABELS[nestedType as keyof typeof OPERATOR_TYPE_LABELS] ??
|
||||
nestedType
|
||||
}
|
||||
value={nestedValue}
|
||||
/>
|
||||
</EuiFlexItemNested>
|
||||
</EuiFlexGroupNested>
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
[getTestId]
|
||||
);
|
||||
|
||||
return (
|
||||
<div data-test-subj={dataTestSubj}>
|
||||
<div data-test-subj={getTestId('os')}>
|
||||
|
@ -57,7 +105,7 @@ export const CriteriaConditions = memo<CriteriaConditionsProps>(
|
|||
/>
|
||||
</strong>
|
||||
</div>
|
||||
{entries.map(({ field, type, value }) => {
|
||||
{entries.map(({ field, type, value, entries: nestedEntries = [] }) => {
|
||||
return (
|
||||
<div data-test-subj={getTestId('condition')} key={field + type + value}>
|
||||
<EuiExpression description={CONDITION_AND} value={field} color="subdued" />
|
||||
|
@ -67,6 +115,7 @@ export const CriteriaConditions = memo<CriteriaConditionsProps>(
|
|||
}
|
||||
value={value}
|
||||
/>
|
||||
{getNestedEntriesContent(type, nestedEntries)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -114,3 +114,15 @@ export const COLLAPSE_ACTION = i18n.translate(
|
|||
defaultMessage: 'Collapse',
|
||||
}
|
||||
);
|
||||
|
||||
export const SHOW_COMMENTS_LABEL = (count: number = 0) =>
|
||||
i18n.translate('xpack.securitySolution.artifactCard.comments.label.show', {
|
||||
defaultMessage: 'Show comments ({count})',
|
||||
values: { count },
|
||||
});
|
||||
|
||||
export const HIDE_COMMENTS_LABEL = (count: number = 0) =>
|
||||
i18n.translate('xpack.securitySolution.artifactCard.comments.label.hide', {
|
||||
defaultMessage: 'Hide comments ({count})',
|
||||
values: { count },
|
||||
});
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash';
|
||||
import uuid from 'uuid';
|
||||
import { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { TrustedAppGenerator } from '../../../../common/endpoint/data_generators/trusted_app_generator';
|
||||
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';
|
||||
|
@ -54,6 +55,14 @@ export const getExceptionProviderMock = (): ExceptionListItemSchema => {
|
|||
},
|
||||
],
|
||||
tags: ['policy:all'],
|
||||
comments: [
|
||||
{
|
||||
id: uuid.v4(),
|
||||
comment: 'test',
|
||||
created_at: new Date().toISOString(),
|
||||
created_by: 'Justa',
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -10,6 +10,13 @@ import { EffectScope, TrustedApp } from '../../../../common/endpoint/types';
|
|||
import { ContextMenuItemNavByRouterProps } from '../context_menu_with_router_support/context_menu_item_nav_by_router';
|
||||
|
||||
export type AnyArtifact = ExceptionListItemSchema | TrustedApp;
|
||||
export interface ArtifactInfoEntry {
|
||||
field: string;
|
||||
type: string;
|
||||
operator: string;
|
||||
value: string;
|
||||
}
|
||||
type ArtifactInfoEntries = ArtifactInfoEntry & { entries?: ArtifactInfoEntry[] };
|
||||
|
||||
/**
|
||||
* A normalized structured that is used internally through out the card's components.
|
||||
|
@ -17,16 +24,11 @@ export type AnyArtifact = ExceptionListItemSchema | TrustedApp;
|
|||
export interface ArtifactInfo
|
||||
extends Pick<
|
||||
ExceptionListItemSchema,
|
||||
'name' | 'created_at' | 'updated_at' | 'created_by' | 'updated_by' | 'description'
|
||||
'name' | 'created_at' | 'updated_at' | 'created_by' | 'updated_by' | 'description' | 'comments'
|
||||
> {
|
||||
effectScope: EffectScope;
|
||||
os: string;
|
||||
entries: Array<{
|
||||
field: string;
|
||||
type: string;
|
||||
operator: string;
|
||||
value: string;
|
||||
}>;
|
||||
entries: ArtifactInfoEntries[];
|
||||
}
|
||||
|
||||
export interface MenuItemPropsByPolicyId {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { EuiAvatar, EuiText, EuiCommentProps } from '@elastic/eui';
|
||||
import styled from 'styled-components';
|
||||
import { CommentsArray } from '@kbn/securitysolution-io-ts-list-types';
|
||||
import { COMMENT_EVENT } from '../../../../common/components/exceptions/translations';
|
||||
import { FormattedRelativePreferenceDate } from '../../../../common/components/formatted_date';
|
||||
|
||||
const CustomEuiAvatar = styled(EuiAvatar)`
|
||||
background-color: ${({ theme }) => theme.eui.euiColorLightShade} !important;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Formats ExceptionItem.comments into EuiCommentList format
|
||||
*
|
||||
* @param comments ExceptionItem.comments
|
||||
*/
|
||||
export const getFormattedComments = (comments: CommentsArray): EuiCommentProps[] => {
|
||||
return comments.map((commentItem) => ({
|
||||
username: commentItem.created_by,
|
||||
timestamp: (
|
||||
<FormattedRelativePreferenceDate value={commentItem.created_at} dateFormat="MMM D, YYYY" />
|
||||
),
|
||||
event: COMMENT_EVENT,
|
||||
timelineIcon: <CustomEuiAvatar size="s" name={commentItem.created_by} />,
|
||||
children: <EuiText size="s">{commentItem.comment}</EuiText>,
|
||||
}));
|
||||
};
|
|
@ -24,6 +24,7 @@ export const mapToArtifactInfo = (_item: MaybeImmutable<AnyArtifact>): ArtifactI
|
|||
updated_at,
|
||||
updated_by,
|
||||
description,
|
||||
comments: isTrustedApp(item) ? [] : item.comments,
|
||||
entries: entries as unknown as ArtifactInfo['entries'],
|
||||
os: isTrustedApp(item) ? item.os : getOsFromExceptionItem(item),
|
||||
effectScope: isTrustedApp(item) ? item.effectScope : getEffectScopeFromExceptionItem(item),
|
||||
|
|
|
@ -109,19 +109,15 @@ describe('When on the Event Filters List Page', () => {
|
|||
it('should render expected fields on card', async () => {
|
||||
render();
|
||||
await dataReceived();
|
||||
const eventMeta = ([] as HTMLElement[]).map.call(
|
||||
renderResult.getByTestId('exceptionsViewerItemDetails').querySelectorAll('dd'),
|
||||
(ele) => ele.textContent
|
||||
);
|
||||
|
||||
expect(eventMeta).toEqual([
|
||||
'some name',
|
||||
'April 20th 2020 @ 11:25:31',
|
||||
'some user',
|
||||
'April 20th 2020 @ 11:25:31',
|
||||
'some user',
|
||||
'some description',
|
||||
]);
|
||||
[
|
||||
['subHeader-touchedBy-createdBy-value', 'some user'],
|
||||
['subHeader-touchedBy-updatedBy-value', 'some user'],
|
||||
['header-created-value', '4/20/2020'],
|
||||
['header-updated-value', '4/20/2020'],
|
||||
].forEach(([suffix, value]) =>
|
||||
expect(renderResult.getByTestId(`eventFilterCard-${suffix}`).textContent).toEqual(value)
|
||||
);
|
||||
});
|
||||
|
||||
it('should show API error if one is encountered', async () => {
|
||||
|
@ -143,8 +139,13 @@ describe('When on the Event Filters List Page', () => {
|
|||
it('should show modal when delete is clicked on a card', async () => {
|
||||
render();
|
||||
await dataReceived();
|
||||
act(() => {
|
||||
fireEvent.click(renderResult.getByTestId('exceptionsViewerDeleteBtn'));
|
||||
|
||||
await act(async () => {
|
||||
(await renderResult.findAllByTestId('eventFilterCard-header-actions-button'))[0].click();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
(await renderResult.findByTestId('deleteEventFilterAction')).click();
|
||||
});
|
||||
|
||||
expect(
|
||||
|
|
|
@ -36,16 +36,20 @@ import {
|
|||
} from '../store/selector';
|
||||
import { PaginatedContent, PaginatedContentProps } from '../../../components/paginated_content';
|
||||
import { Immutable, ListPageRouteState } from '../../../../../common/endpoint/types';
|
||||
import { ExceptionItem } from '../../../../common/components/exceptions/viewer/exception_item';
|
||||
import {
|
||||
ExceptionItem,
|
||||
ExceptionItemProps,
|
||||
} from '../../../../common/components/exceptions/viewer/exception_item';
|
||||
AnyArtifact,
|
||||
ArtifactEntryCard,
|
||||
ArtifactEntryCardProps,
|
||||
} from '../../../components/artifact_entry_card';
|
||||
import { EventFilterDeleteModal } from './components/event_filter_delete_modal';
|
||||
|
||||
import { SearchExceptions } from '../../../components/search_exceptions';
|
||||
import { BackToExternalAppButton } from '../../../components/back_to_external_app_button';
|
||||
import { ABOUT_EVENT_FILTERS } from './translations';
|
||||
|
||||
type ArtifactEntryCardType = typeof ArtifactEntryCard;
|
||||
|
||||
type EventListPaginatedContent = PaginatedContentProps<
|
||||
Immutable<ExceptionListItemSchema>,
|
||||
typeof ExceptionItem
|
||||
|
@ -61,6 +65,20 @@ const AdministrationListPage = styled(_AdministrationListPage)`
|
|||
}
|
||||
`;
|
||||
|
||||
const EDIT_EVENT_FILTER_ACTION_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.eventFilters.list.cardAction.edit',
|
||||
{
|
||||
defaultMessage: 'Edit event filter',
|
||||
}
|
||||
);
|
||||
|
||||
const DELETE_EVENT_FILTER_ACTION_LABEL = i18n.translate(
|
||||
'xpack.securitySolution.eventFilters.list.cardAction.delete',
|
||||
{
|
||||
defaultMessage: 'Delete event filter',
|
||||
}
|
||||
);
|
||||
|
||||
export const EventFiltersListPage = memo(() => {
|
||||
const { state: routeState } = useLocation<ListPageRouteState | undefined>();
|
||||
const history = useHistory();
|
||||
|
@ -133,41 +151,6 @@ export const EventFiltersListPage = memo(() => {
|
|||
[navigateCallback]
|
||||
);
|
||||
|
||||
const handleItemEdit: ExceptionItemProps['onEditException'] = useCallback(
|
||||
(item: ExceptionListItemSchema) => {
|
||||
navigateCallback({
|
||||
show: 'edit',
|
||||
id: item.id,
|
||||
});
|
||||
},
|
||||
[navigateCallback]
|
||||
);
|
||||
|
||||
const handleItemDelete: ExceptionItemProps['onDeleteException'] = useCallback(
|
||||
({ id }) => {
|
||||
dispatch({
|
||||
type: 'eventFilterForDeletion',
|
||||
// Casting below needed due to error around the comments array needing to be mutable
|
||||
payload: listItems.find((item) => item.id === id)! as ExceptionListItemSchema,
|
||||
});
|
||||
},
|
||||
[dispatch, listItems]
|
||||
);
|
||||
|
||||
const handleItemComponentProps: EventListPaginatedContent['itemComponentProps'] = useCallback(
|
||||
(exceptionItem) => ({
|
||||
exceptionItem: exceptionItem as ExceptionListItemSchema,
|
||||
loadingItemIds: [],
|
||||
commentsAccordionId: '',
|
||||
onEditException: handleItemEdit,
|
||||
onDeleteException: handleItemDelete,
|
||||
showModified: true,
|
||||
showName: true,
|
||||
'data-test-subj': `eventFilterCard`,
|
||||
}),
|
||||
[handleItemDelete, handleItemEdit]
|
||||
);
|
||||
|
||||
const handlePaginatedContentChange: EventListPaginatedContent['onChange'] = useCallback(
|
||||
({ pageIndex, pageSize }) => {
|
||||
navigateCallback({
|
||||
|
@ -186,6 +169,59 @@ export const EventFiltersListPage = memo(() => {
|
|||
[navigateCallback, dispatch]
|
||||
);
|
||||
|
||||
const artifactCardPropsPerItem = useMemo(() => {
|
||||
const cachedCardProps: Record<string, ArtifactEntryCardProps> = {};
|
||||
|
||||
// Casting `listItems` below to remove the `Immutable<>` from it in order to prevent errors
|
||||
// with common component's props
|
||||
for (const eventFilter of listItems as ExceptionListItemSchema[]) {
|
||||
let policies: ArtifactEntryCardProps['policies'];
|
||||
|
||||
cachedCardProps[eventFilter.id] = {
|
||||
item: eventFilter as AnyArtifact,
|
||||
policies,
|
||||
hideDescription: true,
|
||||
'data-test-subj': 'eventFilterCard',
|
||||
actions: [
|
||||
{
|
||||
icon: 'controlsHorizontal',
|
||||
onClick: () => {
|
||||
history.push(
|
||||
getEventFiltersListPath({
|
||||
...location,
|
||||
show: 'edit',
|
||||
id: eventFilter.id,
|
||||
})
|
||||
);
|
||||
},
|
||||
'data-test-subj': 'editEventFilterAction',
|
||||
children: EDIT_EVENT_FILTER_ACTION_LABEL,
|
||||
},
|
||||
{
|
||||
icon: 'trash',
|
||||
onClick: () => {
|
||||
dispatch({
|
||||
type: 'eventFilterForDeletion',
|
||||
payload: eventFilter,
|
||||
});
|
||||
},
|
||||
'data-test-subj': 'deleteEventFilterAction',
|
||||
children: DELETE_EVENT_FILTER_ACTION_LABEL,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
return cachedCardProps;
|
||||
}, [dispatch, history, listItems, location]);
|
||||
|
||||
const handleArtifactCardProps = useCallback(
|
||||
(eventFilter: ExceptionListItemSchema) => {
|
||||
return artifactCardPropsPerItem[eventFilter.id];
|
||||
},
|
||||
[artifactCardPropsPerItem]
|
||||
);
|
||||
|
||||
return (
|
||||
<AdministrationListPage
|
||||
headerBackComponent={backButton}
|
||||
|
@ -244,10 +280,10 @@ export const EventFiltersListPage = memo(() => {
|
|||
</>
|
||||
)}
|
||||
|
||||
<PaginatedContent<Immutable<ExceptionListItemSchema>, typeof ExceptionItem>
|
||||
<PaginatedContent<ExceptionListItemSchema, ArtifactEntryCardType>
|
||||
items={listItems}
|
||||
ItemComponent={ExceptionItem}
|
||||
itemComponentProps={handleItemComponentProps}
|
||||
ItemComponent={ArtifactEntryCard}
|
||||
itemComponentProps={handleArtifactCardProps}
|
||||
onChange={handlePaginatedContentChange}
|
||||
error={fetchError?.message}
|
||||
loading={isLoading}
|
||||
|
|
|
@ -138,6 +138,7 @@ export const TrustedAppsGrid = memo(() => {
|
|||
cachedCardProps[trustedApp.id] = {
|
||||
item: trustedApp,
|
||||
policies,
|
||||
hideComments: true,
|
||||
'data-test-subj': 'trustedAppCard',
|
||||
actions: [
|
||||
{
|
||||
|
|
Loading…
Reference in a new issue