Skip to content

Commit a0e752d

Browse files
authored
Merge branch 'main' into updateLabelDiscoverGridCheckBox
2 parents 4ad0bd4 + 687aad0 commit a0e752d

8 files changed

Lines changed: 220 additions & 84 deletions

File tree

nav-kibana-dev.docnav.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
{
7070
"label": "Contributors Newsletters",
7171
"items": [
72+
{ "id": "kibApril2022ContributorNewsletter" },
7273
{ "id": "kibMarch2022ContributorNewsletter" },
7374
{ "id": "kibFebruary2022ContributorNewsletter" },
7475
{ "id": "kibJanuary2022ContributorNewsletter" },

x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings/latest_findings_table.tsx

Lines changed: 7 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,20 @@
77
import React, { useCallback, useMemo, useState } from 'react';
88
import {
99
type Criteria,
10-
EuiToolTip,
11-
EuiTableFieldDataColumnType,
1210
EuiEmptyPrompt,
1311
EuiBasicTable,
14-
PropsOf,
1512
EuiBasicTableProps,
13+
EuiBasicTableColumn,
1614
} from '@elastic/eui';
17-
import moment from 'moment';
1815
import { SortDirection } from '@kbn/data-plugin/common';
1916
import { EuiTableActionsColumnType } from '@elastic/eui/src/components/basic_table/table_types';
2017
import { extractErrorMessage } from '../../../../common/utils/helpers';
2118
import * as TEST_SUBJECTS from '../test_subjects';
2219
import * as TEXT from '../translations';
2320
import type { CspFinding } from '../types';
24-
import { CspEvaluationBadge } from '../../../components/csp_evaluation_badge';
2521
import type { FindingsGroupByNoneQuery, CspFindingsResult } from './use_latest_findings';
2622
import { FindingsRuleFlyout } from '../findings_flyout/findings_flyout';
23+
import { getExpandColumn, getFindingsColumns } from '../layout/findings_layout';
2724

2825
interface BaseFindingsTableProps extends FindingsGroupByNoneQuery {
2926
setQuery(query: Partial<FindingsGroupByNoneQuery>): void;
@@ -42,62 +39,11 @@ const FindingsTableComponent = ({
4239
}: FindingsTableProps) => {
4340
const [selectedFinding, setSelectedFinding] = useState<CspFinding>();
4441

45-
const columns: Array<
46-
EuiTableFieldDataColumnType<CspFinding> | EuiTableActionsColumnType<CspFinding>
47-
> = useMemo(
48-
() => [
49-
{
50-
width: '40px',
51-
actions: [
52-
{
53-
name: 'Expand',
54-
description: 'Expand',
55-
type: 'icon',
56-
icon: 'expand',
57-
onClick: (item) => setSelectedFinding(item),
58-
},
59-
],
60-
},
61-
{
62-
field: 'resource_id',
63-
name: TEXT.RESOURCE_ID,
64-
truncateText: true,
65-
width: '15%',
66-
sortable: true,
67-
render: resourceFilenameRenderer,
68-
},
69-
{
70-
field: 'result.evaluation',
71-
name: TEXT.RESULT,
72-
width: '100px',
73-
sortable: true,
74-
render: resultEvaluationRenderer,
75-
},
76-
{
77-
field: 'rule.name',
78-
name: TEXT.RULE,
79-
sortable: true,
80-
},
81-
{
82-
field: 'cluster_id',
83-
name: TEXT.SYSTEM_ID,
84-
truncateText: true,
85-
sortable: true,
86-
},
87-
{
88-
field: 'rule.section',
89-
name: TEXT.CIS_SECTION,
90-
sortable: true,
91-
truncateText: true,
92-
},
93-
{
94-
field: '@timestamp',
95-
name: TEXT.LAST_CHECKED,
96-
truncateText: true,
97-
sortable: true,
98-
render: timestampRenderer,
99-
},
100-
],
42+
const columns: [
43+
EuiTableActionsColumnType<CspFinding>,
44+
...Array<EuiBasicTableColumn<CspFinding>>
45+
] = useMemo(
46+
() => [getExpandColumn<CspFinding>({ onClick: setSelectedFinding }), ...getFindingsColumns()],
10147
[]
10248
);
10349

@@ -188,20 +134,4 @@ const getEsSearchQueryFromEuiTableParams = ({
188134
sort: sort ? [{ [sort.field]: SortDirection[sort.direction] }] : undefined,
189135
});
190136

191-
const timestampRenderer = (timestamp: string) => (
192-
<EuiToolTip position="top" content={timestamp}>
193-
<span>{moment(timestamp).fromNow()}</span>
194-
</EuiToolTip>
195-
);
196-
197-
const resourceFilenameRenderer = (filename: string) => (
198-
<EuiToolTip position="top" content={filename}>
199-
<span>{filename}</span>
200-
</EuiToolTip>
201-
);
202-
203-
const resultEvaluationRenderer = (type: PropsOf<typeof CspEvaluationBadge>['type']) => (
204-
<CspEvaluationBadge type={type} />
205-
);
206-
207137
export const FindingsTable = React.memo(FindingsTableComponent);

x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/findings_by_resource_container.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { findingsNavigation } from '../../../common/navigation/constants';
2121
import { useCspBreadcrumbs } from '../../../common/navigation/use_csp_breadcrumbs';
2222
import { ResourceFindings } from './resource_findings/resource_findings_container';
2323

24-
export const getDefaultQuery = (): FindingsBaseURLQuery => ({
24+
const getDefaultQuery = (): FindingsBaseURLQuery => ({
2525
query: { language: 'kuery', query: '' },
2626
filters: [],
2727
});
@@ -31,7 +31,7 @@ export const FindingsByResourceContainer = ({ dataView }: { dataView: DataView }
3131
<Route
3232
exact
3333
path={findingsNavigation.findings_by_resource.path}
34-
render={() => <LatestFindingsByResourceContainer dataView={dataView} />}
34+
render={() => <LatestFindingsByResource dataView={dataView} />}
3535
/>
3636
<Route
3737
path={findingsNavigation.resource_findings.path}
@@ -40,7 +40,7 @@ export const FindingsByResourceContainer = ({ dataView }: { dataView: DataView }
4040
</Switch>
4141
);
4242

43-
const LatestFindingsByResourceContainer = ({ dataView }: { dataView: DataView }) => {
43+
const LatestFindingsByResource = ({ dataView }: { dataView: DataView }) => {
4444
useCspBreadcrumbs([findingsNavigation.findings_by_resource]);
4545
const { urlQuery, setUrlQuery } = useUrlQuery(getDefaultQuery);
4646
const findingsGroupByResource = useFindingsByResource(

x-pack/plugins/cloud_security_posture/public/pages/findings/latest_findings_by_resource/resource_findings/resource_findings_container.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,17 @@ import * as TEST_SUBJECTS from '../../test_subjects';
1515
import { PageWrapper, PageTitle, PageTitleText } from '../../layout/findings_layout';
1616
import { useCspBreadcrumbs } from '../../../../common/navigation/use_csp_breadcrumbs';
1717
import { findingsNavigation } from '../../../../common/navigation/constants';
18+
import { useResourceFindings } from './use_resource_findings';
19+
import { useUrlQuery } from '../../../../common/hooks/use_url_query';
20+
import type { FindingsBaseURLQuery } from '../../types';
21+
import { getBaseQuery } from '../../utils';
22+
import { ResourceFindingsTable } from './resource_findings_table';
23+
import { FindingsSearchBar } from '../../layout/findings_search_bar';
24+
25+
const getDefaultQuery = (): FindingsBaseURLQuery => ({
26+
query: { language: 'kuery', query: '' },
27+
filters: [],
28+
});
1829

1930
const BackToResourcesButton = () => {
2031
return (
@@ -33,9 +44,22 @@ export const ResourceFindings = ({ dataView }: { dataView: DataView }) => {
3344
useCspBreadcrumbs([findingsNavigation.findings_default]);
3445
const { euiTheme } = useEuiTheme();
3546
const params = useParams<{ resourceId: string }>();
47+
const { urlQuery, setUrlQuery } = useUrlQuery(getDefaultQuery);
48+
49+
const resourceFindings = useResourceFindings({
50+
...getBaseQuery({ dataView, filters: urlQuery.filters, query: urlQuery.query }),
51+
resourceId: params.resourceId,
52+
});
3653

3754
return (
3855
<div data-test-subj={TEST_SUBJECTS.FINDINGS_CONTAINER}>
56+
<FindingsSearchBar
57+
dataView={dataView}
58+
setQuery={setUrlQuery}
59+
query={urlQuery.query}
60+
filters={urlQuery.filters}
61+
loading={resourceFindings.isLoading}
62+
/>
3963
<PageWrapper>
4064
<PageTitle>
4165
<BackToResourcesButton />
@@ -52,6 +76,11 @@ export const ResourceFindings = ({ dataView }: { dataView: DataView }) => {
5276
/>
5377
</PageTitle>
5478
<EuiSpacer />
79+
<ResourceFindingsTable
80+
loading={resourceFindings.isLoading}
81+
data={resourceFindings.data}
82+
error={resourceFindings.error}
83+
/>
5584
</PageWrapper>
5685
</div>
5786
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
import React from 'react';
8+
import { EuiEmptyPrompt, EuiBasicTable } from '@elastic/eui';
9+
import { extractErrorMessage } from '../../../../../common/utils/helpers';
10+
import * as TEXT from '../../translations';
11+
import type { ResourceFindingsResult } from './use_resource_findings';
12+
import { getFindingsColumns } from '../../layout/findings_layout';
13+
14+
type FindingsGroupByResourceProps = ResourceFindingsResult;
15+
16+
const columns = getFindingsColumns();
17+
18+
const ResourceFindingsTableComponent = ({ error, data, loading }: FindingsGroupByResourceProps) => {
19+
if (!loading && !data?.page.length)
20+
return <EuiEmptyPrompt iconType="logoKibana" title={<h2>{TEXT.NO_FINDINGS}</h2>} />;
21+
22+
return (
23+
<EuiBasicTable
24+
loading={loading}
25+
error={error ? extractErrorMessage(error) : undefined}
26+
items={data?.page || []}
27+
columns={columns}
28+
/>
29+
);
30+
};
31+
32+
export const ResourceFindingsTable = React.memo(ResourceFindingsTableComponent);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
import { useQuery } from 'react-query';
8+
import { lastValueFrom } from 'rxjs';
9+
import { IEsSearchResponse } from '@kbn/data-plugin/common';
10+
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
11+
import { useKibana } from '../../../../common/hooks/use_kibana';
12+
import { showErrorToast } from '../../latest_findings/use_latest_findings';
13+
import type { CspFinding, FindingsBaseEsQuery, FindingsQueryResult } from '../../types';
14+
15+
interface UseResourceFindingsOptions extends FindingsBaseEsQuery {
16+
resourceId: string;
17+
}
18+
19+
export type ResourceFindingsResult = FindingsQueryResult<
20+
ReturnType<typeof useResourceFindings>['data'] | undefined,
21+
unknown
22+
>;
23+
24+
export const getResourceFindingsQuery = ({
25+
index,
26+
query,
27+
resourceId,
28+
}: UseResourceFindingsOptions): estypes.SearchRequest => ({
29+
index,
30+
body: {
31+
query: {
32+
...query,
33+
bool: {
34+
...query?.bool,
35+
filter: [...(query?.bool?.filter || []), { term: { 'resource_id.keyword': resourceId } }],
36+
},
37+
},
38+
},
39+
});
40+
41+
export const useResourceFindings = ({ index, query, resourceId }: UseResourceFindingsOptions) => {
42+
const {
43+
data,
44+
notifications: { toasts },
45+
} = useKibana().services;
46+
47+
return useQuery(
48+
['csp_resource_findings', { index, query, resourceId }],
49+
() =>
50+
lastValueFrom<IEsSearchResponse<CspFinding>>(
51+
data.search.search({
52+
params: getResourceFindingsQuery({ index, query, resourceId }),
53+
})
54+
),
55+
{
56+
select: ({ rawResponse: { hits } }) => ({
57+
page: hits.hits.map((hit) => hit._source!),
58+
total: hits.total as number,
59+
}),
60+
onError: (err) => showErrorToast(toasts, err),
61+
}
62+
);
63+
};

x-pack/plugins/cloud_security_posture/public/pages/findings/layout/findings_layout.tsx

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,20 @@
55
* 2.0.
66
*/
77
import React from 'react';
8-
import { EuiSpacer, EuiTitle, useEuiTheme } from '@elastic/eui';
8+
import {
9+
EuiBasicTableColumn,
10+
EuiSpacer,
11+
EuiTableActionsColumnType,
12+
EuiTitle,
13+
EuiToolTip,
14+
PropsOf,
15+
useEuiTheme,
16+
} from '@elastic/eui';
917
import { css } from '@emotion/react';
18+
import moment from 'moment';
19+
import { CspEvaluationBadge } from '../../../components/csp_evaluation_badge';
20+
import * as TEXT from '../translations';
21+
import { CspFinding } from '../types';
1022

1123
export const PageWrapper: React.FC = ({ children }) => {
1224
const { euiTheme } = useEuiTheme();
@@ -31,3 +43,72 @@ export const PageTitle: React.FC = ({ children }) => (
3143
);
3244

3345
export const PageTitleText = ({ title }: { title: React.ReactNode }) => <h2>{title}</h2>;
46+
47+
export const getExpandColumn = <T extends unknown>({
48+
onClick,
49+
}: {
50+
onClick(item: T): void;
51+
}): EuiTableActionsColumnType<T> => ({
52+
width: '40px',
53+
actions: [
54+
{
55+
name: 'Expand',
56+
description: 'Expand',
57+
type: 'icon',
58+
icon: 'expand',
59+
onClick,
60+
},
61+
],
62+
});
63+
64+
export const getFindingsColumns = (): Array<EuiBasicTableColumn<CspFinding>> => [
65+
{
66+
field: 'resource_id',
67+
name: TEXT.RESOURCE_ID,
68+
truncateText: true,
69+
width: '15%',
70+
sortable: true,
71+
render: (filename: string) => (
72+
<EuiToolTip position="top" content={filename}>
73+
<span>{filename}</span>
74+
</EuiToolTip>
75+
),
76+
},
77+
{
78+
field: 'result.evaluation',
79+
name: TEXT.RESULT,
80+
width: '100px',
81+
sortable: true,
82+
render: (type: PropsOf<typeof CspEvaluationBadge>['type']) => (
83+
<CspEvaluationBadge type={type} />
84+
),
85+
},
86+
{
87+
field: 'rule.name',
88+
name: TEXT.RULE,
89+
sortable: true,
90+
},
91+
{
92+
field: 'cluster_id',
93+
name: TEXT.CLUSTER_ID,
94+
truncateText: true,
95+
sortable: true,
96+
},
97+
{
98+
field: 'rule.section',
99+
name: TEXT.CIS_SECTION,
100+
sortable: true,
101+
truncateText: true,
102+
},
103+
{
104+
field: '@timestamp',
105+
name: TEXT.LAST_CHECKED,
106+
truncateText: true,
107+
sortable: true,
108+
render: (timestamp: number) => (
109+
<EuiToolTip position="top" content={timestamp}>
110+
<span>{moment(timestamp).fromNow()}</span>
111+
</EuiToolTip>
112+
),
113+
},
114+
];

0 commit comments

Comments
 (0)