[Cloud Security] enable leading wildcard search and improve error handling when it is disabled (#162555)

## Summary
The PR: 
- fixes https://github.com/elastic/kibana/issues/157954
- allows a user to recover from
https://github.com/elastic/kibana/issues/157954 by rendering the search
bar even when an error occurs (in line with how Misconfiguration
behaves)
- enables the support of `query:allowLeadingWildcard` setting of Kibana
- fixes unhandled exception on Resource Vulnerabilities page
- fixes the loading state on the search bar in Vulnerabilities which
wasn't working before
![2023-07-26 13 09
20](dcb251e5-d75d-4f8f-815d-958f48098461)

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Maxim Kholod 2023-08-03 09:41:24 +02:00 committed by GitHub
parent 5b283efe32
commit c237e11a60
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 448 additions and 353 deletions

View file

@ -6,17 +6,26 @@
*/
import { useEffect, useCallback, useMemo } from 'react';
import { buildEsQuery } from '@kbn/es-query';
import { buildEsQuery, EsQueryConfig } from '@kbn/es-query';
import type { EuiBasicTableProps, Pagination } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { type Query } from '@kbn/es-query';
import { useKibana } from '../use_kibana';
import type { FindingsBaseProps, FindingsBaseURLQuery } from '../../types';
import type {
FindingsBaseESQueryConfig,
FindingsBaseProps,
FindingsBaseURLQuery,
} from '../../types';
const getBaseQuery = ({ dataView, query, filters }: FindingsBaseURLQuery & FindingsBaseProps) => {
const getBaseQuery = ({
dataView,
query,
filters,
config,
}: FindingsBaseURLQuery & FindingsBaseProps & FindingsBaseESQueryConfig) => {
try {
return {
query: buildEsQuery(dataView, query, filters), // will throw for malformed query
query: buildEsQuery(dataView, query, filters, config), // will throw for malformed query
};
} catch (error) {
return {
@ -56,11 +65,13 @@ export const useBaseEsQuery = ({
data: {
query: { filterManager, queryString },
},
uiSettings,
} = useKibana().services;
const allowLeadingWildcards = uiSettings.get('query:allowLeadingWildcards');
const config: EsQueryConfig = useMemo(() => ({ allowLeadingWildcards }), [allowLeadingWildcards]);
const baseEsQuery = useMemo(
() => getBaseQuery({ dataView, filters, query }),
[dataView, filters, query]
() => getBaseQuery({ dataView, filters, query, config }),
[dataView, filters, query, config]
);
/**

View file

@ -6,7 +6,7 @@
*/
import type { Criteria } from '@elastic/eui';
import type { DataView } from '@kbn/data-views-plugin/common';
import type { BoolQuery, Filter, Query } from '@kbn/es-query';
import type { BoolQuery, Filter, Query, EsQueryConfig } from '@kbn/es-query';
import { CspFinding } from '../../common/schemas/csp_finding';
export type FindingsGroupByKind = 'default' | 'resource';
@ -20,6 +20,10 @@ export interface FindingsBaseProps {
dataView: DataView;
}
export interface FindingsBaseESQueryConfig {
config: EsQueryConfig;
}
export interface FindingsBaseEsQuery {
query?: {
bool: BoolQuery;

View file

@ -131,3 +131,29 @@ export interface Vector {
vector: string;
score: number | undefined;
}
export interface VulnerabilitiesQueryData {
page: VulnerabilityRecord[];
total: number;
}
export interface VulnerabilitiesByResourceQueryData {
page: Array<{
resource: {
id: string;
name: string;
};
cloud: {
region: string;
};
vulnerabilities_count: number;
severity_map: {
critical: number;
high: number;
medium: number;
low: number;
};
}>;
total: number;
total_vulnerabilities: number;
}

View file

@ -22,7 +22,7 @@ import { Routes, Route } from '@kbn/shared-ux-router';
import { LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY } from '../../common/constants';
import { useCloudPostureTable } from '../../common/hooks/use_cloud_posture_table';
import { useLatestVulnerabilities } from './hooks/use_latest_vulnerabilities';
import { VulnerabilityRecord } from './types';
import type { VulnerabilityRecord, VulnerabilitiesQueryData } from './types';
import { LATEST_VULNERABILITIES_INDEX_PATTERN } from '../../../common/constants';
import { ErrorCallout } from '../configurations/layout/error_callout';
import { FindingsSearchBar } from '../configurations/layout/findings_search_bar';
@ -100,12 +100,18 @@ export const Vulnerabilities = () => {
);
};
const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
const VulnerabilitiesDataGrid = ({
dataView,
data,
isFetching,
}: {
dataView: DataView;
data: VulnerabilitiesQueryData | undefined;
isFetching: boolean;
}) => {
const {
pageIndex,
query,
sort,
queryError,
pageSize,
onChangeItemsPerPage,
onChangePage,
@ -120,9 +126,20 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
});
const { euiTheme } = useEuiTheme();
const styles = useStyles();
const [showHighlight, setHighlight] = useState(false);
const invalidIndex = -1;
const selectedVulnerability = useMemo(() => {
return data?.page[urlQuery.vulnerabilityIndex];
}, [data?.page, urlQuery.vulnerabilityIndex]);
const onCloseFlyout = () => {
setUrlQuery({
vulnerabilityIndex: invalidIndex,
});
};
const onSortHandler = useCallback(
(newSort: any) => {
onSort(newSort);
@ -136,41 +153,12 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
[onSort, sort]
);
const multiFieldsSort = useMemo(() => {
return sort.map(({ id, direction }: { id: string; direction: string }) => {
if (id === vulnerabilitiesColumns.severity) {
return severitySortScript(direction);
}
if (id === vulnerabilitiesColumns.package) {
return getCaseInsensitiveSortScript(id, direction);
}
return {
[id]: direction,
};
});
}, [sort]);
const { data, isLoading, isFetching } = useLatestVulnerabilities({
query,
sort: multiFieldsSort,
enabled: !queryError,
const { isLastLimitedPage, limitedTotalItemCount } = useLimitProperties({
total: data?.total,
pageIndex,
pageSize,
});
const invalidIndex = -1;
const selectedVulnerability = useMemo(() => {
return data?.page[urlQuery.vulnerabilityIndex];
}, [data?.page, urlQuery.vulnerabilityIndex]);
const onCloseFlyout = () => {
setUrlQuery({
vulnerabilityIndex: invalidIndex,
});
};
const onOpenFlyout = useCallback(
(vulnerabilityRow: VulnerabilityRecord) => {
const vulnerabilityIndex = data?.page.findIndex(
@ -189,12 +177,6 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
[setUrlQuery, data?.page]
);
const { isLastLimitedPage, limitedTotalItemCount } = useLimitProperties({
total: data?.total,
pageIndex,
pageSize,
});
const columns = useMemo(() => {
if (!data?.page) {
return [];
@ -321,21 +303,132 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
[pageSize, setUrlQuery]
);
const showVulnerabilityFlyout = flyoutVulnerabilityIndex > invalidIndex;
if (data?.page.length === 0) {
return <EmptyState onResetFilters={onResetFilters} />;
}
return (
<>
<EuiProgress
size="xs"
color="accent"
style={{
opacity: isFetching ? 1 : 0,
}}
/>
<EuiDataGrid
className={cx({ [styles.gridStyle]: true }, { [styles.highlightStyle]: showHighlight })}
aria-label={VULNERABILITIES}
columns={columns}
columnVisibility={{
visibleColumns: columns.map(({ id }) => id),
setVisibleColumns: () => {},
}}
schemaDetectors={[severitySchemaConfig]}
rowCount={limitedTotalItemCount}
toolbarVisibility={{
showColumnSelector: false,
showDisplaySelector: false,
showKeyboardShortcuts: false,
showFullScreenSelector: false,
additionalControls: {
left: {
prepend: (
<>
<EuiButtonEmpty size="xs" color="text">
{i18n.translate('xpack.csp.vulnerabilities.totalVulnerabilities', {
defaultMessage:
'{total, plural, one {# Vulnerability} other {# Vulnerabilities}}',
values: { total: data?.total },
})}
</EuiButtonEmpty>
</>
),
},
right: (
<EuiFlexItem grow={false} className={styles.groupBySelector}>
<FindingsGroupBySelector
type="default"
pathnameHandler={vulnerabilitiesPathnameHandler}
/>
</EuiFlexItem>
),
},
}}
gridStyle={{
border: 'horizontal',
cellPadding: 'l',
stripes: false,
rowHover: 'none',
header: 'underline',
}}
renderCellValue={renderCellValue}
inMemory={{ level: 'enhancements' }}
sorting={{ columns: sort, onSort: onSortHandler }}
pagination={{
pageIndex,
pageSize,
pageSizeOptions: [10, 25, 100],
onChangeItemsPerPage,
onChangePage,
}}
/>
{isLastLimitedPage && <LimitedResultsBar />}
{showVulnerabilityFlyout && selectedVulnerability && (
<VulnerabilityFindingFlyout
flyoutIndex={selectedVulnerabilityIndex}
vulnerabilityRecord={selectedVulnerability}
totalVulnerabilitiesCount={limitedTotalItemCount}
onPaginate={onPaginateFlyout}
closeFlyout={onCloseFlyout}
isLoading={isFetching}
/>
)}
</>
);
};
const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
const { pageIndex, query, sort, queryError, pageSize, setUrlQuery } = useCloudPostureTable({
dataView,
defaultQuery: getDefaultQuery,
paginationLocalStorageKey: LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY,
});
const multiFieldsSort = useMemo(() => {
return sort.map(({ id, direction }: { id: string; direction: string }) => {
if (id === vulnerabilitiesColumns.severity) {
return severitySortScript(direction);
}
if (id === vulnerabilitiesColumns.package) {
return getCaseInsensitiveSortScript(id, direction);
}
return {
[id]: direction,
};
});
}, [sort]);
const { data, isLoading, isFetching } = useLatestVulnerabilities({
query,
sort: multiFieldsSort,
enabled: !queryError,
pageIndex,
pageSize,
});
const error = queryError || null;
if (error) {
return <ErrorCallout error={error as Error} />;
}
if (isLoading) {
if (isLoading && !error) {
return defaultLoadingRenderer();
}
if (!data?.page) {
if (!data?.page && !error) {
return defaultNoDataRenderer();
}
const showVulnerabilityFlyout = flyoutVulnerabilityIndex > invalidIndex;
return (
<>
<FindingsSearchBar
@ -343,90 +436,13 @@ const VulnerabilitiesContent = ({ dataView }: { dataView: DataView }) => {
setQuery={(newQuery) => {
setUrlQuery({ ...newQuery, pageIndex: 0 });
}}
loading={isLoading}
loading={isFetching}
placeholder={SEARCH_BAR_PLACEHOLDER}
/>
<EuiSpacer size="m" />
{!isLoading && data.page.length === 0 ? (
<EmptyState onResetFilters={onResetFilters} />
) : (
<>
<EuiProgress
size="xs"
color="accent"
style={{
opacity: isFetching ? 1 : 0,
}}
/>
<EuiDataGrid
className={cx({ [styles.gridStyle]: true }, { [styles.highlightStyle]: showHighlight })}
aria-label={VULNERABILITIES}
columns={columns}
columnVisibility={{
visibleColumns: columns.map(({ id }) => id),
setVisibleColumns: () => {},
}}
schemaDetectors={[severitySchemaConfig]}
rowCount={limitedTotalItemCount}
toolbarVisibility={{
showColumnSelector: false,
showDisplaySelector: false,
showKeyboardShortcuts: false,
showFullScreenSelector: false,
additionalControls: {
left: {
prepend: (
<>
<EuiButtonEmpty size="xs" color="text">
{i18n.translate('xpack.csp.vulnerabilities.totalVulnerabilities', {
defaultMessage:
'{total, plural, one {# Vulnerability} other {# Vulnerabilities}}',
values: { total: data?.total },
})}
</EuiButtonEmpty>
</>
),
},
right: (
<EuiFlexItem grow={false} className={styles.groupBySelector}>
<FindingsGroupBySelector
type="default"
pathnameHandler={vulnerabilitiesPathnameHandler}
/>
</EuiFlexItem>
),
},
}}
gridStyle={{
border: 'horizontal',
cellPadding: 'l',
stripes: false,
rowHover: 'none',
header: 'underline',
}}
renderCellValue={renderCellValue}
inMemory={{ level: 'enhancements' }}
sorting={{ columns: sort, onSort: onSortHandler }}
pagination={{
pageIndex,
pageSize,
pageSizeOptions: [10, 25, 100],
onChangeItemsPerPage,
onChangePage,
}}
/>
{isLastLimitedPage && <LimitedResultsBar />}
{showVulnerabilityFlyout && selectedVulnerability && (
<VulnerabilityFindingFlyout
flyoutIndex={selectedVulnerabilityIndex}
vulnerabilityRecord={selectedVulnerability}
totalVulnerabilitiesCount={limitedTotalItemCount}
onPaginate={onPaginateFlyout}
closeFlyout={onCloseFlyout}
isLoading={isFetching}
/>
)}
</>
{error && <ErrorCallout error={error as Error} />}
{!error && (
<VulnerabilitiesDataGrid dataView={dataView} data={data} isFetching={isFetching} />
)}
</>
);

View file

@ -19,10 +19,11 @@ import React, { useCallback, useMemo, useState, useEffect } from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import { Link, useParams, generatePath } from 'react-router-dom';
import type { BoolQuery } from '@kbn/es-query';
import { LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY } from '../../../../common/constants';
import { useCloudPostureTable } from '../../../../common/hooks/use_cloud_posture_table';
import { useLatestVulnerabilities } from '../../hooks/use_latest_vulnerabilities';
import { VulnerabilityRecord } from '../../types';
import type { VulnerabilityRecord, VulnerabilitiesQueryData } from '../../types';
import { ErrorCallout } from '../../../configurations/layout/error_callout';
import { FindingsSearchBar } from '../../../configurations/layout/findings_search_bar';
import { CVSScoreBadge, SeverityStatusBadge } from '../../../../components/vulnerability_badges';
@ -63,15 +64,18 @@ const getDefaultQuery = ({ query, filters }: any) => ({
pageIndex: 0,
});
export const ResourceVulnerabilities = ({ dataView }: { dataView: DataView }) => {
const params = useParams<{ resourceId: string }>();
const resourceId = decodeURIComponent(params.resourceId);
const ResourceVulnerabilitiesDataGrid = ({
dataView,
data,
isFetching,
}: {
dataView: DataView;
data: VulnerabilitiesQueryData;
isFetching: boolean;
}) => {
const {
pageIndex,
query,
sort,
queryError,
pageSize,
onChangeItemsPerPage,
onChangePage,
@ -102,35 +106,6 @@ export const ResourceVulnerabilities = ({ dataView }: { dataView: DataView }) =>
[onSort, sort]
);
const multiFieldsSort = useMemo(() => {
return sort.map(({ id, direction }: { id: string; direction: string }) => {
if (id === vulnerabilitiesColumns.severity) {
return severitySortScript(direction);
}
if (id === vulnerabilitiesColumns.package) {
return getCaseInsensitiveSortScript(id, direction);
}
return {
[id]: direction,
};
});
}, [sort]);
const { data, isLoading, isFetching } = useLatestVulnerabilities({
query: {
...query,
bool: {
...query!.bool,
filter: [...(query?.bool?.filter || []), { term: { 'resource.id': resourceId } }],
},
},
sort: multiFieldsSort,
enabled: !queryError,
pageIndex,
pageSize,
});
const invalidIndex = -1;
const selectedVulnerability = useMemo(() => {
@ -293,11 +268,130 @@ export const ResourceVulnerabilities = ({ dataView }: { dataView: DataView }) =>
[pageSize, setUrlQuery]
);
const showVulnerabilityFlyout = flyoutVulnerabilityIndex > invalidIndex;
if (data.page.length === 0) {
return <EmptyState onResetFilters={onResetFilters} />;
}
return (
<>
<EuiProgress
size="xs"
color="accent"
style={{
opacity: isFetching ? 1 : 0,
}}
/>
<EuiDataGrid
className={cx({ [styles.gridStyle]: true }, { [styles.highlightStyle]: showHighlight })}
aria-label={VULNERABILITIES}
columns={columns}
columnVisibility={{
visibleColumns: columns.map(({ id }) => id),
setVisibleColumns: () => {},
}}
height={undefined}
width={undefined}
schemaDetectors={[severitySchemaConfig]}
rowCount={limitedTotalItemCount}
rowHeightsOptions={{
defaultHeight: 40,
}}
toolbarVisibility={{
showColumnSelector: false,
showDisplaySelector: false,
showKeyboardShortcuts: false,
showFullScreenSelector: false,
additionalControls: {
left: {
prepend: (
<>
<EuiButtonEmpty size="xs" color="text">
{i18n.translate('xpack.csp.vulnerabilities.totalVulnerabilities', {
defaultMessage:
'{total, plural, one {# Vulnerability} other {# Vulnerabilities}}',
values: { total: data?.total },
})}
</EuiButtonEmpty>
</>
),
},
},
}}
gridStyle={{
border: 'horizontal',
cellPadding: 'l',
stripes: false,
rowHover: 'none',
header: 'underline',
}}
renderCellValue={renderCellValue}
inMemory={{ level: 'enhancements' }}
sorting={{ columns: sort, onSort: onSortHandler }}
pagination={{
pageIndex,
pageSize,
pageSizeOptions: [10, 25, 100],
onChangeItemsPerPage,
onChangePage,
}}
/>
{isLastLimitedPage && <LimitedResultsBar />}
{showVulnerabilityFlyout && selectedVulnerability && (
<VulnerabilityFindingFlyout
flyoutIndex={selectedVulnerabilityIndex}
vulnerabilityRecord={selectedVulnerability}
totalVulnerabilitiesCount={limitedTotalItemCount}
onPaginate={onPaginateFlyout}
closeFlyout={onCloseFlyout}
isLoading={isFetching}
/>
)}
</>
);
};
export const ResourceVulnerabilities = ({ dataView }: { dataView: DataView }) => {
const params = useParams<{ resourceId: string }>();
const resourceId = decodeURIComponent(params.resourceId);
const { pageIndex, query, sort, queryError, pageSize, setUrlQuery } = useCloudPostureTable({
dataView,
defaultQuery: getDefaultQuery,
paginationLocalStorageKey: LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY,
});
const multiFieldsSort = useMemo(() => {
return sort.map(({ id, direction }: { id: string; direction: string }) => {
if (id === vulnerabilitiesColumns.severity) {
return severitySortScript(direction);
}
if (id === vulnerabilitiesColumns.package) {
return getCaseInsensitiveSortScript(id, direction);
}
return {
[id]: direction,
};
});
}, [sort]);
const { data, isLoading, isFetching } = useLatestVulnerabilities({
query: {
...query,
bool: {
...(query?.bool as BoolQuery),
filter: [...(query?.bool?.filter || []), { term: { 'resource.id': resourceId } }],
},
},
sort: multiFieldsSort,
enabled: !queryError,
pageIndex,
pageSize,
});
const error = queryError || null;
if (error) {
return <ErrorCallout error={error as Error} />;
}
if (isLoading) {
return defaultLoadingRenderer();
}
@ -306,8 +400,6 @@ export const ResourceVulnerabilities = ({ dataView }: { dataView: DataView }) =>
return defaultNoDataRenderer();
}
const showVulnerabilityFlyout = flyoutVulnerabilityIndex > invalidIndex;
return (
<>
<FindingsSearchBar
@ -315,7 +407,7 @@ export const ResourceVulnerabilities = ({ dataView }: { dataView: DataView }) =>
setQuery={(newQuery) => {
setUrlQuery({ ...newQuery, pageIndex: 0 });
}}
loading={isLoading}
loading={isFetching}
placeholder={SEARCH_BAR_PLACEHOLDER}
/>
<Link to={generatePath(findingsNavigation.vulnerabilities_by_resource.path)}>
@ -363,83 +455,9 @@ export const ResourceVulnerabilities = ({ dataView }: { dataView: DataView }) =>
/>
<EuiSpacer />
<EuiSpacer size="m" />
{!isLoading && data?.page.length === 0 ? (
<EmptyState onResetFilters={onResetFilters} />
) : (
<>
<EuiProgress
size="xs"
color="accent"
style={{
opacity: isFetching ? 1 : 0,
}}
/>
<EuiDataGrid
className={cx({ [styles.gridStyle]: true }, { [styles.highlightStyle]: showHighlight })}
aria-label={VULNERABILITIES}
columns={columns}
columnVisibility={{
visibleColumns: columns.map(({ id }) => id),
setVisibleColumns: () => {},
}}
height={undefined}
width={undefined}
schemaDetectors={[severitySchemaConfig]}
rowCount={limitedTotalItemCount}
rowHeightsOptions={{
defaultHeight: 40,
}}
toolbarVisibility={{
showColumnSelector: false,
showDisplaySelector: false,
showKeyboardShortcuts: false,
showFullScreenSelector: false,
additionalControls: {
left: {
prepend: (
<>
<EuiButtonEmpty size="xs" color="text">
{i18n.translate('xpack.csp.vulnerabilities.totalVulnerabilities', {
defaultMessage:
'{total, plural, one {# Vulnerability} other {# Vulnerabilities}}',
values: { total: data?.total },
})}
</EuiButtonEmpty>
</>
),
},
},
}}
gridStyle={{
border: 'horizontal',
cellPadding: 'l',
stripes: false,
rowHover: 'none',
header: 'underline',
}}
renderCellValue={renderCellValue}
inMemory={{ level: 'enhancements' }}
sorting={{ columns: sort, onSort: onSortHandler }}
pagination={{
pageIndex,
pageSize,
pageSizeOptions: [10, 25, 100],
onChangeItemsPerPage,
onChangePage,
}}
/>
{isLastLimitedPage && <LimitedResultsBar />}
{showVulnerabilityFlyout && selectedVulnerability && (
<VulnerabilityFindingFlyout
flyoutIndex={selectedVulnerabilityIndex}
vulnerabilityRecord={selectedVulnerability}
totalVulnerabilitiesCount={limitedTotalItemCount}
onPaginate={onPaginateFlyout}
closeFlyout={onCloseFlyout}
isLoading={isFetching}
/>
)}
</>
{error && <ErrorCallout error={error as Error} />}
{!error && (
<ResourceVulnerabilitiesDataGrid dataView={dataView} data={data} isFetching={isFetching} />
)}
</>
);

View file

@ -41,6 +41,7 @@ import { EmptyState } from '../../../components/empty_state';
import { SeverityMap } from './severity_map';
import { VULNERABILITY_RESOURCE_COUNT } from './test_subjects';
import { getVulnerabilitiesGridCellActions } from '../utils/get_vulnerabilities_grid_cell_actions';
import type { VulnerabilitiesByResourceQueryData } from '../types';
const getDefaultQuery = ({ query, filters }: any): any => ({
query,
@ -49,12 +50,18 @@ const getDefaultQuery = ({ query, filters }: any): any => ({
pageIndex: 0,
});
export const VulnerabilitiesByResource = ({ dataView }: { dataView: DataView }) => {
const VulnerabilitiesByResourceDataGrid = ({
dataView,
data,
isFetching,
}: {
dataView: DataView;
data: VulnerabilitiesByResourceQueryData | undefined;
isFetching: boolean;
}) => {
const {
pageIndex,
query,
sort,
queryError,
pageSize,
onChangeItemsPerPage,
onChangePage,
@ -67,15 +74,7 @@ export const VulnerabilitiesByResource = ({ dataView }: { dataView: DataView })
defaultQuery: getDefaultQuery,
paginationLocalStorageKey: LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY,
});
const styles = useStyles();
const { data, isLoading, isFetching } = useLatestVulnerabilitiesByResource({
query,
sortOrder: sort[0]?.direction,
enabled: !queryError,
pageIndex,
pageSize,
});
const { isLastLimitedPage, limitedTotalItemCount } = useLimitProperties({
total: data?.total,
@ -151,16 +150,109 @@ export const VulnerabilitiesByResource = ({ dataView }: { dataView: DataView })
return Cell;
}, [data?.page, pageSize, isFetching]);
if (data?.page.length === 0) {
return <EmptyState onResetFilters={onResetFilters} />;
}
return (
<>
<EuiProgress
size="xs"
color="accent"
style={{
opacity: isFetching ? 1 : 0,
}}
/>
<EuiDataGrid
className={styles.gridStyle}
aria-label={VULNERABILITIES}
columns={columns}
columnVisibility={{
visibleColumns: columns.map(({ id }) => id),
setVisibleColumns: () => {},
}}
rowCount={limitedTotalItemCount}
toolbarVisibility={{
showColumnSelector: false,
showDisplaySelector: false,
showKeyboardShortcuts: false,
showSortSelector: false,
showFullScreenSelector: false,
additionalControls: {
left: {
prepend: (
<>
<EuiButtonEmpty size="xs" color="text">
{i18n.translate('xpack.csp.vulnerabilitiesByResource.totalResources', {
defaultMessage: '{total, plural, one {# Resource} other {# Resources}}',
values: { total: data?.total },
})}
</EuiButtonEmpty>
<EuiButtonEmpty size="xs" color="text">
{i18n.translate('xpack.csp.vulnerabilitiesByResource.totalVulnerabilities', {
defaultMessage:
'{total, plural, one {# Vulnerability} other {# Vulnerabilities}}',
values: { total: data?.total_vulnerabilities },
})}
</EuiButtonEmpty>
</>
),
},
right: (
<EuiFlexItem grow={false} className={styles.groupBySelector}>
<FindingsGroupBySelector
type="resource"
pathnameHandler={vulnerabilitiesPathnameHandler}
/>
</EuiFlexItem>
),
},
}}
gridStyle={{
border: 'horizontal',
cellPadding: 'l',
stripes: false,
rowHover: 'none',
header: 'underline',
}}
renderCellValue={renderCellValue}
inMemory={{ level: 'enhancements' }}
sorting={{ columns: sort, onSort }}
pagination={{
pageIndex,
pageSize,
pageSizeOptions: [10, 25, 100],
onChangeItemsPerPage,
onChangePage,
}}
/>
{isLastLimitedPage && <LimitedResultsBar />}
</>
);
};
export const VulnerabilitiesByResource = ({ dataView }: { dataView: DataView }) => {
const { pageIndex, query, sort, queryError, pageSize, setUrlQuery } = useCloudPostureTable({
dataView,
defaultQuery: getDefaultQuery,
paginationLocalStorageKey: LOCAL_STORAGE_PAGE_SIZE_FINDINGS_KEY,
});
const { data, isLoading, isFetching } = useLatestVulnerabilitiesByResource({
query,
sortOrder: sort[0]?.direction,
enabled: !queryError,
pageIndex,
pageSize,
});
const error = queryError || null;
if (error) {
return <ErrorCallout error={error as Error} />;
}
if (isLoading) {
if (isLoading && !error) {
return defaultLoadingRenderer();
}
if (!data?.page) {
if (!data?.page && !error) {
return defaultNoDataRenderer();
}
@ -171,89 +263,17 @@ export const VulnerabilitiesByResource = ({ dataView }: { dataView: DataView })
setQuery={(newQuery) => {
setUrlQuery({ ...newQuery, pageIndex: 0 });
}}
loading={isLoading}
loading={isFetching}
placeholder={SEARCH_BAR_PLACEHOLDER}
/>
<EuiSpacer size="m" />
{!isLoading && data.page.length === 0 ? (
<EmptyState onResetFilters={onResetFilters} />
) : (
<>
<EuiProgress
size="xs"
color="accent"
style={{
opacity: isFetching ? 1 : 0,
}}
/>
<EuiDataGrid
className={styles.gridStyle}
aria-label={VULNERABILITIES}
columns={columns}
columnVisibility={{
visibleColumns: columns.map(({ id }) => id),
setVisibleColumns: () => {},
}}
rowCount={limitedTotalItemCount}
toolbarVisibility={{
showColumnSelector: false,
showDisplaySelector: false,
showKeyboardShortcuts: false,
showSortSelector: false,
showFullScreenSelector: false,
additionalControls: {
left: {
prepend: (
<>
<EuiButtonEmpty size="xs" color="text">
{i18n.translate('xpack.csp.vulnerabilitiesByResource.totalResources', {
defaultMessage: '{total, plural, one {# Resource} other {# Resources}}',
values: { total: data?.total },
})}
</EuiButtonEmpty>
<EuiButtonEmpty size="xs" color="text">
{i18n.translate(
'xpack.csp.vulnerabilitiesByResource.totalVulnerabilities',
{
defaultMessage:
'{total, plural, one {# Vulnerability} other {# Vulnerabilities}}',
values: { total: data?.total_vulnerabilities },
}
)}
</EuiButtonEmpty>
</>
),
},
right: (
<EuiFlexItem grow={false} className={styles.groupBySelector}>
<FindingsGroupBySelector
type="resource"
pathnameHandler={vulnerabilitiesPathnameHandler}
/>
</EuiFlexItem>
),
},
}}
gridStyle={{
border: 'horizontal',
cellPadding: 'l',
stripes: false,
rowHover: 'none',
header: 'underline',
}}
renderCellValue={renderCellValue}
inMemory={{ level: 'enhancements' }}
sorting={{ columns: sort, onSort }}
pagination={{
pageIndex,
pageSize,
pageSizeOptions: [10, 25, 100],
onChangeItemsPerPage,
onChangePage,
}}
/>
{isLastLimitedPage && <LimitedResultsBar />}
</>
{error && <ErrorCallout error={error as Error} />}
{!error && (
<VulnerabilitiesByResourceDataGrid
dataView={dataView}
data={data}
isFetching={isFetching}
/>
)}
</>
);