[Security Solution] - remove styled-components and cleanup for event viewer and data table components (#206523)

## Summary

This PR originally aimed at replacing the usages `styled-components`
with `@emotion/react` in the
`security_solution/public/common/components/events_viewer` folder. I
quickly realized removing some of these would require a small refactor.
This lead to making a few more changes, as many properties were actually
unused so a cleanup was welcome.

Only 2 small UI changes are introduced in this PR:
- the inspect icon on the top right corner of the tables are now always
visible instead of only visible on hover. I'm aware that this is a
different behavior from the alerts table in the alerts page, but we also
have other tables (like the one on threat intelligence page) where the
icon is always shown. Waiting on @codearos for confirmation here
- the `Grid view` and `Additional filters` button are reversed due to
the simplification of the code

No other UI changes are introduced. No behavior logic has been changed
either.

The biggest code cleanup are:
- removal of a bunch of unused properties and logic
- deletion of the RightTopMenu component: it was used in both
`StatefulEventsViewerComponent` and `getPersistentControlsHook` but none
of the internal logic was overlapping. I don't know how we got there but
its current implementation was overly complex and completely
unnecessary...

#### Alerts page

![Screenshot 2025-01-13 at 4 33
36 PM](https://github.com/user-attachments/assets/c6c588c1-16f1-49f8-bcc0-246fb05f7e10)

#### Rule creation page

![Screenshot 2025-01-13 at 4 34
14 PM](https://github.com/user-attachments/assets/ea2332c3-425a-4960-8bd6-f2d7395cdf34)

#### Host/User/Network events tab

![Screenshot 2025-01-13 at 4 34
27 PM](https://github.com/user-attachments/assets/4194e406-6bff-4a46-bc99-aadd1aea88d7)

#### Host session view tab

![Screenshot 2025-01-13 at 4 34
42 PM](https://github.com/user-attachments/assets/045b3bb2-2681-4089-a303-a77f797f9b90)

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Philippe Oberti 2025-01-15 16:18:19 +01:00 committed by GitHub
parent d9b9425372
commit 708789102f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 155 additions and 444 deletions

View file

@ -196,8 +196,6 @@ module.exports = {
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]empty_value[\/\\]index.tsx/,
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]endpoint[\/\\]agents[\/\\]agent_status[\/\\]agent_status.tsx/,
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]events_viewer[\/\\]index.tsx/,
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]events_viewer[\/\\]right_top_menu.tsx/,
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]events_viewer[\/\\]styles.tsx/,
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]events_viewer[\/\\]summary_view_select[\/\\]index.tsx/,
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]field_selection[\/\\]index.tsx/,
/x-pack[\/\\]solutions[\/\\]security[\/\\]plugins[\/\\]security_solution[\/\\]public[\/\\]common[\/\\]components[\/\\]filters_global[\/\\]filters_global.tsx/,

View file

@ -6,20 +6,19 @@
*/
import type {
EuiDataGridRefProps,
EuiDataGridColumn,
EuiDataGridCellValueElementProps,
EuiDataGridStyle,
EuiDataGridToolBarVisibilityOptions,
EuiDataGridColumn,
EuiDataGridControlColumn,
EuiDataGridPaginationProps,
EuiDataGridRowHeightsOptions,
EuiDataGridProps,
EuiDataGridRefProps,
EuiDataGridStyle,
EuiDataGridToolBarVisibilityOptions,
} from '@elastic/eui';
import { EuiDataGrid, EuiProgress } from '@elastic/eui';
import { getOr } from 'lodash/fp';
import memoizeOne from 'memoize-one';
import React, { useCallback, useEffect, useMemo, useContext, useRef } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useRef } from 'react';
import { useDispatch } from 'react-redux';
import styled, { ThemeContext } from 'styled-components';
@ -31,8 +30,8 @@ import type {
import { i18n } from '@kbn/i18n';
import {
BrowserFields,
DeprecatedCellValueElementProps,
ColumnHeaderOptions,
DeprecatedCellValueElementProps,
DeprecatedRowRenderer,
TimelineItem,
} from '@kbn/timelines-plugin/common';
@ -88,12 +87,9 @@ interface BaseDataTableProps {
loadPage: (newActivePage: number) => void;
renderCellValue: (props: DeprecatedCellValueElementProps) => React.ReactNode;
rowRenderers: DeprecatedRowRenderer[];
hasCrudPermissions?: boolean;
unitCountText: string;
pagination: EuiDataGridPaginationProps & { pageSize: number };
totalItems: number;
rowHeightsOptions?: EuiDataGridRowHeightsOptions;
isEventRenderedView?: boolean;
getFieldBrowser: GetFieldBrowser;
getFieldSpec: (fieldName: string) => FieldSpec | undefined;
cellActionsTriggerId?: string;
@ -103,12 +99,11 @@ export type DataTableProps = BaseDataTableProps & Omit<EuiDataGridProps, NonCust
const ES_LIMIT_COUNT = 9999;
const gridStyle = (isEventRenderedView: boolean | undefined = false): EuiDataGridStyle => ({
const gridStyle: EuiDataGridStyle = {
border: 'none',
fontSize: 's',
header: 'underline',
stripes: isEventRenderedView === true,
});
};
const EuiDataGridContainer = styled.div<{ hideLastPage: boolean }>`
ul.euiPagination__list {
@ -116,19 +111,23 @@ const EuiDataGridContainer = styled.div<{ hideLastPage: boolean }>`
${({ hideLastPage }) => `${hideLastPage ? 'display:none' : ''}`};
}
}
div .euiDataGridRowCell {
display: flex;
align-items: center;
}
div .euiDataGridRowCell > [data-focus-lock-disabled] {
display: flex;
align-items: center;
flex-grow: 1;
width: 100%;
}
div .euiDataGridRowCell__content {
flex-grow: 1;
}
div .siemEventsTable__trSupplement--summary {
display: block;
}
@ -136,8 +135,7 @@ const EuiDataGridContainer = styled.div<{ hideLastPage: boolean }>`
const memoizedGetColumnHeaders: (
headers: ColumnHeaderOptions[],
browserFields: BrowserFields,
isEventRenderedView: boolean
browserFields: BrowserFields
) => ColumnHeaderOptions[] = memoizeOne(getColumnHeaders);
// eslint-disable-next-line react/display-name
@ -148,7 +146,6 @@ export const DataTableComponent = React.memo<DataTableProps>(
bulkActions = true,
data,
fieldBrowserOptions,
hasCrudPermissions,
id,
leadingControlColumns,
loadPage,
@ -157,8 +154,6 @@ export const DataTableComponent = React.memo<DataTableProps>(
pagination,
unitCountText,
totalItems,
rowHeightsOptions,
isEventRenderedView = false,
getFieldBrowser,
getFieldSpec,
cellActionsTriggerId,
@ -178,7 +173,7 @@ export const DataTableComponent = React.memo<DataTableProps>(
dataViewId,
} = dataTable;
const columnHeaders = memoizedGetColumnHeaders(columns, browserFields, isEventRenderedView);
const columnHeaders = memoizedGetColumnHeaders(columns, browserFields);
const dataGridRef = useRef<EuiDataGridRefProps>(null);
@ -189,10 +184,6 @@ export const DataTableComponent = React.memo<DataTableProps>(
const theme: EuiTheme = useContext(ThemeContext);
const showBulkActions = useMemo(() => {
if (!hasCrudPermissions) {
return false;
}
if (selectedCount === 0 || !showCheckboxes) {
return false;
}
@ -200,7 +191,7 @@ export const DataTableComponent = React.memo<DataTableProps>(
return bulkActions;
}
return (bulkActions?.customBulkActions?.length || bulkActions?.alertStatusActions) ?? true;
}, [hasCrudPermissions, selectedCount, showCheckboxes, bulkActions]);
}, [selectedCount, showCheckboxes, bulkActions]);
const onResetColumns = useCallback(() => {
dispatch(dataTableActions.updateColumns({ id, columns: defaultColumns }));
@ -237,22 +228,18 @@ export const DataTableComponent = React.memo<DataTableProps>(
{isLoading && <EuiProgress size="xs" position="absolute" color="accent" />}
<UnitCount data-test-subj="server-side-event-count">{unitCountText}</UnitCount>
{additionalControls ?? null}
{!isEventRenderedView ? (
getFieldBrowser({
browserFields,
options: fieldBrowserOptions,
columnIds: columnHeaders.map(({ id: columnId }) => columnId),
onResetColumns,
onToggleColumn,
})
) : (
<></>
)}
{getFieldBrowser({
browserFields,
options: fieldBrowserOptions,
columnIds: columnHeaders.map(({ id: columnId }) => columnId),
onResetColumns,
onToggleColumn,
})}
</>
),
},
},
...(showBulkActions || isEventRenderedView
...(showBulkActions
? {
showColumnSelector: false,
showSortSelector: false,
@ -269,7 +256,6 @@ export const DataTableComponent = React.memo<DataTableProps>(
isLoading,
unitCountText,
additionalControls,
isEventRenderedView,
getFieldBrowser,
browserFields,
fieldBrowserOptions,
@ -468,9 +454,9 @@ export const DataTableComponent = React.memo<DataTableProps>(
id={'body-data-grid'}
data-test-subj="body-data-grid"
aria-label={DATA_TABLE_ARIA_LABEL}
columns={isEventRenderedView ? columnHeaders : columnsWithCellActions}
columns={columnsWithCellActions}
columnVisibility={{ visibleColumns, setVisibleColumns: onSetVisibleColumns }}
gridStyle={gridStyle(isEventRenderedView)}
gridStyle={gridStyle}
leadingControlColumns={leadingControlColumns}
toolbarVisibility={toolbarVisibility}
rowCount={totalItems}
@ -479,7 +465,7 @@ export const DataTableComponent = React.memo<DataTableProps>(
onColumnResize={onColumnResize}
pagination={pagination}
ref={dataGridRef}
rowHeightsOptions={rowHeightsOptions}
rowHeightsOptions={undefined}
/>
</EuiDataGridContainer>
</>

View file

@ -7,11 +7,11 @@
import { render } from '@testing-library/react';
import React from 'react';
import { TableId, dataTableActions } from '@kbn/securitysolution-data-table';
import { dataTableActions, TableId } from '@kbn/securitysolution-data-table';
import { HostsType } from '../../../explore/hosts/store/model';
import { TestProviders } from '../../mock';
import type { EventsQueryTabBodyComponentProps } from './events_query_tab_body';
import { EventsQueryTabBody, ALERTS_EVENTS_HISTOGRAM_ID } from './events_query_tab_body';
import { ALERTS_EVENTS_HISTOGRAM_ID, EventsQueryTabBody } from './events_query_tab_body';
import { useGlobalFullScreen } from '../../containers/use_full_screen';
import { licenseService } from '../../hooks/use_license';
import { mockHistory } from '../../mock/router';
@ -59,9 +59,13 @@ jest.mock('react-router-dom', () => ({
useLocation: jest.fn().mockReturnValue({ pathname: '/test' }),
}));
const FakeStatefulEventsViewer = ({ additionalFilters }: { additionalFilters: JSX.Element }) => (
const FakeStatefulEventsViewer = ({
topRightMenuOptions,
}: {
topRightMenuOptions: JSX.Element;
}) => (
<div>
{additionalFilters}
{topRightMenuOptions}
{'MockedStatefulEventsViewer'}
</div>
);

View file

@ -10,8 +10,8 @@ import { useDispatch } from 'react-redux';
import { EuiCheckbox } from '@elastic/eui';
import type { Filter } from '@kbn/es-query';
import { dataTableActions } from '@kbn/securitysolution-data-table';
import type { TableId } from '@kbn/securitysolution-data-table';
import { dataTableActions } from '@kbn/securitysolution-data-table';
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import type { CustomBulkAction } from '../../../../common/types';
import { RowRendererValues } from '../../../../common/api/timeline';
@ -177,7 +177,7 @@ const EventsQueryTabBodyComponent: React.FC<EventsQueryTabBodyComponentProps> =
/>
)}
<StatefulEventsViewer
additionalFilters={toggleExternalAlertsCheckbox}
topRightMenuOptions={toggleExternalAlertsCheckbox}
cellActionsTriggerId={SecurityCellActionsTrigger.DEFAULT}
start={startDate}
end={endDate}

View file

@ -5,13 +5,9 @@
* 2.0.
*/
import type { ViewSelection } from '@kbn/securitysolution-data-table';
import { TableId } from '@kbn/securitysolution-data-table';
import type { CombineQueries } from '../../lib/kuery';
import { buildTimeRangeFilter, combineQueries } from '../../lib/kuery';
import { EVENTS_TABLE_CLASS_NAME } from './styles';
export const getCombinedFilterQuery = ({
from,
to,
@ -25,41 +21,3 @@ export const getCombinedFilterQuery = ({
return combinedQueries ? combinedQueries.filterQuery : undefined;
};
export const resolverIsShowing = (graphEventId: string | undefined): boolean =>
graphEventId != null && graphEventId !== '';
export const EVENTS_COUNT_BUTTON_CLASS_NAME = 'local-events-count-button';
/** Returns `true` when the element, or one of it's children has focus */
export const elementOrChildrenHasFocus = (element: HTMLElement | null | undefined): boolean =>
element === document.activeElement || element?.querySelector(':focus-within') != null;
/** Returns true if the events table has focus */
export const tableHasFocus = (containerElement: HTMLElement | null): boolean =>
elementOrChildrenHasFocus(
containerElement?.querySelector<HTMLDivElement>(`.${EVENTS_TABLE_CLASS_NAME}`)
);
export const isSelectableView = (tableId: string): boolean =>
tableId === TableId.alertsOnAlertsPage || tableId === TableId.alertsOnRuleDetailsPage;
export const isViewSelection = (value: unknown): value is ViewSelection =>
value === 'gridView' || value === 'eventRenderedView';
/** always returns a valid default `ViewSelection` */
export const getDefaultViewSelection = ({
tableId,
value,
}: {
tableId: string;
value: unknown;
}): ViewSelection => {
const defaultViewSelection = 'gridView';
if (!isSelectableView(tableId)) {
return defaultViewSelection;
} else {
return isViewSelection(value) ? value : defaultViewSelection;
}
};

View file

@ -12,7 +12,7 @@ import { render } from '@testing-library/react';
import { TestProviders } from '../../mock';
import { mockEventViewerResponse } from './mock';
import { StatefulEventsViewer, type EventsViewerProps } from '.';
import { type EventsViewerProps, StatefulEventsViewer } from '.';
import { eventsDefaultModel } from './default_model';
import { EntityType } from '@kbn/timelines-plugin/common';
import { SourcererScopeName } from '../../../sourcerer/store/model';
@ -54,18 +54,17 @@ const to = '2019-08-26T22:10:56.791Z';
const ACTION_BUTTON_COUNT = 4;
const testProps: EventsViewerProps = {
bulkActions: false,
defaultModel: eventsDefaultModel,
end: to,
entityType: EntityType.EVENTS,
indexNames: [],
tableId: TableId.test,
leadingControlColumns: getDefaultControlColumn(ACTION_BUTTON_COUNT),
renderCellValue: DefaultCellRenderer,
rowRenderers: defaultRowRenderers,
sourcererScope: SourcererScopeName.default,
start: from,
bulkActions: false,
hasCrudPermissions: true,
tableId: TableId.test,
};
describe('StatefulEventsViewer', () => {
beforeAll(() => {
@ -93,7 +92,7 @@ describe('StatefulEventsViewer', () => {
</TestProviders>
);
expect(wrapper.find(`[data-test-subj="hoverVisibilityContainer"]`).exists()).toBeTruthy();
expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBeTruthy();
});
test('it closes field editor when unmounted', () => {
@ -119,7 +118,7 @@ describe('StatefulEventsViewer', () => {
<TestProviders>
<StatefulEventsViewer
{...testProps}
additionalRightMenuOptions={[<p data-test-subj="right-option" />]}
topRightMenuOptions={[<p data-test-subj="right-option" />]}
/>
</TestProviders>
);

View file

@ -5,20 +5,16 @@
* 2.0.
*/
import { css } from '@emotion/react';
import type { SubsetDataTableModel, TableId } from '@kbn/securitysolution-data-table';
import {
dataTableActions,
DataTableComponent,
defaultHeaders,
getEventIdToDataMapping,
} from '@kbn/securitysolution-data-table';
import type {
SubsetDataTableModel,
TableId,
ViewSelection,
} from '@kbn/securitysolution-data-table';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import { AlertConsumers } from '@kbn/rule-data-utils';
import React, { useRef, useCallback, useMemo, useEffect, useState, useContext } from 'react';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import type { ConnectedProps } from 'react-redux';
import { connect, useDispatch, useSelector } from 'react-redux';
import { ThemeContext } from 'styled-components';
@ -33,9 +29,9 @@ import type {
import { isEmpty } from 'lodash';
import { getEsQueryConfig } from '@kbn/data-plugin/common';
import type { EuiTheme } from '@kbn/kibana-react-plugin/common';
import type { EuiDataGridRowHeightsOptions } from '@elastic/eui';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import type { RunTimeMappings } from '@kbn/timelines-plugin/common/search_strategy';
import { ALERTS_TABLE_VIEW_SELECTION_KEY } from '../../../../common/constants';
import { InspectButton } from '../inspect';
import type {
ControlColumnProps,
OnRowSelected,
@ -47,8 +43,6 @@ import type { RowRenderer, SortColumnTimeline as Sort } from '../../../../common
import { InputsModelId } from '../../store/inputs/constants';
import type { State } from '../../store';
import { inputsActions } from '../../store/actions';
import { InspectButtonContainer } from '../inspect';
import { useGlobalFullScreen } from '../../containers/use_full_screen';
import { eventsViewerSelector } from './selectors';
import type { SourcererScopeName } from '../../../sourcerer/store/model';
import { useSourcererDataView } from '../../../sourcerer/containers';
@ -58,55 +52,39 @@ import { GraphOverlay } from '../../../timelines/components/graph_overlay';
import type { FieldEditorActions } from '../../../timelines/components/fields_browser';
import { useFieldBrowserOptions } from '../../../timelines/components/fields_browser';
import {
useSessionViewNavigation,
useSessionView,
useSessionViewNavigation,
} from '../../../timelines/components/timeline/tabs/session/use_session_view';
import {
EventsContainerLoading,
FullScreenContainer,
FullWidthFlexGroupTable,
ScrollableFlexItem,
StyledEuiPanel,
} from './styles';
import { getDefaultViewSelection, getCombinedFilterQuery } from './helpers';
import { getCombinedFilterQuery } from './helpers';
import { useTimelineEvents } from './use_timelines_events';
import { TableContext, EmptyTable, TableLoading } from './shared';
import type { AlertWorkflowStatus } from '../../types';
import { EmptyTable, TableContext, TableLoading } from './shared';
import { useQueryInspector } from '../page/manage_query';
import type { SetQuery } from '../../containers/use_global_time/types';
import { checkBoxControlColumn, transformControlColumns } from '../control_columns';
import { RightTopMenu } from './right_top_menu';
import { useAlertBulkActions } from './use_alert_bulk_actions';
import type { BulkActionsProp } from '../toolbar/bulk_actions/types';
import { StatefulEventContext } from './stateful_event_context';
import { defaultUnit } from '../toolbar/unit';
import { useGetFieldSpec } from '../../hooks/use_get_field_spec';
const storage = new Storage(localStorage);
const SECURITY_ALERTS_CONSUMERS = [AlertConsumers.SIEM];
export interface EventsViewerProps {
bulkActions: boolean | BulkActionsProp;
cellActionsTriggerId?: string;
defaultModel: SubsetDataTableModel;
end: string;
entityType?: EntityType;
tableId: TableId;
indexNames?: string[];
leadingControlColumns: ControlColumnProps[];
sourcererScope: SourcererScopeName;
start: string;
showTotalCount?: boolean; // eslint-disable-line react/no-unused-prop-types
pageFilters?: Filter[];
currentFilter?: AlertWorkflowStatus;
onRuleChange?: () => void;
renderCellValue: React.FC<CellValueElementProps>;
rowRenderers: RowRenderer[];
additionalFilters?: React.ReactNode;
hasCrudPermissions?: boolean;
sourcererScope: SourcererScopeName;
start: string;
tableId: TableId;
topRightMenuOptions?: React.ReactNode;
unit?: (n: number) => string;
indexNames?: string[];
bulkActions: boolean | BulkActionsProp;
additionalRightMenuOptions?: React.ReactNode[];
cellActionsTriggerId?: string;
}
/**
@ -115,19 +93,14 @@ export interface EventsViewerProps {
* NOTE: As of writting, it is not used in the Case_View component
*/
const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux> = ({
additionalFilters,
additionalRightMenuOptions,
bulkActions,
cellActionsTriggerId,
clearSelected,
currentFilter,
defaultModel,
end,
entityType = 'events',
hasCrudPermissions = true,
indexNames,
leadingControlColumns,
onRuleChange,
pageFilters,
renderCellValue,
rowRenderers,
@ -135,6 +108,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
sourcererScope,
start,
tableId,
topRightMenuOptions,
unit = defaultUnit,
}) => {
const dispatch = useDispatch();
@ -162,6 +136,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
title,
} = defaultModel,
} = useSelector((state: State) => eventsViewerSelector(state, tableId));
const inspectModalTitle = useMemo(() => <span data-test-subj="title">{title}</span>, [title]);
const {
uiSettings,
@ -169,13 +144,6 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
triggersActionsUi: { getFieldBrowser },
} = useKibana().services;
const [tableView, setTableView] = useState<ViewSelection>(
getDefaultViewSelection({
tableId,
value: storage.get(ALERTS_TABLE_VIEW_SELECTION_KEY),
})
);
const {
browserFields,
dataViewId,
@ -187,8 +155,6 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
const getFieldSpec = useGetFieldSpec(sourcererScope);
const { globalFullScreen } = useGlobalFullScreen();
const editorActionsRef = useRef<FieldEditorActions>(null);
useEffect(() => {
dispatch(
@ -215,14 +181,13 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
const globalFilters = useMemo(() => [...filters, ...(pageFilters ?? [])], [filters, pageFilters]);
// TODO remove this when session view is fully migrated to the flyout and the advanced settings is removed
const { Navigation } = useSessionViewNavigation({
scopeId: tableId,
});
const { SessionView } = useSessionView({
scopeId: tableId,
});
const graphOverlay = useMemo(() => {
const shouldShowOverlay =
(graphEventId != null && graphEventId.length > 0) || sessionViewConfig != null;
@ -230,6 +195,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
<GraphOverlay scopeId={tableId} SessionView={SessionView} Navigation={Navigation} />
) : null;
}, [graphEventId, tableId, sessionViewConfig, SessionView, Navigation]);
const setQuery = useCallback(
({ id, inspect, loading, refetch }: SetQuery) =>
dispatch(
@ -320,7 +286,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
skip: !canQueryTimeline,
sort: sortField,
startDate: start,
filterStatus: currentFilter,
filterStatus: undefined,
});
useEffect(() => {
@ -411,17 +377,12 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
({ eventIds, isSelected }: { eventIds: string[]; isSelected: boolean }) => {
setSelected({
id: tableId,
eventIds: getEventIdToDataMapping(
nonDeletedEvents,
eventIds,
queryFields,
hasCrudPermissions
),
eventIds: getEventIdToDataMapping(nonDeletedEvents, eventIds, queryFields, true),
isSelected,
isSelectAllChecked: isSelected && selectedCount + 1 === nonDeletedEvents.length,
});
},
[setSelected, tableId, nonDeletedEvents, queryFields, hasCrudPermissions, selectedCount]
[setSelected, tableId, nonDeletedEvents, queryFields, selectedCount]
);
const onSelectPage: OnSelectAll = useCallback(
@ -433,13 +394,13 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
nonDeletedEvents,
nonDeletedEvents.map((event) => event._id),
queryFields,
hasCrudPermissions
true
),
isSelected,
isSelectAllChecked: isSelected,
})
: clearSelected({ id: tableId }),
[setSelected, tableId, nonDeletedEvents, queryFields, hasCrudPermissions, clearSelected]
[setSelected, tableId, nonDeletedEvents, queryFields, clearSelected]
);
// Sync to selectAll so parent components can select all events
@ -460,7 +421,7 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
fieldBrowserOptions,
loadingEventIds,
onRowSelected,
onRuleChange,
onRuleChange: undefined,
selectedEventIds,
showCheckboxes,
tabType: 'query',
@ -483,7 +444,6 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
fieldBrowserOptions,
loadingEventIds,
onRowSelected,
onRuleChange,
selectedEventIds,
tableId,
isSelectAllChecked,
@ -500,9 +460,9 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
tableId,
data: nonDeletedEvents,
totalItems: totalCountMinusDeleted,
hasAlertsCrud: hasCrudPermissions,
hasAlertsCrud: true,
showCheckboxes,
filterStatus: currentFilter,
filterStatus: undefined,
filterQuery,
bulkActions,
selectedCount,
@ -521,15 +481,6 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
[totalCountMinusDeleted, unit]
);
const rowHeightsOptions: EuiDataGridRowHeightsOptions | undefined = useMemo(() => {
if (tableView === 'eventRenderedView') {
return {
defaultHeight: 'auto' as const,
};
}
return undefined;
}, [tableView]);
const pagination = useMemo(
() => ({
pageIndex: pageInfo.activePage,
@ -542,83 +493,79 @@ const StatefulEventsViewerComponent: React.FC<EventsViewerProps & PropsFromRedux
);
return (
<>
<FullScreenContainer $isFullScreen={globalFullScreen}>
<InspectButtonContainer>
<StyledEuiPanel
hasBorder={false}
hasShadow={false}
paddingSize="none"
data-test-subj="events-viewer-panel"
$isFullScreen={globalFullScreen}
<div data-test-subj="events-viewer-panel">
{showFullLoading && <TableLoading height="short" />}
{graphOverlay}
{canQueryTimeline && (
<TableContext.Provider value={tableContext}>
<div
data-timeline-id={tableId}
data-test-subj={`events-container-loading-${loading}`}
css={css`
position: relative;
`}
>
{showFullLoading && <TableLoading height="short" />}
{graphOverlay}
{canQueryTimeline && (
<TableContext.Provider value={tableContext}>
<EventsContainerLoading
data-timeline-id={tableId}
data-test-subj={`events-container-loading-${loading}`}
>
<RightTopMenu
tableView={tableView}
loading={loading}
tableId={tableId}
title={title}
onViewChange={(selectedView) => setTableView(selectedView)}
additionalFilters={additionalFilters}
hasRightOffset={tableView === 'gridView' && nonDeletedEvents.length > 0}
additionalMenuOptions={additionalRightMenuOptions}
/>
{!hasAlerts && !loading && !graphOverlay && <EmptyTable />}
{hasAlerts && (
<FullWidthFlexGroupTable
$visible={!graphEventId && graphOverlay == null}
gutterSize="none"
>
<ScrollableFlexItem grow={1}>
<StatefulEventContext.Provider value={activeStatefulEventContext}>
<DataTableComponent
cellActionsTriggerId={cellActionsTriggerId}
additionalControls={alertBulkActions}
unitCountText={unitCountText}
browserFields={browserFields}
data={nonDeletedEvents}
id={tableId}
loadPage={loadPage}
// TODO: migrate away from deprecated type
renderCellValue={
renderCellValue as (
props: DeprecatedCellValueElementProps
) => React.ReactNode
}
// TODO: migrate away from deprecated type
rowRenderers={rowRenderers as unknown as DeprecatedRowRenderer[]}
totalItems={totalCountMinusDeleted}
bulkActions={bulkActions}
fieldBrowserOptions={fieldBrowserOptions}
hasCrudPermissions={hasCrudPermissions}
leadingControlColumns={transformedLeadingControlColumns}
pagination={pagination}
isEventRenderedView={tableView === 'eventRenderedView'}
rowHeightsOptions={rowHeightsOptions}
getFieldBrowser={getFieldBrowser}
getFieldSpec={getFieldSpec}
/>
</StatefulEventContext.Provider>
</ScrollableFlexItem>
</FullWidthFlexGroupTable>
{!loading && !graphOverlay && (
<div
css={css`
position: absolute;
top: ${theme.eui.euiSizeXS};
z-index: ${theme.eui.euiZLevel1 - 3};
right: ${nonDeletedEvents.length > 0 ? '72px' : theme.eui.euiSizeXS};
`}
>
<EuiFlexGroup data-test-subj="events-viewer-updated" gutterSize="m">
<EuiFlexItem grow={false}>
<InspectButton title={inspectModalTitle} queryId={tableId} />
</EuiFlexItem>
{topRightMenuOptions && (
<EuiFlexItem grow={false}>{topRightMenuOptions}</EuiFlexItem>
)}
</EventsContainerLoading>
</TableContext.Provider>
</EuiFlexGroup>
</div>
)}
</StyledEuiPanel>
</InspectButtonContainer>
</FullScreenContainer>
</>
{!hasAlerts && !loading && !graphOverlay && <EmptyTable />}
{hasAlerts && (
<EuiFlexItem
css={css`
display: ${!graphEventId && graphOverlay == null ? 'flex' : 'none'};
overflow: auto;
`}
>
<StatefulEventContext.Provider value={activeStatefulEventContext}>
<DataTableComponent
additionalControls={alertBulkActions}
browserFields={browserFields}
bulkActions={bulkActions}
data={nonDeletedEvents}
fieldBrowserOptions={fieldBrowserOptions}
id={tableId}
leadingControlColumns={transformedLeadingControlColumns}
loadPage={loadPage}
// TODO: migrate away from deprecated type
renderCellValue={
renderCellValue as (props: DeprecatedCellValueElementProps) => React.ReactNode
}
// TODO: migrate away from deprecated type
rowRenderers={rowRenderers as unknown as DeprecatedRowRenderer[]}
unitCountText={unitCountText}
pagination={pagination}
totalItems={totalCountMinusDeleted}
getFieldBrowser={getFieldBrowser}
getFieldSpec={getFieldSpec}
cellActionsTriggerId={cellActionsTriggerId}
/>
</StatefulEventContext.Provider>
</EuiFlexItem>
)}
</div>
</TableContext.Provider>
)}
</div>
);
};

View file

@ -1,88 +0,0 @@
/*
* 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 type { ViewSelection } from '@kbn/securitysolution-data-table';
import { TableId } from '@kbn/securitysolution-data-table';
import React, { useMemo } from 'react';
import type { CSSProperties } from 'styled-components';
import styled from 'styled-components';
import { InspectButton } from '../inspect';
import { UpdatedFlexGroup, UpdatedFlexItem } from './styles';
import { SummaryViewSelector } from './summary_view_select';
const TitleText = styled.span`
margin-right: 12px;
`;
interface Props {
tableView: ViewSelection;
loading: boolean;
tableId: TableId;
title: string;
onViewChange: (viewSelection: ViewSelection) => void;
additionalFilters?: React.ReactNode;
hasRightOffset?: boolean;
showInspect?: boolean;
position?: CSSProperties['position'];
additionalMenuOptions?: React.ReactNode[];
}
export const RightTopMenu = ({
tableView,
loading,
tableId,
title,
onViewChange,
additionalFilters,
hasRightOffset,
showInspect = true,
position = 'absolute',
additionalMenuOptions = [],
}: Props) => {
const alignItems = tableView === 'gridView' ? 'baseline' : 'center';
const justTitle = useMemo(() => <TitleText data-test-subj="title">{title}</TitleText>, [title]);
const menuOptions = useMemo(
() =>
additionalMenuOptions.length
? additionalMenuOptions.map((additionalMenuOption, i) => (
<UpdatedFlexItem grow={false} $show={!loading} key={i}>
{additionalMenuOption}
</UpdatedFlexItem>
))
: null,
[additionalMenuOptions, loading]
);
return (
<UpdatedFlexGroup
alignItems={alignItems}
data-test-subj="events-viewer-updated"
gutterSize="m"
component="span"
justifyContent="flexEnd"
direction="row"
$hasRightOffset={hasRightOffset}
position={position}
>
{showInspect ? (
<UpdatedFlexItem grow={false} $show={!loading}>
<InspectButton title={justTitle} queryId={tableId} />
</UpdatedFlexItem>
) : null}
<UpdatedFlexItem grow={false} $show={!loading}>
{additionalFilters}
</UpdatedFlexItem>
{[TableId.alertsOnRuleDetailsPage, TableId.alertsOnAlertsPage].includes(tableId) && (
<UpdatedFlexItem grow={false} $show={!loading} data-test-subj="summary-view-selector">
<SummaryViewSelector viewSelected={tableView} onViewChange={onViewChange} />
</UpdatedFlexItem>
)}
{menuOptions}
</UpdatedFlexGroup>
);
};

View file

@ -1,92 +0,0 @@
/*
* 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 { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
import type { CSSProperties } from 'styled-components';
import styled from 'styled-components';
export const SELECTOR_TIMELINE_GLOBAL_CONTAINER = 'securitySolutionTimeline__container';
export const EVENTS_TABLE_CLASS_NAME = 'siemEventsTable';
export const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>`
height: ${({ $isFullScreen }) => ($isFullScreen ? '100%' : undefined)};
flex: 1 1 auto;
display: flex;
width: 100%;
`;
export const FullWidthFlexGroupTable = styled(EuiFlexGroup)<{ $visible: boolean }>`
overflow: hidden;
margin: 0;
display: ${({ $visible }) => ($visible ? 'flex' : 'none')};
`;
export const ScrollableFlexItem = styled(EuiFlexItem)`
overflow: auto;
`;
export const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible?: boolean }>`
overflow: hidden;
margin: 0;
min-height: 490px;
display: ${({ $visible = true }) => ($visible ? 'flex' : 'none')};
`;
export const UpdatedFlexGroup = styled(EuiFlexGroup)<{
$hasRightOffset?: boolean;
position: CSSProperties['position'];
}>`
${({ $hasRightOffset, theme, position }) =>
position === 'relative'
? `margin-right: ${theme.eui.euiSizeXS}; margin-left: `
: $hasRightOffset && position === 'absolute'
? `margin-right: ${theme.eui.euiSizeXL};`
: `margin-right: ${theme.eui.euiSizeXS};`}
${({ position }) => {
return position === 'absolute'
? `position: absolute`
: `display: flex; justify-content:center; align-items:center`;
}};
display: inline-flex;
z-index: ${({ theme }) => theme.eui.euiZLevel1 - 3};
${({ $hasRightOffset, theme, position }) =>
position === 'relative'
? `right: 0;`
: $hasRightOffset && position === 'absolute'
? `right: ${theme.eui.euiSizeXL};`
: `right: ${theme.eui.euiSizeL};`}
`;
export const UpdatedFlexItem = styled(EuiFlexItem)<{ $show: boolean }>`
${({ $show }) => ($show ? '' : 'visibility: hidden;')}
`;
export const EventsContainerLoading = styled.div.attrs(({ className = '' }) => ({
className: `${SELECTOR_TIMELINE_GLOBAL_CONTAINER} ${className}`,
}))`
position: relative;
width: 100%;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
`;
export const StyledEuiPanel = styled(EuiPanel)<{ $isFullScreen: boolean }>`
display: flex;
flex-direction: column;
position: relative;
width: 100%;
${({ $isFullScreen }) =>
$isFullScreen &&
`
border: 0;
box-shadow: none;
padding-top: 0;
padding-bottom: 0;
`}
`;

View file

@ -8,7 +8,7 @@
import React from 'react';
import { getPersistentControlsHook } from './use_persistent_controls';
import { TableId } from '@kbn/securitysolution-data-table';
import { render, fireEvent, renderHook } from '@testing-library/react';
import { fireEvent, render, renderHook } from '@testing-library/react';
import { createMockStore, mockGlobalState, TestProviders } from '../../../common/mock';
import { useSourcererDataView } from '../../../sourcerer/containers';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
@ -93,7 +93,7 @@ describe('usePersistentControls', () => {
),
});
const groupSelector = result.current.right.props.additionalMenuOptions[0];
const groupSelector = result.current.right.props.children[2];
const { getByTestId } = render(<TestProviders store={store}>{groupSelector}</TestProviders>);
fireEvent.click(getByTestId('group-selector-dropdown'));

View file

@ -7,23 +7,25 @@
import React, { useCallback, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import type { ViewSelection } from '@kbn/securitysolution-data-table';
import {
dataTableActions,
dataTableSelectors,
tableDefaults,
dataTableActions,
TableId,
} from '@kbn/securitysolution-data-table';
import type { ViewSelection, TableId } from '@kbn/securitysolution-data-table';
import { useGetGroupSelectorStateless } from '@kbn/grouping/src/hooks/use_get_group_selector';
import { getTelemetryEvent } from '@kbn/grouping/src/telemetry/const';
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { SummaryViewSelector } from '../../../common/components/events_viewer/summary_view_select';
import { groupIdSelector } from '../../../common/store/grouping/selectors';
import { useSourcererDataView } from '../../../sourcerer/containers';
import { SourcererScopeName } from '../../../sourcerer/store/model';
import { updateGroups } from '../../../common/store/grouping/actions';
import { useKibana } from '../../../common/lib/kibana';
import { METRIC_TYPE, AlertsEventTypes, track } from '../../../common/lib/telemetry';
import { AlertsEventTypes, METRIC_TYPE, track } from '../../../common/lib/telemetry';
import { useDataTableFilters } from '../../../common/hooks/use_data_table_filters';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
import { RightTopMenu } from '../../../common/components/events_viewer/right_top_menu';
import { AdditionalFiltersAction } from '../../components/alerts_table/additional_filters_action';
const { changeViewMode } = dataTableActions;
@ -120,18 +122,15 @@ export const getPersistentControlsHook = (tableId: TableId) => {
const rightTopMenu = useMemo(
() => (
<RightTopMenu
position="relative"
tableView={tableView}
loading={false}
tableId={tableId}
title={'Some Title'}
onViewChange={handleChangeTableView}
hasRightOffset={false}
additionalFilters={additionalFiltersComponent}
showInspect={false}
additionalMenuOptions={groupSelector != null ? [groupSelector] : []}
/>
<EuiFlexGroup alignItems="center" gutterSize="m">
{[TableId.alertsOnRuleDetailsPage, TableId.alertsOnAlertsPage].includes(tableId) && (
<EuiFlexItem grow={false} data-test-subj="summary-view-selector">
<SummaryViewSelector viewSelected={tableView} onViewChange={handleChangeTableView} />
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>{additionalFiltersComponent}</EuiFlexItem>
<EuiFlexItem grow={false}>{groupSelector}</EuiFlexItem>
</EuiFlexGroup>
),
[tableView, handleChangeTableView, additionalFiltersComponent, groupSelector]
);