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

Exceptions List component #140985

Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0b07b88
add components with a draft exception-list-details to test
WafaaNasr Sep 19, 2022
b2f8121
fix jest config in xPack=> security=> Public
WafaaNasr Sep 20, 2022
c3f86c6
fix tests
WafaaNasr Sep 20, 2022
e16c1b4
fix header test and use RTL
WafaaNasr Sep 20, 2022
90751c1
covert meta test to use RTL and header
WafaaNasr Sep 20, 2022
0e460c0
fix utlity messageid
WafaaNasr Sep 20, 2022
f97b206
fix messageid in utilty
WafaaNasr Sep 20, 2022
d1bf46c
create osCondition, entryContent and entryContent.helper from Conditi…
WafaaNasr Sep 21, 2022
2102337
comment test until fixing all
WafaaNasr Sep 22, 2022
d680afa
Merge branch 'main' into refactor/exception-list-common-component-bas…
kibanamachine Sep 22, 2022
86c2ac3
create package with first components + test + jest config
WafaaNasr Sep 26, 2022
1d0bd61
add constants for url
WafaaNasr Sep 26, 2022
94282c8
[CI] Auto-commit changed files from 'node scripts/generate codeowners'
kibanamachine Sep 26, 2022
1bcf876
disable tests until finishing moving rest of components or fix it+ ad…
WafaaNasr Sep 26, 2022
8e9ee1e
add exceptionList-components +fixning build issues
WafaaNasr Sep 27, 2022
7cc521f
add exceptionList-components +fixning build issues
WafaaNasr Sep 27, 2022
853cedc
fix translations id + pass comments as props
WafaaNasr Sep 27, 2022
a633535
move utiitly out of package until moving all + fixing css
WafaaNasr Sep 27, 2022
6b9cce2
Merge branch 'main' of github.com:elastic/kibana into pr/140985
Sep 27, 2022
ea504bc
copy non-js/ts files through babel
Sep 27, 2022
936ed09
remove list-details-components
WafaaNasr Sep 27, 2022
d2275dc
apply comments
WafaaNasr Sep 27, 2022
af0d816
apply comments in references
WafaaNasr Sep 27, 2022
3a1c9b2
fix meta tests
WafaaNasr Sep 28, 2022
04789cd
update tests + add some descriptions
WafaaNasr Sep 28, 2022
fbd4d83
Merge branch 'main' into refactor/exception-list-common-component-bas…
kibanamachine Sep 28, 2022
65f2e0e
fix camelcase file name in Readme
WafaaNasr Sep 28, 2022
5621dac
fix camelcase file name in Readme
WafaaNasr Sep 28, 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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* 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 { render } from '@testing-library/react';

import { EmptyViewerState } from './empty_viewer_state';
import { ViewerStatus } from '../types';

describe('EmptyViewerState', () => {
it('it renders error screen when "viewerStatus" is "error" with the default title and body', () => {
WafaaNasr marked this conversation as resolved.
Show resolved Hide resolved
const wrapper = render(
<EmptyViewerState
isReadOnly={false}
viewerStatus={ViewerStatus.ERROR}
onCreateExceptionListItem={jest.fn()}
/>
);

expect(wrapper.getByTestId('error_viewer_state')).toBeTruthy();
expect(wrapper).toMatchSnapshot();
expect(wrapper.getByTestId('error_title')).toHaveTextContent('Unable to load exception items');
expect(wrapper.getByTestId('error_body')).toHaveTextContent(
'There was an error loading the exception items. Contact your administrator for help.'
);
});
it('it renders error screen when "viewerStatus" is "error" when sending the title and body props', () => {
const wrapper = render(
<EmptyViewerState
isReadOnly={false}
viewerStatus={ViewerStatus.ERROR}
onCreateExceptionListItem={jest.fn()}
title="Error title"
body="Error body"
/>
);

expect(wrapper.getByTestId('error_viewer_state')).toBeTruthy();
expect(wrapper).toMatchSnapshot();
expect(wrapper.getByTestId('error_title')).toHaveTextContent('Error title');
expect(wrapper.getByTestId('error_body')).toHaveTextContent('Error body');
});
it('it renders loading screen when "viewerStatus" is "loading"', () => {
const wrapper = render(
<EmptyViewerState
isReadOnly={false}
viewerStatus={ViewerStatus.LOADING}
onCreateExceptionListItem={jest.fn()}
/>
);

expect(wrapper.getByTestId('loading_viewer_state')).toBeTruthy();
expect(wrapper).toMatchSnapshot();
});
it('it renders empty search screen when "viewerStatus" is "empty_search" with the default title and body', () => {
const wrapper = render(
<EmptyViewerState
isReadOnly={false}
viewerStatus={ViewerStatus.EMPTY_SEARCH}
onCreateExceptionListItem={jest.fn()}
/>
);

expect(wrapper.getByTestId('empty_search_viewer_state')).toBeTruthy();
expect(wrapper).toMatchSnapshot();
expect(wrapper.getByTestId('empty_search_title')).toHaveTextContent(
'No results match your search criteria'
);
expect(wrapper.getByTestId('empty_search_body')).toHaveTextContent('Try modifying your search');
});
it('it renders empty search screen when "viewerStatus" is "empty_search" when sending title and body props', () => {
const wrapper = render(
<EmptyViewerState
isReadOnly={false}
viewerStatus={ViewerStatus.EMPTY_SEARCH}
onCreateExceptionListItem={jest.fn()}
title="Empty search title"
body="Empty search body"
/>
);

expect(wrapper.getByTestId('empty_search_viewer_state')).toBeTruthy();
expect(wrapper).toMatchSnapshot();
expect(wrapper.getByTestId('empty_search_title')).toHaveTextContent('Empty search title');
expect(wrapper.getByTestId('empty_search_body')).toHaveTextContent('Empty search body');
});
it('it renders no items screen when "viewerStatus" is "empty" when sending title and body props', () => {
const wrapper = render(
<EmptyViewerState
isReadOnly={false}
viewerStatus={ViewerStatus.EMPTY}
onCreateExceptionListItem={jest.fn()}
body="There are no endpoint exceptions."
buttonText="Add endpoint exception"
/>
);

const { getByTestId } = wrapper;
expect(getByTestId('empty_body')).toHaveTextContent('There are no endpoint exceptions.');
expect(getByTestId('empty_state_button')).toHaveTextContent('Add endpoint exception');
expect(getByTestId('empty_viewer_state')).toBeTruthy();
});
it('it renders no items screen when "viewerStatus" is "empty" with default title and body props', () => {
const wrapper = render(
<EmptyViewerState
isReadOnly={false}
viewerStatus={ViewerStatus.EMPTY}
onCreateExceptionListItem={jest.fn()}
/>
);

const { getByTestId } = wrapper;
expect(wrapper).toMatchSnapshot();
expect(getByTestId('empty_viewer_state')).toBeTruthy();
expect(getByTestId('empty_title')).toHaveTextContent('Add exceptions to this rule');
expect(getByTestId('empty_body')).toHaveTextContent(
'There is no exception in your rule. Create your first rule exception.'
);
expect(getByTestId('empty_state_button')).toHaveTextContent('Create rule exception');
});
it('it renders no items screen when "viewerStatus" is "empty" with default title and body props and listType endPoint', () => {
const wrapper = render(
<EmptyViewerState
isReadOnly={false}
viewerStatus={ViewerStatus.EMPTY}
onCreateExceptionListItem={jest.fn()}
listType="endpoint"
/>
);

const { getByTestId } = wrapper;
expect(wrapper).toMatchSnapshot();
expect(getByTestId('empty_viewer_state')).toBeTruthy();
expect(getByTestId('empty_title')).toHaveTextContent('Add exceptions to this rule');
expect(getByTestId('empty_body')).toHaveTextContent(
'There is no exception in your rule. Create your first rule exception.'
);
expect(getByTestId('empty_state_button')).toHaveTextContent('Create endpoint exception');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* 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, { useMemo } from 'react';
import type { FC } from 'react';

import {
EuiLoadingContent,
EuiImage,
EuiEmptyPrompt,
EuiButton,
useEuiTheme,
EuiPanel,
} from '@elastic/eui';
import type { ExpressionColor } from '@elastic/eui/src/components/expression/expression';
import type { EuiFacetGroupLayout } from '@elastic/eui/src/components/facet/facet_group';

import illustration from '../../../../../common/images/illustration_product_no_results_magnifying_glass.svg';
import { ViewerStatus } from '../types';
import * as i18n from '../translations';

interface EmptyViewerStateProps {
title?: string;
body?: string;
buttonText?: string;
listType?: string;
WafaaNasr marked this conversation as resolved.
Show resolved Hide resolved
isReadOnly: boolean;
viewerStatus: ViewerStatus;
onCreateExceptionListItem?: () => void | null;
}

const EmptyViewerStateComponent: FC<EmptyViewerStateProps> = ({
title,
body,
buttonText,
listType,
isReadOnly,
viewerStatus,
onCreateExceptionListItem,
}) => {
const { euiTheme } = useEuiTheme();

const euiEmptyPromptProps = useMemo(() => {
switch (viewerStatus) {
case ViewerStatus.ERROR: {
return {
color: 'danger' as ExpressionColor,
iconType: 'alert',
title: (
<h2 data-test-subj="error_title">{title || i18n.EMPTY_VIEWER_STATE_ERROR_TITLE}</h2>
),
body: <p data-test-subj="error_body">{body || i18n.EMPTY_VIEWER_STATE_ERROR_BODY}</p>,
'data-test-subj': 'error_viewer_state',
};
}
case ViewerStatus.EMPTY:
return {
color: 'subdued' as ExpressionColor,
iconType: 'plusInCircle',
iconColor: euiTheme.colors.darkestShade,
title: (
<h2 data-test-subj="empty_title">{title || i18n.EMPTY_VIEWER_STATE_EMPTY_TITLE}</h2>
),
body: <p data-test-subj="empty_body">{body || i18n.EMPTY_VIEWER_STATE_EMPTY_BODY}</p>,
'data-test-subj': 'empty_viewer_state',
actions: [
<EuiButton
data-test-subj="empty_state_button"
onClick={onCreateExceptionListItem}
iconType="plusInCircle"
color="primary"
isDisabled={isReadOnly}
fill
>
{buttonText || i18n.EMPTY_VIEWER_STATE_EMPTY_VIEWER_BUTTON(listType || 'rule')}
</EuiButton>,
],
};
case ViewerStatus.EMPTY_SEARCH:
return {
color: 'plain' as ExpressionColor,
WafaaNasr marked this conversation as resolved.
Show resolved Hide resolved
layout: 'horizontal' as EuiFacetGroupLayout,
hasBorder: true,
hasShadow: false,
icon: <EuiImage size="fullWidth" alt="" url={illustration} />,
title: (
<h3 data-test-subj="empty_search_title">
{title || i18n.EMPTY_VIEWER_STATE_EMPTY_SEARCH_TITLE}
</h3>
),
body: (
<p data-test-subj="empty_search_body">
{body || i18n.EMPTY_VIEWER_STATE_EMPTY_SEARCH_BODY}
</p>
),
'data-test-subj': 'empty_search_viewer_state',
};
}
}, [
viewerStatus,
euiTheme.colors.darkestShade,
title,
body,
onCreateExceptionListItem,
isReadOnly,
buttonText,
listType,
]);

if (viewerStatus === ViewerStatus.LOADING || viewerStatus === ViewerStatus.SEARCHING)
return <EuiLoadingContent lines={4} data-test-subj="loading_viewer_state" />;

return (
<EuiPanel
hasShadow={false}
hasBorder={false}
color={viewerStatus === 'empty_search' ? 'subdued' : 'transparent'}
style={{
margin: `${euiTheme.size.l} 0`,
padding: `${euiTheme.size.l} 0`,
}}
>
<EuiEmptyPrompt {...euiEmptyPromptProps} />
</EuiPanel>
);
};

export const EmptyViewerState = React.memo(EmptyViewerStateComponent);

EmptyViewerState.displayName = 'EmptyViewerState';
Original file line number Diff line number Diff line change
@@ -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 React, { memo } from 'react';
import type { EuiCommentProps } from '@elastic/eui';
import {
EuiAccordion,
EuiCommentList,
EuiFlexItem,
EuiPanel,
EuiText,
useEuiTheme,
} from '@elastic/eui';

import * as i18n from '../translations';

export interface ExceptionItemCardCommentsProps {
comments: EuiCommentProps[];
}

export const ExceptionItemCardComments = memo<ExceptionItemCardCommentsProps>(({ comments }) => {
const { euiTheme } = useEuiTheme();

return (
<EuiFlexItem>
<EuiAccordion
id="exceptionItemCardComments"
WafaaNasr marked this conversation as resolved.
Show resolved Hide resolved
buttonContent={
<EuiText size="s" style={{ color: euiTheme.colors.primary }}>
{i18n.exceptionItemCardCommentsAccordion(comments.length)}
</EuiText>
}
arrowDisplay="none"
data-test-subj="exceptionsViewerCommentAccordion"
>
<EuiPanel
data-test-subj="accordion-content"
hasBorder={false}
hasShadow={false}
paddingSize="m"
>
<EuiCommentList data-test-subj="accordion-comment-list" comments={comments} />
WafaaNasr marked this conversation as resolved.
Show resolved Hide resolved
</EuiPanel>
</EuiAccordion>
</EuiFlexItem>
);
});

ExceptionItemCardComments.displayName = 'ExceptionItemCardComments';
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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 { ListOperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types';
import * as i18n from '../translations';

export const OS_LABELS = Object.freeze({
linux: i18n.OS_LINUX,
mac: i18n.OS_MAC,
macos: i18n.OS_MAC,
windows: i18n.OS_WINDOWS,
});

export const OPERATOR_TYPE_LABELS_INCLUDED = Object.freeze({
[ListOperatorTypeEnum.NESTED]: i18n.CONDITION_OPERATOR_TYPE_NESTED,
[ListOperatorTypeEnum.MATCH_ANY]: i18n.CONDITION_OPERATOR_TYPE_MATCH_ANY,
[ListOperatorTypeEnum.MATCH]: i18n.CONDITION_OPERATOR_TYPE_MATCH,
[ListOperatorTypeEnum.WILDCARD]: i18n.CONDITION_OPERATOR_TYPE_WILDCARD_MATCHES,
[ListOperatorTypeEnum.EXISTS]: i18n.CONDITION_OPERATOR_TYPE_EXISTS,
[ListOperatorTypeEnum.LIST]: i18n.CONDITION_OPERATOR_TYPE_LIST,
});

export const OPERATOR_TYPE_LABELS_EXCLUDED = Object.freeze({
[ListOperatorTypeEnum.MATCH_ANY]: i18n.CONDITION_OPERATOR_TYPE_NOT_MATCH_ANY,
[ListOperatorTypeEnum.MATCH]: i18n.CONDITION_OPERATOR_TYPE_NOT_MATCH,
[ListOperatorTypeEnum.WILDCARD]: i18n.CONDITION_OPERATOR_TYPE_WILDCARD_DOES_NOT_MATCH,
[ListOperatorTypeEnum.EXISTS]: i18n.CONDITION_OPERATOR_TYPE_DOES_NOT_EXIST,
[ListOperatorTypeEnum.LIST]: i18n.CONDITION_OPERATOR_TYPE_NOT_IN_LIST,
});
Loading