Skip to content

Commit

Permalink
Finding flyout changes (#132115)
Browse files Browse the repository at this point in the history
  • Loading branch information
JordanSh authored May 15, 2022
1 parent d864dc8 commit 059f09d
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 94 deletions.
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,63 @@
/*
* 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 } 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 }) => (
<EuiDescriptionList listItems={getRuleList(data)} />
);
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

0 comments on commit 059f09d

Please sign in to comment.