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

Finding flyout changes #132115

Merged
merged 18 commits into from
May 15, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -9,88 +9,56 @@ import {
EuiCodeBlock,
EuiFlexItem,
EuiSpacer,
EuiDescriptionList,
EuiTextColor,
EuiFlyout,
EuiFlyoutHeader,
EuiTitle,
EuiFlyoutBody,
EuiTabs,
EuiTab,
EuiFlexGrid,
EuiCard,
EuiFlexGroup,
EuiIcon,
type PropsOf,
EuiMarkdownFormat,
} from '@elastic/eui';
import { assertNever } from '@kbn/std';
import moment from 'moment';
import type { CspFinding } from '../types';
import { CspEvaluationBadge } from '../../../components/csp_evaluation_badge';
import * as TEXT from '../translations';
import cisLogoIcon from '../../../assets/icons/cis_logo.svg';
import k8sLogoIcon from '../../../assets/icons/k8s_logo.svg';
import { ResourceTab } from './resource_tab';
import { JsonTab } from './json_tab';
import { OverviewTab } from './overview_tab';
import { RuleTab } from './rule_tab';

const tabs = [
{ title: TEXT.REMEDIATION, id: 'remediation' },
{ title: TEXT.OVERVIEW, id: 'overview' },
{ title: TEXT.RULE, id: 'rule' },
{ title: TEXT.RESOURCE, id: 'resource' },
{ title: TEXT.GENERAL, id: 'general' },
{ title: TEXT.JSON, id: 'json' },
] as const;

const CodeBlock: React.FC<PropsOf<typeof EuiCodeBlock>> = (props) => (
export const CodeBlock: React.FC<PropsOf<typeof EuiCodeBlock>> = (props) => (
<EuiCodeBlock isCopyable paddingSize="s" overflowHeight={300} {...props} />
);

const Markdown: React.FC<PropsOf<typeof EuiMarkdownFormat>> = (props) => (
export const Markdown: React.FC<PropsOf<typeof EuiMarkdownFormat>> = (props) => (
<EuiMarkdownFormat textSize="s" {...props} />
);

type FindingsTab = typeof tabs[number];

type EuiListItemsProps = NonNullable<PropsOf<typeof EuiDescriptionList>['listItems']>[number];

interface Card {
title: string;
listItems: Array<[EuiListItemsProps['title'], EuiListItemsProps['description']]>;
}

interface FindingFlyoutProps {
onClose(): void;
findings: CspFinding;
}

const Cards = ({ data }: { data: Card[] }) => (
<EuiFlexGrid direction="column" gutterSize={'l'}>
{data.map((card) => (
<EuiFlexItem key={card.title} style={{ display: 'block' }}>
<EuiCard textAlign="left" title={card.title} hasBorder>
<EuiDescriptionList
compressed={false}
type="column"
listItems={card.listItems.map((v) => ({ title: v[0], description: v[1] }))}
style={{ flexFlow: 'column' }}
descriptionProps={{
style: { width: '100%' },
}}
/>
</EuiCard>
</EuiFlexItem>
))}
</EuiFlexGrid>
);

const FindingsTab = ({ tab, findings }: { findings: CspFinding; tab: FindingsTab }) => {
switch (tab.id) {
case 'remediation':
return <Cards data={getRemediationCards(findings)} />;
case 'overview':
return <OverviewTab data={findings} />;
case 'rule':
return <RuleTab data={findings} />;
case 'resource':
return <ResourceTab data={findings} />;
case 'general':
return <Cards data={getGeneralCards(findings)} />;
case 'json':
return <JsonTab data={findings} />;
default:
Expand Down Expand Up @@ -131,55 +99,3 @@ export const FindingsRuleFlyout = ({ onClose, findings }: FindingFlyoutProps) =>
</EuiFlyout>
);
};

const getGeneralCards = ({ rule, ...rest }: CspFinding): Card[] => [
{
title: TEXT.RULE,
listItems: [
[TEXT.RULE_EVALUATED_AT, moment(rest['@timestamp']).format('MMMM D, YYYY @ HH:mm:ss.SSS')],
[
TEXT.FRAMEWORK_SOURCES,
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type={cisLogoIcon} size="xxl" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIcon type={k8sLogoIcon} size="xxl" />
</EuiFlexItem>
</EuiFlexGroup>,
],
[TEXT.CIS_SECTION, rule.section],
[TEXT.PROFILE_APPLICABILITY, <Markdown>{rule.profile_applicability}</Markdown>],
[TEXT.BENCHMARK, rule.benchmark.name],
[TEXT.NAME, rule.name],
[TEXT.DESCRIPTION, <Markdown>{rule.description}</Markdown>],
[TEXT.AUDIT, <Markdown>{rule.audit}</Markdown>],
[TEXT.REFERENCES, <Markdown>{rule.references}</Markdown>],
],
},
];

const getRemediationCards = ({ result, rule, ...rest }: CspFinding): Card[] => [
{
title: TEXT.RESULT_DETAILS,
listItems: [
result.expected
? [TEXT.EXPECTED, <CodeBlock>{JSON.stringify(result.expected, null, 2)}</CodeBlock>]
: ['', ''],
[TEXT.EVIDENCE, <CodeBlock>{JSON.stringify(result.evidence, null, 2)}</CodeBlock>],
[
TEXT.RULE_EVALUATED_AT,
<span>{moment(rest['@timestamp']).format('MMMM D, YYYY @ HH:mm:ss.SSS')}</span>,
],
],
},
{
title: TEXT.REMEDIATION,
listItems: [
['', <Markdown>{rule.remediation}</Markdown>],
[TEXT.IMPACT, <Markdown>{rule.impact}</Markdown>],
[TEXT.DEFAULT_VALUE, <Markdown>{rule.default_value}</Markdown>],
[TEXT.RATIONALE, <Markdown>{rule.rationale}</Markdown>],
],
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* 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 {
EuiAccordion,
EuiDescriptionList,
EuiFlexGroup,
EuiFlexItem,
EuiIcon,
EuiPanel,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import React, { useMemo } from 'react';
import moment from 'moment';
import type { EuiDescriptionListProps, EuiAccordionProps } from '@elastic/eui';
import cisLogoIcon from '../../../assets/icons/cis_logo.svg';
import k8sLogoIcon from '../../../assets/icons/k8s_logo.svg';
import * as TEXT from '../translations';
import { CspFinding } from '../types';
import { CodeBlock, Markdown } from './findings_flyout';

type Accordion = Pick<EuiAccordionProps, 'title' | 'id' | 'initialIsOpen'> &
Pick<EuiDescriptionListProps, 'listItems'>;

const getDetailsList = (data: CspFinding) => [
{
title: TEXT.EVALUATED_AT,
description: moment(data['@timestamp']).format('MMMM D, YYYY @ HH:mm:ss.SSS'),
},
{
title: TEXT.RESOURCE_NAME,
description: data.resource.name,
},
{
title: TEXT.RULE_NAME,
description: data.rule.name,
},
{
title: TEXT.FRAMEWORK_SOURCES,
description: (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type={cisLogoIcon} size="xxl" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIcon type={k8sLogoIcon} size="xxl" />
</EuiFlexItem>
</EuiFlexGroup>
),
},
{
title: TEXT.CIS_SECTION,
description: data.rule.section,
},
];

const getRemediationList = ({ rule }: CspFinding) => [
{
title: '',
description: <Markdown>{rule.remediation}</Markdown>,
},
{
title: TEXT.IMPACT,
description: <Markdown>{rule.impact}</Markdown>,
},
{
title: TEXT.DEFAULT_VALUE,
description: <Markdown>{rule.default_value}</Markdown>,
},
{
title: TEXT.RATIONALE,
description: <Markdown>{rule.rationale}</Markdown>,
},
];

const getEvidenceList = ({ result }: CspFinding) =>
[
result.expected && {
title: TEXT.EXPECTED,
description: <CodeBlock>{JSON.stringify(result.expected, null, 2)}</CodeBlock>,
},
{
title: TEXT.ACTUAL,
description: <CodeBlock>{JSON.stringify(result.evidence, null, 2)}</CodeBlock>,
},
].filter(Boolean) as EuiDescriptionListProps['listItems'];

export const OverviewTab = ({ data }: { data: CspFinding }) => {
const accordions: Accordion[] = useMemo(
() => [
{
initialIsOpen: true,
title: TEXT.DETAILS,
id: 'detailsAccordion',
listItems: getDetailsList(data),
},
{
initialIsOpen: true,
title: TEXT.REMEDIATION,
id: 'remediationAccordion',
listItems: getRemediationList(data),
},
{
initialIsOpen: false,
title: TEXT.EVIDENCE,
id: 'evidenceAccordion',
listItems: getEvidenceList(data),
},
],
[data]
);

return (
<>
{accordions.map((accordion) => (
<React.Fragment key={accordion.id}>
<EuiPanel hasShadow={false} hasBorder>
<EuiAccordion
id={accordion.id}
buttonContent={
<EuiText>
<strong>{accordion.title}</strong>
</EuiText>
}
arrowDisplay="right"
initialIsOpen={accordion.initialIsOpen}
>
<EuiSpacer size="m" />
<EuiDescriptionList listItems={accordion.listItems} />
</EuiAccordion>
</EuiPanel>
<EuiSpacer size="m" />
</React.Fragment>
))}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* 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 { EuiDescriptionList, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiPanel } from '@elastic/eui';
import React from 'react';
import cisLogoIcon from '../../../assets/icons/cis_logo.svg';
import k8sLogoIcon from '../../../assets/icons/k8s_logo.svg';
import * as TEXT from '../translations';
import { CspFinding } from '../types';
import { Markdown } from './findings_flyout';

const getRuleList = ({ rule }: CspFinding) => [
{
title: TEXT.NAME,
description: rule.name,
},
{
title: TEXT.DESCRIPTION,
description: <Markdown>{rule.description}</Markdown>,
},
{
title: TEXT.FRAMEWORK_SOURCES,
description: (
<EuiFlexGroup gutterSize="s">
<EuiFlexItem grow={false}>
<EuiIcon type={cisLogoIcon} size="xxl" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiIcon type={k8sLogoIcon} size="xxl" />
</EuiFlexItem>
</EuiFlexGroup>
),
},
{
title: TEXT.CIS_SECTION,
description: rule.section,
},
{
title: TEXT.PROFILE_APPLICABILITY,
description: <Markdown>{rule.profile_applicability}</Markdown>,
},
{
title: TEXT.BENCHMARK,
description: rule.benchmark.name,
},

{
title: TEXT.AUDIT,
description: <Markdown>{rule.audit}</Markdown>,
},
{
title: TEXT.REFERENCES,
description: <Markdown>{rule.references}</Markdown>,
},
];

export const RuleTab = ({ data }: { data: CspFinding }) => (
<EuiPanel hasShadow={false} hasBorder>
<EuiDescriptionList listItems={getRuleList(data)} />
</EuiPanel>
);
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const getFakeFindings = (name: string): CspFinding & { id: string } => ({
version: chance.string(),
},
resource: {
name: chance.string(),
filename: chance.string(),
type: chance.string(),
path: chance.string(),
Expand Down
Loading