mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Security Solution] Restores the @timestamp
column actions and fixes an @timestamp
alignment issue (#118990) (#119184)
## [Security Solution] Restores the `@timestamp` column actions and fixes an `@timestamp` alignment issue This PR fixes the `@timestamp` column issues described in https://github.com/elastic/kibana/issues/118989 The PR: - Fixes an issue where the `Filter in`, `Filter out`, and `Investigate in timeline` actions were disabled in the `Security > Alerts` table, per the before / after screenshots below: **Before**  _Above: The `Filter in`, `Filter out`, and `Investigate in timeline` actions were disabled for `@timestamp` in Security > Alerts_ **After**  _Above: The `Filter in`, `Filter out`, and `Investigate in timeline` actions are enabled for `@timestamp` in Security > Alerts_ - Fixes a CSS issue where text truncation styles were causing the `@timestamp` column to be mis-aligned in Timeline, per the before / after screenshots below: **Before**  _Above: The `@timestamp` column in Timeline was vertically mis-aligned_ **After**  _Above: The `@timestamp` column in Timeline is correctly (vertically) aligned_ ### No changes to the o11y alert actions There are no changes to the actions shown in the `o11y` alerts table, per the before / after screenshots below:  _Above: Before - the `@timestamp` field in the `o11y` alerts table does NOT have actions_  _Above: After - the `@timestamp` field in the `o11y` alerts table (still) does NOT have actions_ ### Field browser search input auto-focus - Fixed an issue where the `Fields` browser search input was not auto-focused, per the screenshot above:  _Above: The search input is auto-focused when the `Fields` browser is opened_ ### Details The fix that re-enables the `Filter in`, `Filter out`, and `Investigate in timeline` actions in the `Security > Alerts` required removing a recently-introduced `TODO` in `x-pack/plugins/timelines/public/components/t_grid/body/index.tsx`, which defined a common set of disabled actions for both the `o11y` and `Security` solutions. The `TODO` was replaced by a `disabledCellActions` prop, which enables each solution to configure the disabled cell actions independently. ### Desk testing While desk testing the `@timestamp` alignment issue: > - Fixes a CSS issue where text truncation styles were causing the `@timestamp` column to be mis-aligned in Timeline You may find it informative to locally edit `x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.tsx` to add the following styles: ```css background-color: red; ``` and ```css background-color: green; ``` to `ProviderContentWrapper`, as shown in the code below: ```typescript export const ProviderContentWrapper = styled.span` > span.euiToolTipAnchor { background-color: red; display: block; /* allow EuiTooltip content to be truncatable */ } > span.euiToolTipAnchor.eui-textTruncate { background-color: green; display: inline-block; /* do not override display when a tooltip is truncated via eui-textTruncate */ } `; ``` as illustrated by the `diff` below:  _Above: `background-color: green` and `background-color: red` styles added locally for desk testing_ With the (temporary) style changes above, the effect (and scope) of the new style is easily seen, as shown in the screenshot below:  _Above: The effect of the (green) style changes compared with the (red) unchanged styles_ When the new style introduced in this PR is commented-out for desk testing, as shown in the code below: ```typescript export const ProviderContentWrapper = styled.span` > span.euiToolTipAnchor { background-color: red; display: block; /* allow EuiTooltip content to be truncatable */ } /* > span.euiToolTipAnchor.eui-textTruncate { background-color: green; display: inline-block; /* do not override display when a tooltip is truncated via eui-textTruncate */ } */ `; ``` the behavior of `@timestamp` reverts to the behavior prior to this PR, as shown in the screenshot below:  _Above: The `@timestamp` column defaults to the old (red) unchanged behavior when the new style is commented-out for desk testing_ Co-authored-by: Andrew Goldstein <andrew-goldstein@users.noreply.github.com>
This commit is contained in:
parent
c517030fee
commit
b627dca3ba
14 changed files with 87 additions and 16 deletions
|
@ -313,6 +313,14 @@ function ObservabilityActions({
|
|||
);
|
||||
}
|
||||
|
||||
const FIELDS_WITHOUT_CELL_ACTIONS = [
|
||||
'@timestamp',
|
||||
'signal.rule.risk_score',
|
||||
'signal.reason',
|
||||
'kibana.alert.duration.us',
|
||||
'kibana.alert.reason',
|
||||
];
|
||||
|
||||
export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
||||
const { indexNames, rangeFrom, rangeTo, kuery, workflowStatus, setRefetch, addToQuery } = props;
|
||||
const prevWorkflowStatus = usePrevious(workflowStatus);
|
||||
|
@ -382,6 +390,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
columns,
|
||||
deletedEventIds,
|
||||
defaultCellActions: getDefaultCellActions({ addToQuery }),
|
||||
disabledCellActions: FIELDS_WITHOUT_CELL_ACTIONS,
|
||||
end: rangeTo,
|
||||
filters: [],
|
||||
hasAlertsCrudPermissions,
|
||||
|
|
|
@ -85,6 +85,10 @@ export const ProviderContentWrapper = styled.span`
|
|||
> span.euiToolTipAnchor {
|
||||
display: block; /* allow EuiTooltip content to be truncatable */
|
||||
}
|
||||
|
||||
> span.euiToolTipAnchor.eui-textTruncate {
|
||||
display: inline-block; /* do not override display when a tooltip is truncated via eui-textTruncate */
|
||||
}
|
||||
`;
|
||||
|
||||
type RenderFunctionProp = (
|
||||
|
|
|
@ -26,6 +26,7 @@ import type { EntityType } from '../../../../../timelines/common';
|
|||
import { TGridCellAction } from '../../../../../timelines/common/types';
|
||||
import { DetailsPanel } from '../../../timelines/components/side_panel';
|
||||
import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
|
||||
import { FIELDS_WITHOUT_CELL_ACTIONS } from '../../lib/cell_actions/constants';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
|
||||
import { useCreateFieldButton } from '../../../timelines/components/create_field_button';
|
||||
|
@ -180,6 +181,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
|
|||
dataProviders,
|
||||
defaultCellActions,
|
||||
deletedEventIds,
|
||||
disabledCellActions: FIELDS_WITHOUT_CELL_ACTIONS,
|
||||
docValueFields,
|
||||
end,
|
||||
entityType,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** actions are disabled for these fields in tables and popovers */
|
||||
export const FIELDS_WITHOUT_CELL_ACTIONS = ['signal.rule.risk_score', 'signal.reason'];
|
|
@ -13,14 +13,14 @@ import { getColumnRenderer } from '../body/renderers/get_column_renderer';
|
|||
|
||||
import { CellValueElementProps } from '.';
|
||||
import { getLink } from '../../../../common/lib/cell_actions/helpers';
|
||||
import { FIELDS_WITHOUT_CELL_ACTIONS } from '../../../../common/lib/cell_actions/constants';
|
||||
import {
|
||||
ExpandedCellValueActions,
|
||||
StyledContent,
|
||||
} from '../../../../common/lib/cell_actions/expanded_cell_value_actions';
|
||||
|
||||
const FIELDS_WITHOUT_CELL_ACTIONS = ['signal.rule.risk_score', 'signal.reason'];
|
||||
const hasCellActions = (columnId?: string) => {
|
||||
return columnId && FIELDS_WITHOUT_CELL_ACTIONS.indexOf(columnId) < 0;
|
||||
return columnId && !FIELDS_WITHOUT_CELL_ACTIONS.includes(columnId);
|
||||
};
|
||||
|
||||
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
|
||||
|
|
|
@ -11,6 +11,7 @@ import { ColumnHeaderOptions } from '../../../../common';
|
|||
import { Ecs } from '../../../../common/ecs';
|
||||
import {
|
||||
allowSorting,
|
||||
hasCellActions,
|
||||
mapSortDirectionToDirection,
|
||||
mapSortingColumns,
|
||||
stringifyEvent,
|
||||
|
@ -420,4 +421,20 @@ describe('helpers', () => {
|
|||
expect(mockedSetCellProps).toBeCalledWith({ style: { backgroundColor: 'inherit' } });
|
||||
});
|
||||
});
|
||||
|
||||
describe('hasCellActions', () => {
|
||||
const columnId = '@timestamp';
|
||||
|
||||
test('it returns false when the columnId is included in `disabledCellActions` ', () => {
|
||||
const disabledCellActions = ['foo', '@timestamp', 'bar', 'baz']; // includes @timestamp
|
||||
|
||||
expect(hasCellActions({ columnId, disabledCellActions })).toBe(false);
|
||||
});
|
||||
|
||||
test('it returns true when the columnId is NOT included in `disabledCellActions` ', () => {
|
||||
const disabledCellActions = ['foo', 'bar', 'baz'];
|
||||
|
||||
expect(hasCellActions({ columnId, disabledCellActions })).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -239,3 +239,12 @@ export const addBuildingBlockStyle = (
|
|||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns true when the specified column has cell actions */
|
||||
export const hasCellActions = ({
|
||||
columnId,
|
||||
disabledCellActions,
|
||||
}: {
|
||||
columnId: string;
|
||||
disabledCellActions: string[];
|
||||
}) => !disabledCellActions.includes(columnId);
|
||||
|
|
|
@ -68,6 +68,8 @@ describe('Body', () => {
|
|||
clearSelected: jest.fn() as unknown as StatefulBodyProps['clearSelected'],
|
||||
columnHeaders: defaultHeaders,
|
||||
data: mockTimelineData,
|
||||
defaultCellActions: [],
|
||||
disabledCellActions: ['signal.rule.risk_score', 'signal.reason'],
|
||||
excludedRowRendererIds: [],
|
||||
id: 'timeline-test',
|
||||
isSelectAllChecked: false,
|
||||
|
@ -156,7 +158,7 @@ describe('Body', () => {
|
|||
).toEqual(mockTimelineData[0].ecs.timestamp);
|
||||
});
|
||||
|
||||
test("timestamp column doesn't render cell actions", () => {
|
||||
test('timestamp column renders cell actions', () => {
|
||||
const headersJustTimestamp = defaultHeaders.filter((h) => h.id === '@timestamp');
|
||||
const testProps = {
|
||||
...props,
|
||||
|
@ -176,7 +178,7 @@ describe('Body', () => {
|
|||
.first()
|
||||
.prop<EuiDataGridColumn[]>('columns')
|
||||
.find((c) => c.id === '@timestamp')?.cellActions
|
||||
).toBeUndefined();
|
||||
).toBeDefined();
|
||||
});
|
||||
|
||||
test("signal.rule.risk_score column doesn't render cell actions", () => {
|
||||
|
|
|
@ -59,6 +59,7 @@ import { getColumnHeaders } from './column_headers/helpers';
|
|||
import {
|
||||
addBuildingBlockStyle,
|
||||
getEventIdToDataMapping,
|
||||
hasCellActions,
|
||||
mapSortDirectionToDirection,
|
||||
mapSortingColumns,
|
||||
} from './helpers';
|
||||
|
@ -92,6 +93,7 @@ interface OwnProps {
|
|||
createFieldComponent?: CreateFieldComponentType;
|
||||
data: TimelineItem[];
|
||||
defaultCellActions?: TGridCellAction[];
|
||||
disabledCellActions: string[];
|
||||
filters?: Filter[];
|
||||
filterQuery?: string;
|
||||
filterStatus?: AlertStatus;
|
||||
|
@ -145,16 +147,6 @@ const EuiDataGridContainer = styled.div<{ hideLastPage: boolean }>`
|
|||
}
|
||||
`;
|
||||
|
||||
// TODO: accept extra list of column ids without actions from callsites
|
||||
const FIELDS_WITHOUT_CELL_ACTIONS = [
|
||||
'@timestamp',
|
||||
'signal.rule.risk_score',
|
||||
'signal.reason',
|
||||
'kibana.alert.duration.us',
|
||||
'kibana.alert.reason',
|
||||
];
|
||||
const hasCellActions = (columnId?: string) =>
|
||||
columnId && FIELDS_WITHOUT_CELL_ACTIONS.indexOf(columnId) < 0;
|
||||
const transformControlColumns = ({
|
||||
columnHeaders,
|
||||
controlColumns,
|
||||
|
@ -182,6 +174,7 @@ const transformControlColumns = ({
|
|||
controlColumns: ControlColumnProps[];
|
||||
createFieldComponent?: CreateFieldComponentType;
|
||||
data: TimelineItem[];
|
||||
disabledCellActions: string[];
|
||||
isEventViewer?: boolean;
|
||||
loadingEventIds: string[];
|
||||
onRowSelected: OnRowSelected;
|
||||
|
@ -311,6 +304,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
createFieldComponent,
|
||||
data,
|
||||
defaultCellActions,
|
||||
disabledCellActions,
|
||||
filterQuery,
|
||||
filters,
|
||||
filterStatus,
|
||||
|
@ -621,6 +615,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
controlColumns,
|
||||
createFieldComponent,
|
||||
data,
|
||||
disabledCellActions,
|
||||
isEventViewer,
|
||||
loadingEventIds,
|
||||
onRowSelected,
|
||||
|
@ -647,6 +642,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
columnHeaders,
|
||||
createFieldComponent,
|
||||
data,
|
||||
disabledCellActions,
|
||||
isEventViewer,
|
||||
id,
|
||||
loadingEventIds,
|
||||
|
@ -692,7 +688,10 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
},
|
||||
],
|
||||
},
|
||||
...(hasCellActions(header.id)
|
||||
...(hasCellActions({
|
||||
columnId: header.id,
|
||||
disabledCellActions,
|
||||
})
|
||||
? {
|
||||
cellActions:
|
||||
header.tGridCellActions?.map(buildAction) ??
|
||||
|
@ -701,7 +700,16 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
: {}),
|
||||
};
|
||||
}),
|
||||
[columnHeaders, defaultCellActions, browserFields, data, pageSize, id, dispatch]
|
||||
[
|
||||
browserFields,
|
||||
columnHeaders,
|
||||
data,
|
||||
defaultCellActions,
|
||||
disabledCellActions,
|
||||
dispatch,
|
||||
id,
|
||||
pageSize,
|
||||
]
|
||||
);
|
||||
|
||||
const renderTGridCellValue = useMemo(() => {
|
||||
|
|
|
@ -102,6 +102,7 @@ export interface TGridIntegratedProps {
|
|||
dataProviders: DataProvider[];
|
||||
defaultCellActions?: TGridCellAction[];
|
||||
deletedEventIds: Readonly<string[]>;
|
||||
disabledCellActions: string[];
|
||||
docValueFields: DocValueFields[];
|
||||
end: string;
|
||||
entityType: EntityType;
|
||||
|
@ -144,6 +145,7 @@ const TGridIntegratedComponent: React.FC<TGridIntegratedProps> = ({
|
|||
dataProviders,
|
||||
defaultCellActions,
|
||||
deletedEventIds,
|
||||
disabledCellActions,
|
||||
docValueFields,
|
||||
end,
|
||||
entityType,
|
||||
|
@ -353,6 +355,7 @@ const TGridIntegratedComponent: React.FC<TGridIntegratedProps> = ({
|
|||
createFieldComponent={createFieldComponent}
|
||||
data={nonDeletedEvents}
|
||||
defaultCellActions={defaultCellActions}
|
||||
disabledCellActions={disabledCellActions}
|
||||
filterQuery={filterQuery}
|
||||
filters={filters}
|
||||
filterStatus={filterStatus}
|
||||
|
|
|
@ -86,6 +86,7 @@ export interface TGridStandaloneProps {
|
|||
columns: ColumnHeaderOptions[];
|
||||
defaultCellActions?: TGridCellAction[];
|
||||
deletedEventIds: Readonly<string[]>;
|
||||
disabledCellActions: string[];
|
||||
end: string;
|
||||
entityType?: EntityType;
|
||||
loadingText: React.ReactNode;
|
||||
|
@ -126,6 +127,7 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
|||
columns,
|
||||
defaultCellActions,
|
||||
deletedEventIds,
|
||||
disabledCellActions,
|
||||
end,
|
||||
entityType = 'alerts',
|
||||
loadingText,
|
||||
|
@ -381,6 +383,7 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
|||
browserFields={browserFields}
|
||||
data={nonDeletedEvents}
|
||||
defaultCellActions={defaultCellActions}
|
||||
disabledCellActions={disabledCellActions}
|
||||
filterQuery={filterQuery}
|
||||
hasAlertsCrud={hasAlertsCrud}
|
||||
hasAlertsCrudPermissions={hasAlertsCrudPermissions}
|
||||
|
|
|
@ -57,12 +57,15 @@ const CountRow = React.memo<Pick<Props, 'filteredBrowserFields'>>(({ filteredBro
|
|||
|
||||
CountRow.displayName = 'CountRow';
|
||||
|
||||
const inputRef = (node: HTMLInputElement | null) => node?.focus();
|
||||
|
||||
export const Search = React.memo<Props>(
|
||||
({ isSearching, filteredBrowserFields, onSearchInputChange, searchInput, timelineId }) => (
|
||||
<>
|
||||
<EuiFieldSearch
|
||||
className={getFieldBrowserSearchInputClassName(timelineId)}
|
||||
data-test-subj="field-search"
|
||||
inputRef={inputRef}
|
||||
isLoading={isSearching}
|
||||
onChange={onSearchInputChange}
|
||||
placeholder={i18n.FILTER_PLACEHOLDER}
|
||||
|
|
|
@ -92,6 +92,7 @@ export const tGridIntegratedProps: TGridIntegratedProps = {
|
|||
columns: columnHeaders,
|
||||
dataProviders: mockDataProviders,
|
||||
deletedEventIds: [],
|
||||
disabledCellActions: [],
|
||||
docValueFields: mockDocValueFields,
|
||||
end: '2021-08-19T00:30:00.000Z',
|
||||
entityType: 'alerts',
|
||||
|
|
|
@ -75,6 +75,7 @@ const AppRoot = React.memo(
|
|||
columns: [],
|
||||
indexNames: [],
|
||||
deletedEventIds: [],
|
||||
disabledCellActions: [],
|
||||
end: '',
|
||||
footerText: 'Events',
|
||||
filters: [],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue