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

[Cloud Security] Add Fields selector to the CloudSecurityDataTable #167844

Merged
merged 43 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e2bce7a
WIP: unified datatable
opauloh Sep 21, 2023
b62831f
WIP: cloud security data table
opauloh Sep 26, 2023
8bf5bee
Merge branch 'main' into unified-data-grid/foundation
opauloh Sep 26, 2023
f838a25
WIP: Cloud Security DataTable
opauloh Sep 27, 2023
50a7da9
data table component
opauloh Sep 29, 2023
d86aca3
layout consistency
opauloh Sep 29, 2023
372f994
fix data view function
opauloh Sep 29, 2023
b3d1d87
adding constants
opauloh Sep 29, 2023
dc1c90c
fix type and sorting
opauloh Sep 29, 2023
0f8dcdb
revert changes
opauloh Sep 29, 2023
5737847
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Sep 29, 2023
fb560d9
enable multi filtering
opauloh Sep 29, 2023
6cb56c9
Merge remote-tracking branch 'origin/unified-data-grid/foundation' in…
opauloh Sep 29, 2023
397f09a
adding todos
opauloh Oct 1, 2023
3abd3dd
updating translations
opauloh Oct 1, 2023
8b4bace
fixing lexical sort
opauloh Oct 1, 2023
3cdfdae
removing unused function
opauloh Oct 1, 2023
2905d02
fixing FTR tests
opauloh Oct 1, 2023
9ffeca0
changing FTR methods for the datatable
opauloh Oct 1, 2023
196d6a4
removing unused fields
opauloh Oct 1, 2023
c6ab64d
Merge branch 'main' into unified-data-grid/foundation
opauloh Oct 1, 2023
8820da8
add partial types
opauloh Oct 1, 2023
3d4a0ae
WIP: adding fields
opauloh Oct 1, 2023
90be39e
add fields selector component
opauloh Oct 3, 2023
2c44497
additional controls component
opauloh Oct 3, 2023
d51debe
adding tests
opauloh Oct 3, 2023
6a61c1b
cloud security datatable refactor
opauloh Oct 3, 2023
c991208
fixing CI error
opauloh Oct 3, 2023
e77c944
Merge branch 'main' into unified-data-grid/column-toggler
opauloh Oct 3, 2023
126272d
update row height
opauloh Oct 3, 2023
279d6b3
Update x-pack/plugins/cloud_security_posture/public/common/api/use_la…
opauloh Oct 4, 2023
7050e51
Update use_latest_findings_data_view.ts
opauloh Oct 4, 2023
ad8219f
change the deprecated api
opauloh Oct 4, 2023
e1dc390
Merge branch 'main' into unified-data-grid/foundation
opauloh Oct 4, 2023
6842aa5
Merge branch 'unified-data-grid/foundation' into unified-data-grid/co…
opauloh Oct 4, 2023
a25dfa4
design fixes cloud security data table
opauloh Oct 5, 2023
bac1df2
design alignments
opauloh Oct 5, 2023
5d521bb
Merge branch 'main' into unified-data-grid/column-toggler
opauloh Oct 5, 2023
8b56553
add text placeholder
opauloh Oct 5, 2023
94cf652
[CI] Auto-commit changed files from 'node scripts/precommit_hook.js -…
kibanamachine Oct 5, 2023
79b637c
add custom search to the table
opauloh Oct 5, 2023
3d442ae
Merge remote-tracking branch 'origin/unified-data-grid/column-toggler…
opauloh Oct 5, 2023
2f5b70d
add pluralization
opauloh Oct 5, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 React, { useState } from 'react';
import { i18n } from '@kbn/i18n';
import { EuiButtonEmpty, EuiFlexItem } from '@elastic/eui';
import { type DataView } from '@kbn/data-views-plugin/common';
import numeral from '@elastic/numeral';
import { FieldsSelectorModal } from './fields_selector';
import { FindingsGroupBySelector } from '../../pages/configurations/layout/findings_group_by_selector';
import { useStyles } from './use_styles';

const formatNumber = (value: number) => {
return value < 1000 ? value : numeral(value).format('0.0a');
};

export const AdditionalControls = ({
total,
title,
dataView,
columns,
onAddColumn,
onRemoveColumn,
}: {
total: number;
title: string;
dataView: DataView;
columns: string[];
onAddColumn: (column: string) => void;
onRemoveColumn: (column: string) => void;
}) => {
const styles = useStyles();

const [isFieldSelectorModalVisible, setIsFieldSelectorModalVisible] = useState(false);

const closeModal = () => setIsFieldSelectorModalVisible(false);
const showModal = () => setIsFieldSelectorModalVisible(true);

return (
<>
{isFieldSelectorModalVisible && (
<FieldsSelectorModal
columns={columns}
dataView={dataView}
closeModal={closeModal}
onAddColumn={onAddColumn}
onRemoveColumn={onRemoveColumn}
/>
)}
<EuiFlexItem grow={0}>
<span className="cspDataTableTotal">{`${formatNumber(total)} ${title}`}</span>
</EuiFlexItem>
<EuiFlexItem grow={0}>
<EuiButtonEmpty
className="cspDataTableFields"
iconType="tableOfContents"
onClick={showModal}
size="xs"
color="text"
>
{i18n.translate('xpack.csp.dataTable.fields', {
defaultMessage: 'Fields',
})}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false} className={styles.groupBySelector}>
<FindingsGroupBySelector type="default" />
</EuiFlexItem>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,23 @@ import {
SORT_DEFAULT_ORDER_SETTING,
} from '@kbn/discover-utils';
import { DataTableRecord } from '@kbn/discover-utils/types';
import {
EuiDataGridCellValueElementProps,
EuiDataGridStyle,
EuiFlexItem,
EuiProgress,
} from '@elastic/eui';
import { EuiDataGridCellValueElementProps, EuiDataGridStyle, EuiProgress } from '@elastic/eui';
import { AddFieldFilterHandler } from '@kbn/unified-field-list';
import { generateFilters } from '@kbn/data-plugin/public';
import { DocViewFilterFn } from '@kbn/unified-doc-viewer/types';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import numeral from '@elastic/numeral';
import { useKibana } from '../../common/hooks/use_kibana';
import { CloudPostureTableResult } from '../../common/hooks/use_cloud_posture_table';
import { FindingsGroupBySelector } from '../../pages/configurations/layout/findings_group_by_selector';
import { EmptyState } from '../empty_state';
import { MAX_FINDINGS_TO_LOAD } from '../../common/constants';
import { useStyles } from './use_styles';
import { AdditionalControls } from './additional_controls';

export interface CloudSecurityDefaultColumn {
id: string;
width?: number;
}

const formatNumber = (value: number) => {
return value < 1000 ? value : numeral(value).format('0.0a');
};

const gridStyle: EuiDataGridStyle = {
border: 'horizontal',
cellPadding: 'l',
Expand All @@ -50,6 +41,9 @@ const gridStyle: EuiDataGridStyle = {

const useNewFieldsApi = true;

// Hide Checkbox, enable open details Flyout
const controlColumnIds = ['openDetails'];

interface CloudSecurityDataGridProps {
dataView: DataView;
isLoading: boolean;
Expand Down Expand Up @@ -113,7 +107,8 @@ export const CloudSecurityDataTable = ({
`${columnsLocalStorageKey}:settings`,
{
columns: defaultColumns.reduce((prev, curr) => {
const newColumn = { [curr.id]: {} };
const columnDefaultSettings = curr.width ? { width: curr.width } : {};
const newColumn = { [curr.id]: columnDefaultSettings };
return { ...prev, ...newColumn };
}, {} as UnifiedDataTableSettings['columns']),
}
Expand Down Expand Up @@ -153,7 +148,12 @@ export const CloudSecurityDataTable = ({
dataViewFieldEditor,
};

const { columns: currentColumns, onSetColumns } = useColumns({
const {
columns: currentColumns,
onSetColumns,
onAddColumn,
onRemoveColumn,
} = useColumns({
capabilities,
defaultOrder: uiSettings.get(SORT_DEFAULT_ORDER_SETTING),
dataView,
Expand Down Expand Up @@ -205,25 +205,39 @@ export const CloudSecurityDataTable = ({
return <EmptyState onResetFilters={onResetFilters} />;
}

const externalAdditionalControls = (
<AdditionalControls
total={total}
dataView={dataView}
title={title}
columns={currentColumns}
onAddColumn={onAddColumn}
onRemoveColumn={onRemoveColumn}
/>
);

const dataTableStyle = {
// Change the height of the grid to fit the page
// If there are filters, leave space for the filter bar
// Todo: Replace this component with EuiAutoSizer
height: `calc(100vh - ${filters.length > 0 ? 443 : 403}px)`,
};

const rowHeightState =
uiSettings.get(ROW_HEIGHT_OPTION) === -1 ? 0 : uiSettings.get(ROW_HEIGHT_OPTION);

const loadingStyle = {
opacity: isLoading ? 1 : 0,
};

return (
<CellActionsProvider getTriggerCompatibleActions={uiActions.getTriggerCompatibleActions}>
<div
data-test-subj={rest['data-test-subj']}
className={styles.gridContainer}
style={{
// Change the height of the grid to fit the page
// If there are filters, leave space for the filter bar
// Todo: Replace this component with EuiAutoSizer
height: `calc(100vh - ${filters.length > 0 ? 454 : 414}px)`,
}}
style={dataTableStyle}
>
<EuiProgress
size="xs"
color="accent"
style={{
opacity: isLoading ? 1 : 0,
}}
/>
<EuiProgress size="xs" color="accent" style={loadingStyle} />
<UnifiedDataTable
className={styles.gridStyle}
ariaLabelledBy={title}
Expand All @@ -245,31 +259,18 @@ export const CloudSecurityDataTable = ({
services={services}
useNewFieldsApi
onUpdateRowsPerPage={onChangeItemsPerPage}
configRowHeight={uiSettings.get(ROW_HEIGHT_OPTION)}
rowHeightState={rowHeightState}
showMultiFields={uiSettings.get(SHOW_MULTIFIELDS)}
showTimeCol={false}
settings={settings}
onFetchMoreRecords={loadMore}
externalCustomRenderers={externalCustomRenderers}
rowHeightState={uiSettings.get(ROW_HEIGHT_OPTION)}
externalAdditionalControls={<AdditionalControls total={total} title={title} />}
externalAdditionalControls={externalAdditionalControls}
gridStyleOverride={gridStyle}
rowLineHeightOverride="24px"
controlColumnIds={controlColumnIds}
/>
</div>
</CellActionsProvider>
);
};

const AdditionalControls = ({ total, title }: { total: number; title: string }) => {
const styles = useStyles();
return (
<>
<EuiFlexItem>
<span className="cspDataTableTotal">{`${formatNumber(total)} ${title}`}</span>
</EuiFlexItem>
<EuiFlexItem grow={false} className={styles.groupBySelector}>
<FindingsGroupBySelector type="default" />
</EuiFlexItem>
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 React from 'react';
import { render, fireEvent } from '@testing-library/react';
import { FieldsSelectorTable, FieldsSelectorCommonProps } from './fields_selector';
import { TestProvider } from '../../test/test_provider';

const mockDataView = {
fields: {
getAll: () => [
{ id: 'field1', name: 'field1', customLabel: 'Label 1', visualizable: true },
{ id: 'field2', name: 'field2', customLabel: 'Label 2', visualizable: true },
],
},
} as any;

const renderFieldsTable = (props: Partial<FieldsSelectorCommonProps> = {}) => {
const defaultProps: FieldsSelectorCommonProps = {
dataView: mockDataView,
columns: [],
onAddColumn: jest.fn(),
onRemoveColumn: jest.fn(),
};

return render(
<TestProvider>
<FieldsSelectorTable title="Fields" {...defaultProps} {...props} />
</TestProvider>
);
};

describe('FieldsSelectorTable', () => {
it('renders the table with data correctly', () => {
const { getByText } = renderFieldsTable();

expect(getByText('Label 1')).toBeInTheDocument();
expect(getByText('Label 2')).toBeInTheDocument();
});

it('calls onAddColumn when a checkbox is checked', () => {
const onAddColumn = jest.fn();
const { getAllByRole } = renderFieldsTable({
onAddColumn,
});

const checkbox = getAllByRole('checkbox')[0];
fireEvent.click(checkbox);

expect(onAddColumn).toHaveBeenCalledWith('field1');
});

it('calls onRemoveColumn when a checkbox is unchecked', () => {
const onRemoveColumn = jest.fn();
const { getAllByRole } = renderFieldsTable({
columns: ['field1', 'field2'],
onRemoveColumn,
});

const checkbox = getAllByRole('checkbox')[1];
fireEvent.click(checkbox);

expect(onRemoveColumn).toHaveBeenCalledWith('field2');
});
});
Loading