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] [Findings] Adding grouping component #169884

Merged
merged 38 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8bbb01a
wip: grouping
opauloh Oct 13, 2023
c40e2fc
group selector fix
opauloh Oct 17, 2023
0ac7e9e
WIP: group selector on findings page
opauloh Oct 17, 2023
bb7f308
adding more group by fields
opauloh Oct 18, 2023
1bacb6a
Merge branch 'main' into grouping/foundation
opauloh Oct 18, 2023
c9f251e
add non url additional filters
opauloh Oct 23, 2023
9432b87
Merge branch 'main' into grouping/foundation
opauloh Oct 23, 2023
4fd1510
adding custom title
opauloh Oct 25, 2023
fd4e071
additional filters
opauloh Oct 25, 2023
40a7797
datatable group component
opauloh Oct 25, 2023
bd80f34
clean up code
opauloh Oct 25, 2023
befa489
[CI] Auto-commit changed files from 'node scripts/lint_ts_projects --…
kibanamachine Oct 25, 2023
2f761d5
add more information to disabled
opauloh Oct 25, 2023
ec0dbdf
code clean up
opauloh Oct 26, 2023
d55081c
hiding take action button
opauloh Oct 26, 2023
4d8e9bc
Merge branch 'main' into grouping/foundation
opauloh Nov 9, 2023
59cf7e3
addressing PR review suggestions
opauloh Nov 11, 2023
5abda98
splitting and optimizing code
opauloh Nov 13, 2023
dcde804
splitting code into hooks and components
opauloh Nov 13, 2023
97c19a3
addressing pr comments, removing anys
opauloh Nov 13, 2023
be967d2
findings grouping FTR tests
opauloh Nov 15, 2023
199f938
reverting change
opauloh Nov 15, 2023
f51a462
adding comments and time filtering
opauloh Nov 15, 2023
1af2377
revert use posture table
opauloh Nov 15, 2023
1eb9ef2
add use posture data table
opauloh Nov 15, 2023
a145184
update FTR tests
opauloh Nov 15, 2023
24188a3
Merge branch 'main' into grouping/foundation
opauloh Nov 15, 2023
975fd13
update dashboard navigation
opauloh Nov 15, 2023
1c0308c
fix ci errors
opauloh Nov 15, 2023
7bacb48
fixing ci and FTR tests
opauloh Nov 16, 2023
ca4713c
fix CI types error
opauloh Nov 16, 2023
95f8cf4
remove unused service from ftr
opauloh Nov 16, 2023
70e9539
Merge branch 'main' into grouping/foundation
opauloh Nov 21, 2023
2c35de7
Merge branch 'main' into grouping/foundation
opauloh Nov 21, 2023
3d866de
Merge branch 'main' into grouping/foundation
opauloh Nov 21, 2023
dca18ba
Merge branch 'main' into grouping/foundation
opauloh Nov 22, 2023
af4d6b5
Merge branch 'main' into grouping/foundation
opauloh Nov 27, 2023
6ff86ab
adding maxGroupingLevels tests
opauloh Nov 27, 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
Expand Up @@ -43,8 +43,17 @@ const GroupSelectorComponent = ({
[groupsSelected]
);

const panels: EuiContextMenuPanelDescriptor[] = useMemo(
() => [
const panels: EuiContextMenuPanelDescriptor[] = useMemo(() => {
const isOptionDisabled = (key?: string) => {
// Do not disable when maxGroupingLevels is 1 to allow toggling between groups
if (maxGroupingLevels === 1) {
opauloh marked this conversation as resolved.
Show resolved Hide resolved
return false;
}
// Disable all non selected options when the maxGroupingLevels is reached
return groupsSelected.length === maxGroupingLevels && (key ? !isGroupSelected(key) : true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we keep the behaviour of disabling currently selected option when the max level is 1? Right now to remove grouping, you can either switch to none or "unselect" the option. If we change the behavior to toggle when max level is 1, maybe it makes sense to disable this "unselect" behaviour

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great point, I'll adjust it in the code and bring it to discussion with the code owners

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats fine, can you please just add some unit tests? We never get to line 53 in the current tests

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the unit tests related to the changes introduced in this PR, thanks a lot for reviewing it @stephmilovic!

};

return [
{
id: 'firstPanel',
title: i18n.SELECT_FIELD(maxGroupingLevels),
Expand All @@ -57,7 +66,7 @@ const GroupSelectorComponent = ({
},
...options.map<EuiContextMenuPanelItemDescriptor>((o) => ({
'data-test-subj': `panel-${o.key}`,
disabled: groupsSelected.length === maxGroupingLevels && !isGroupSelected(o.key),
disabled: isOptionDisabled(o.key),
name: o.label,
onClick: () => onGroupChange(o.key),
icon: isGroupSelected(o.key) ? 'check' : 'empty',
Expand All @@ -66,7 +75,7 @@ const GroupSelectorComponent = ({
'data-test-subj': `panel-custom`,
name: i18n.CUSTOM_FIELD,
icon: 'empty',
disabled: groupsSelected.length === maxGroupingLevels,
disabled: isOptionDisabled(),
panel: 'customPanel',
hasPanel: true,
},
Expand All @@ -87,9 +96,8 @@ const GroupSelectorComponent = ({
/>
),
},
],
[fields, groupsSelected.length, isGroupSelected, maxGroupingLevels, onGroupChange, options]
);
];
}, [fields, groupsSelected.length, isGroupSelected, maxGroupingLevels, onGroupChange, options]);
const selectedOptions = useMemo(
() => options.filter((groupOption) => isGroupSelected(groupOption.key)),
[isGroupSelected, options]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface UseGetGroupSelectorArgs {
event: string | string[],
count?: number | undefined
) => void;
title?: string;
}

interface UseGetGroupSelectorStateless
Expand Down Expand Up @@ -84,6 +85,7 @@ export const useGetGroupSelector = ({
onGroupChange,
onOptionsChange,
tracker,
title,
}: UseGetGroupSelectorArgs) => {
const { activeGroups: selectedGroups, options } =
groupByIdSelector({ groups: groupingState }, groupingId) ?? defaultGroup;
Expand Down Expand Up @@ -120,10 +122,15 @@ export const useGetGroupSelector = ({
return;
}

const newSelectedGroups = isNoneGroup([groupSelection])
? [groupSelection]
: [...selectedGroups.filter((selectedGroup) => selectedGroup !== 'none'), groupSelection];
setSelectedGroups(newSelectedGroups);
// Simulate a toggle behavior when maxGroupingLevels is 1
opauloh marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think kbn-securitysolution-grouping lib would benefit from having some unit tests. As it didn't have any, I wouldn't block this PR on that, but I'd vote for adding some of the tests which are testing your changes to the lib

Copy link
Contributor

@stephmilovic stephmilovic Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think kbn-securitysolution-grouping lib would benefit from having some unit tests. As it didn't have any...

@maxcold Why do you think there are not unit tests? We do have tests that should have been added to :

  • packages/kbn-securitysolution-grouping/src/components/group_selector/index.test.tsx
  • packages/kbn-securitysolution-grouping/src/hooks/use_get_group_selector.test.tsx

Please add unit tests

Copy link
Contributor

@maxcold maxcold Nov 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@stephmilovic totally my bad, I have no idea where I looked tbh, but I remember checking the codebase in my code editor and not finding the unit test files. Some kind of a blackout from my side I guess :) for sure we need to cover new cases with unit tests

if (maxGroupingLevels === 1) {
setSelectedGroups([groupSelection]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks to be the only untested condition here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

test added 🙌

} else {
const newSelectedGroups = isNoneGroup([groupSelection])
? [groupSelection]
: [...selectedGroups.filter((selectedGroup) => selectedGroup !== 'none'), groupSelection];
setSelectedGroups(newSelectedGroups);
}

// built-in telemetry: UI-counter
tracker?.(
Expand All @@ -133,7 +140,7 @@ export const useGetGroupSelector = ({

onGroupChange?.({ tableId: groupingId, groupByField: groupSelection });
},
[groupingId, onGroupChange, selectedGroups, setSelectedGroups, tracker]
[groupingId, maxGroupingLevels, onGroupChange, selectedGroups, setSelectedGroups, tracker]
);

useEffect(() => {
Expand Down Expand Up @@ -184,6 +191,7 @@ export const useGetGroupSelector = ({
fields,
maxGroupingLevels,
options,
title,
}}
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ interface GroupingArgs<T> {
event: string | string[],
count?: number | undefined
) => void;
title?: string;
}

/**
Expand All @@ -85,6 +86,7 @@ interface GroupingArgs<T> {
* @param onGroupChange callback executed when selected group is changed, used for tracking
* @param onOptionsChange callback executed when grouping options are changed, used for consumer grouping selector
* @param tracker telemetry handler
* @param title of the grouping selector component
* @returns {@link Grouping} the grouping constructor { getGrouping, groupSelector, pagination, selectedGroups }
*/
export const useGrouping = <T,>({
Expand All @@ -96,6 +98,7 @@ export const useGrouping = <T,>({
onGroupChange,
onOptionsChange,
tracker,
title,
}: GroupingArgs<T>): Grouping<T> => {
const [groupingState, dispatch] = useReducer(groupsReducerWithStorage, initialState);
const { activeGroups: selectedGroups } = useMemo(
Expand Down Expand Up @@ -125,6 +128,7 @@ export const useGrouping = <T,>({
onGroupChange,
onOptionsChange,
tracker,
title,
});

const getGrouping = useCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/
import { Dispatch, SetStateAction, useCallback } from 'react';
import { type DataView } from '@kbn/data-views-plugin/common';
import { BoolQuery } from '@kbn/es-query';
import { BoolQuery, Filter } from '@kbn/es-query';
import { CriteriaWithPagination } from '@elastic/eui';
import { DataTableRecord } from '@kbn/discover-utils/types';
import { useUrlQuery } from '../use_url_query';
Expand All @@ -21,7 +21,7 @@ export interface CloudPostureTableResult {
sort: any;
// TODO: Remove any when all finding tables are converted to CloudSecurityDataTable
filters: any[];
query?: { bool: BoolQuery };
query: { bool: BoolQuery };
queryError?: Error;
pageIndex: number;
// TODO: remove any, urlQuery is an object with query fields but we also add custom fields to it, need to assert usages
Expand All @@ -48,12 +48,14 @@ export const useCloudPostureTable = ({
dataView,
paginationLocalStorageKey,
columnsLocalStorageKey,
additionalFilters,
}: {
// TODO: Remove any when all finding tables are converted to CloudSecurityDataTable
defaultQuery?: (params: any) => any;
dataView: DataView;
paginationLocalStorageKey: string;
columnsLocalStorageKey?: string;
additionalFilters?: Filter[];
}): CloudPostureTableResult => {
const getPersistedDefaultQuery = usePersistedQuery(defaultQuery);
const { urlQuery, setUrlQuery } = useUrlQuery(getPersistedDefaultQuery);
Expand Down Expand Up @@ -117,6 +119,7 @@ export const useCloudPostureTable = ({
dataView,
filters: urlQuery.filters,
query: urlQuery.query,
...(additionalFilters ? { additionalFilters } : {}),
});

const handleUpdateQuery = useCallback(
Expand All @@ -133,12 +136,14 @@ export const useCloudPostureTable = ({
})
.flat() || [];

const queryError = baseEsQuery instanceof Error ? baseEsQuery : undefined;

return {
setUrlQuery,
sort: urlQuery.sort,
filters: urlQuery.filters,
query: baseEsQuery.query,
queryError: baseEsQuery.error,
queryError,
pageIndex: urlQuery.pageIndex,
urlQuery,
setTableOptions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ const getBaseQuery = ({
query: buildEsQuery(dataView, query, filters, config), // will throw for malformed query
};
} catch (error) {
return {
query: undefined,
error: error instanceof Error ? error : new Error('Unknown Error'),
};
throw new Error(error);
}
};

Expand Down Expand Up @@ -59,6 +56,7 @@ export const useBaseEsQuery = ({
dataView,
filters,
query,
additionalFilters,
}: FindingsBaseURLQuery & FindingsBaseProps) => {
const {
notifications: { toasts },
Expand All @@ -70,8 +68,14 @@ export const useBaseEsQuery = ({
const allowLeadingWildcards = uiSettings.get('query:allowLeadingWildcards');
const config: EsQueryConfig = useMemo(() => ({ allowLeadingWildcards }), [allowLeadingWildcards]);
const baseEsQuery = useMemo(
() => getBaseQuery({ dataView, filters, query, config }),
[dataView, filters, query, config]
() =>
getBaseQuery({
dataView,
filters: filters.concat(additionalFilters ?? []).flat(),
query,
config,
}),
[dataView, filters, additionalFilters, query, config]
);

/**
Expand All @@ -83,7 +87,7 @@ export const useBaseEsQuery = ({
}, [filters, filterManager, queryString, query]);

const handleMalformedQueryError = () => {
const error = baseEsQuery.error;
const error = baseEsQuery instanceof Error ? baseEsQuery : undefined;
if (error) {
toasts.addError(error, {
title: i18n.translate('xpack.csp.findings.search.queryErrorToastMessage', {
Expand All @@ -94,7 +98,7 @@ export const useBaseEsQuery = ({
}
};

useEffect(handleMalformedQueryError, [baseEsQuery.error, toasts]);
useEffect(handleMalformedQueryError, [baseEsQuery, toasts]);

return baseEsQuery;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export type FindingsGroupByKind = 'default' | 'resource';
export interface FindingsBaseURLQuery {
query: Query;
filters: Filter[];
/**
* Filters that are part of the query
* but not persisted in the URL or in the Filter Manager
*/
additionalFilters?: Filter[];
opauloh marked this conversation as resolved.
Show resolved Hide resolved
}

export interface FindingsBaseProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ 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) => {
Expand All @@ -24,13 +23,15 @@ export const AdditionalControls = ({
columns,
onAddColumn,
onRemoveColumn,
groupSelector,
}: {
total: number;
title: string;
dataView: DataView;
columns: string[];
onAddColumn: (column: string) => void;
onRemoveColumn: (column: string) => void;
groupSelector?: JSX.Element;
}) => {
const styles = useStyles();

Expand All @@ -39,6 +40,18 @@ export const AdditionalControls = ({
const closeModal = () => setIsFieldSelectorModalVisible(false);
const showModal = () => setIsFieldSelectorModalVisible(true);

const GroupSelector = () => {
opauloh marked this conversation as resolved.
Show resolved Hide resolved
opauloh marked this conversation as resolved.
Show resolved Hide resolved
if (groupSelector) {
return (
<EuiFlexItem grow={false} className={styles.groupBySelector}>
{groupSelector}
</EuiFlexItem>
);
}

return null;
};

return (
<>
{isFieldSelectorModalVisible && (
Expand Down Expand Up @@ -66,9 +79,7 @@ export const AdditionalControls = ({
})}
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false} className={styles.groupBySelector}>
<FindingsGroupBySelector type="default" />
</EuiFlexItem>
<GroupSelector />
opauloh marked this conversation as resolved.
Show resolved Hide resolved
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,15 @@ interface CloudSecurityDataGridProps {
*/
loadMore: () => void;
'data-test-subj'?: string;
/**
* This is the component that will be rendered in the group selector.
* This component will receive the current group and a function to change the group.
*/
groupSelector?: JSX.Element;
/**
* Height override for the data grid.
*/
height?: number;
}

export const CloudSecurityDataTable = ({
Expand All @@ -86,6 +95,8 @@ export const CloudSecurityDataTable = ({
loadMore,
title,
customCellRenderer,
groupSelector,
height,
...rest
}: CloudSecurityDataGridProps) => {
const {
Expand Down Expand Up @@ -213,14 +224,15 @@ export const CloudSecurityDataTable = ({
columns={currentColumns}
onAddColumn={onAddColumn}
onRemoveColumn={onRemoveColumn}
groupSelector={groupSelector}
/>
);

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)`,
height: height ?? `calc(100vh - ${filters.length > 0 ? 443 : 403}px)`,
};

const rowHeightState =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ export const useStyles = () => {
border-bottom: none;
margin-bottom: ${euiTheme.size.s};
border-top: none;
& .euiButtonEmpty {
font-weight: ${euiTheme.font.weight.bold};
}
}
& .euiDataGrid--headerUnderline .euiDataGridHeaderCell {
border-bottom: ${euiTheme.border.width.thick} solid ${euiTheme.colors.fullShade};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ export const EmptyState = ({
&& > .euiEmptyPrompt__main {
gap: ${euiTheme.size.xl};
}
margin-top: ${euiTheme.size.xxxl}};
&& {
margin-top: ${euiTheme.size.xxxl}};
}
`}
data-test-subj={EMPTY_STATE_TEST_SUBJ}
icon={
Expand Down
Loading