diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx index be05e2b8418d..feec289432a6 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/findings_flyout.tsx @@ -9,7 +9,6 @@ import { EuiCodeBlock, EuiFlexItem, EuiSpacer, - EuiDescriptionList, EuiTextColor, EuiFlyout, EuiFlyoutHeader, @@ -17,80 +16,49 @@ import { 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> = (props) => ( +export const CodeBlock: React.FC> = (props) => ( ); -const Markdown: React.FC> = (props) => ( +export const Markdown: React.FC> = (props) => ( ); type FindingsTab = typeof tabs[number]; -type EuiListItemsProps = NonNullable['listItems']>[number]; - -interface Card { - title: string; - listItems: Array<[EuiListItemsProps['title'], EuiListItemsProps['description']]>; -} - interface FindingFlyoutProps { onClose(): void; findings: CspFinding; } -const Cards = ({ data }: { data: Card[] }) => ( - - {data.map((card) => ( - - - ({ title: v[0], description: v[1] }))} - style={{ flexFlow: 'column' }} - descriptionProps={{ - style: { width: '100%' }, - }} - /> - - - ))} - -); - const FindingsTab = ({ tab, findings }: { findings: CspFinding; tab: FindingsTab }) => { switch (tab.id) { - case 'remediation': - return ; + case 'overview': + return ; + case 'rule': + return ; case 'resource': return ; - case 'general': - return ; case 'json': return ; default: @@ -131,55 +99,3 @@ export const FindingsRuleFlyout = ({ onClose, findings }: FindingFlyoutProps) => ); }; - -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, - - - - - - - - , - ], - [TEXT.CIS_SECTION, rule.section], - [TEXT.PROFILE_APPLICABILITY, {rule.profile_applicability}], - [TEXT.BENCHMARK, rule.benchmark.name], - [TEXT.NAME, rule.name], - [TEXT.DESCRIPTION, {rule.description}], - [TEXT.AUDIT, {rule.audit}], - [TEXT.REFERENCES, {rule.references}], - ], - }, -]; - -const getRemediationCards = ({ result, rule, ...rest }: CspFinding): Card[] => [ - { - title: TEXT.RESULT_DETAILS, - listItems: [ - result.expected - ? [TEXT.EXPECTED, {JSON.stringify(result.expected, null, 2)}] - : ['', ''], - [TEXT.EVIDENCE, {JSON.stringify(result.evidence, null, 2)}], - [ - TEXT.RULE_EVALUATED_AT, - {moment(rest['@timestamp']).format('MMMM D, YYYY @ HH:mm:ss.SSS')}, - ], - ], - }, - { - title: TEXT.REMEDIATION, - listItems: [ - ['', {rule.remediation}], - [TEXT.IMPACT, {rule.impact}], - [TEXT.DEFAULT_VALUE, {rule.default_value}], - [TEXT.RATIONALE, {rule.rationale}], - ], - }, -]; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx new file mode 100644 index 000000000000..dfc78c13b0f3 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/overview_tab.tsx @@ -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 & + Pick; + +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: ( + + + + + + + + + ), + }, + { + title: TEXT.CIS_SECTION, + description: data.rule.section, + }, +]; + +const getRemediationList = ({ rule }: CspFinding) => [ + { + title: '', + description: {rule.remediation}, + }, + { + title: TEXT.IMPACT, + description: {rule.impact}, + }, + { + title: TEXT.DEFAULT_VALUE, + description: {rule.default_value}, + }, + { + title: TEXT.RATIONALE, + description: {rule.rationale}, + }, +]; + +const getEvidenceList = ({ result }: CspFinding) => + [ + result.expected && { + title: TEXT.EXPECTED, + description: {JSON.stringify(result.expected, null, 2)}, + }, + { + title: TEXT.ACTUAL, + description: {JSON.stringify(result.evidence, null, 2)}, + }, + ].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) => ( + + + + {accordion.title} + + } + arrowDisplay="right" + initialIsOpen={accordion.initialIsOpen} + > + + + + + + + ))} + + ); +}; diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/rule_tab.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/rule_tab.tsx new file mode 100644 index 000000000000..610e97798e92 --- /dev/null +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/findings_flyout/rule_tab.tsx @@ -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: {rule.description}, + }, + { + title: TEXT.FRAMEWORK_SOURCES, + description: ( + + + + + + + + + ), + }, + { + title: TEXT.CIS_SECTION, + description: rule.section, + }, + { + title: TEXT.PROFILE_APPLICABILITY, + description: {rule.profile_applicability}, + }, + { + title: TEXT.BENCHMARK, + description: rule.benchmark.name, + }, + + { + title: TEXT.AUDIT, + description: {rule.audit}, + }, + { + title: TEXT.REFERENCES, + description: {rule.references}, + }, +]; + +export const RuleTab = ({ data }: { data: CspFinding }) => ( + +); diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.test.tsx b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.test.tsx index d01af2fa96e9..9fb20ed19c25 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.test.tsx +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.test.tsx @@ -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(), diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/translations.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/translations.ts index 53d4b8a86e5c..13fcf7bc7b90 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/translations.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/translations.ts @@ -42,10 +42,18 @@ export const RESOURCE = i18n.translate('xpack.csp.findings.resourceLabel', { defaultMessage: 'Resource', }); +export const RESOURCE_NAME = i18n.translate('xpack.csp.findings.resourceNameLabel', { + defaultMessage: 'Resource Name', +}); + export const GENERAL = i18n.translate('xpack.csp.findings.findingsFlyout.generalTabLabel', { defaultMessage: 'General', }); +export const OVERVIEW = i18n.translate('xpack.csp.findings.findingsFlyout.overviewTabLabel', { + defaultMessage: 'Overview', +}); + export const JSON = i18n.translate('xpack.csp.findings.findingsFlyout.jsonTabLabel', { defaultMessage: 'JSON', }); @@ -137,6 +145,10 @@ export const RULE_EVALUATED_AT = i18n.translate('xpack.csp.findings.ruleEvaluate defaultMessage: 'Rule evaluated at', }); +export const EVALUATED_AT = i18n.translate('xpack.csp.findings.evaluatedAt', { + defaultMessage: 'Evaluated at', +}); + export const FRAMEWORK_SOURCES = i18n.translate('xpack.csp.findings.frameworkSourcesLabel', { defaultMessage: 'Framework Sources', }); @@ -173,6 +185,10 @@ export const EXPECTED = i18n.translate('xpack.csp.findings.expectedLabel', { defaultMessage: 'Expected', }); +export const ACTUAL = i18n.translate('xpack.csp.findings.actualLabel', { + defaultMessage: 'Actual', +}); + export const EVIDENCE = i18n.translate('xpack.csp.findings.evidenceLabel', { defaultMessage: 'Evidence', }); @@ -237,6 +253,10 @@ export const NO_FINDINGS = i18n.translate('xpack.csp.findings.nonFindingsLabel', defaultMessage: 'There are no Findings', }); +export const DETAILS = i18n.translate('xpack.csp.findings.findingsFlyout.detailsTabLabel', { + defaultMessage: 'Details', +}); + export const FINDINGS_SEARCH_PLACEHOLDER = i18n.translate( 'xpack.csp.findings.searchBar.searchPlaceholder', { defaultMessage: 'Search findings (eg. resource.section : "API Server")' } diff --git a/x-pack/plugins/cloud_security_posture/public/pages/findings/types.ts b/x-pack/plugins/cloud_security_posture/public/pages/findings/types.ts index 9fed484a8812..57646c6883ab 100644 --- a/x-pack/plugins/cloud_security_posture/public/pages/findings/types.ts +++ b/x-pack/plugins/cloud_security_posture/public/pages/findings/types.ts @@ -69,6 +69,7 @@ interface CspFindingResource { mode: string; path: string; type: string; + name: string; [other_keys: string]: unknown; }