mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[8.0] [Security Solution] Adjusts the width of the Actions
column and action icon buttons (#118454) (#118598)
* [Security Solution] Adjusts the width of the `Actions` column and action icon buttons (#118454) ## [Security Solution] Adjusts the width of the `Actions` column and action icon buttons This PR adjusts the width of the `Actions` column, and normalizes the action icon button sizes throughout the Security Solution, per https://github.com/elastic/kibana/issues/115726 ### Before / after screenshots This section provides before / after screenshots for the following views: - Alerts - Alerts > Event rendered - Rules > Details - Rules > Details > Event rendered - Host > Events - Host > External alerts - Network > External alerts - Timeline > Query tab - Timeline > Correlation tab - Timeline > Pinned tab - Observability > alerts (no change) #### Alerts (before)  #### Alerts (after)  #### Alerts > Event rendered (before)  #### Alerts > Event rendered (after)  #### Rules > Details (before)  #### Rules > Details (after)  #### Rules > Details > Event rendered (before)  #### Rules > Details > Event rendered (after)  #### Host > Events (before)  #### Host > Events (after)  #### Host > External alerts (before)  #### Host > External alerts (after)  #### Network > External alerts (before)  #### Network > External alerts (after)  #### Timeline > Query tab (before)  #### Timeline > Query tab (after)  #### Timeline > Correlation tab (before)  #### Timeline > Correlation tab (after)  #### Timeline > Pinned tab (before)  #### Timeline > Pinned tab (after)  #### Observability > alerts (before)  #### Observability > alerts (after / no change)  ### Additional details - Per [this comment](https://github.com/elastic/kibana/issues/115726#issuecomment-962077067) from @monina-n , the size of all action buttons have been normalized match the size off the `...` overflow button (`28 x 32` at the time of this writing) via the `EuiButtonIcon` `size` prop: ``` size="s" ``` - The horizontal alignment of the `Analyze event` icon was updated by the EUI team in the following PR: https://github.com/elastic/eui/pull/5365 # Conflicts: # x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx * - removed createFieldComponent prop
This commit is contained in:
parent
81f69039af
commit
6f17707bab
41 changed files with 309 additions and 1205 deletions
|
@ -21,6 +21,7 @@ import { SourcererScopeName } from '../../store/sourcerer/model';
|
|||
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
|
||||
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
|
||||
import type { EntityType } from '../../../../../timelines/common';
|
||||
import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
|
||||
|
||||
export interface OwnProps {
|
||||
end: string;
|
||||
|
@ -79,6 +80,7 @@ const AlertsTableComponent: React.FC<Props> = ({
|
|||
const dispatch = useDispatch();
|
||||
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
|
||||
const { filterManager } = useKibana().services.data.query;
|
||||
const ACTION_BUTTON_COUNT = 3;
|
||||
|
||||
const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled');
|
||||
|
||||
|
@ -104,6 +106,8 @@ const AlertsTableComponent: React.FC<Props> = ({
|
|||
);
|
||||
}, [dispatch, filterManager, tGridEnabled, timelineId]);
|
||||
|
||||
const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []);
|
||||
|
||||
return (
|
||||
<StatefulEventsViewer
|
||||
pageFilters={alertsFilter}
|
||||
|
@ -112,6 +116,7 @@ const AlertsTableComponent: React.FC<Props> = ({
|
|||
end={endDate}
|
||||
entityType={entityType}
|
||||
id={timelineId}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
renderCellValue={DefaultCellRenderer}
|
||||
rowRenderers={defaultRowRenderers}
|
||||
scopeId={SourcererScopeName.default}
|
||||
|
|
|
@ -1,485 +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 React from 'react';
|
||||
import { waitFor, act } from '@testing-library/react';
|
||||
import useResizeObserver from 'use-resize-observer/polyfilled';
|
||||
|
||||
import '../../mock/match_media';
|
||||
import { mockIndexNames, mockIndexPattern, TestProviders } from '../../mock';
|
||||
|
||||
import { mockEventViewerResponse, mockEventViewerResponseWithEvents } from './mock';
|
||||
import { StatefulEventsViewer } from '.';
|
||||
import { EventsViewer } from './events_viewer';
|
||||
import { defaultHeaders } from './default_headers';
|
||||
import { useSourcererDataView } from '../../containers/sourcerer';
|
||||
import {
|
||||
mockBrowserFields,
|
||||
mockDocValueFields,
|
||||
mockRuntimeMappings,
|
||||
} from '../../containers/source/mock';
|
||||
import { eventsDefaultModel } from './default_model';
|
||||
import { useMountAppended } from '../../utils/use_mount_appended';
|
||||
import { inputsModel } from '../../store/inputs';
|
||||
import { TimelineId, SortDirection } from '../../../../common/types/timeline';
|
||||
import { KqlMode } from '../../../timelines/store/timeline/model';
|
||||
import { EntityType } from '../../../../../timelines/common';
|
||||
import { AlertsTableFilterGroup } from '../../../detections/components/alerts_table/alerts_filter_group';
|
||||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
|
||||
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
|
||||
import { useTimelineEvents } from '../../../timelines/containers';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
|
||||
import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions';
|
||||
import { mockTimelines } from '../../mock/mock_timelines_plugin';
|
||||
|
||||
jest.mock('../../lib/kibana', () => ({
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
application: {
|
||||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: jest.fn(),
|
||||
capabilities: {
|
||||
siem: { crud_alerts: true, read_alerts: true },
|
||||
},
|
||||
},
|
||||
uiSettings: {
|
||||
get: jest.fn(),
|
||||
},
|
||||
savedObjects: {
|
||||
client: {},
|
||||
},
|
||||
timelines: { ...mockTimelines },
|
||||
},
|
||||
}),
|
||||
useToasts: jest.fn().mockReturnValue({
|
||||
addError: jest.fn(),
|
||||
addSuccess: jest.fn(),
|
||||
addWarning: jest.fn(),
|
||||
}),
|
||||
useGetUserCasesPermissions: jest.fn(),
|
||||
useDateFormat: jest.fn(),
|
||||
useTimeZone: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../hooks/use_experimental_features');
|
||||
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
|
||||
|
||||
jest.mock('../../../timelines/components/graph_overlay', () => ({
|
||||
GraphOverlay: jest.fn(() => <div />),
|
||||
}));
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
jest.mock('react-redux', () => {
|
||||
const original = jest.requireActual('react-redux');
|
||||
return {
|
||||
...original,
|
||||
useDispatch: () => mockDispatch,
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@elastic/eui', () => {
|
||||
const original = jest.requireActual('@elastic/eui');
|
||||
return {
|
||||
...original,
|
||||
useDataGridColumnSorting: jest.fn(),
|
||||
};
|
||||
});
|
||||
jest.mock('../../../timelines/containers', () => ({
|
||||
useTimelineEvents: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../components/url_state/normalize_time_range.ts');
|
||||
|
||||
const mockUseSourcererDataView: jest.Mock = useSourcererDataView as jest.Mock;
|
||||
jest.mock('../../containers/sourcerer');
|
||||
|
||||
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
|
||||
jest.mock('use-resize-observer/polyfilled');
|
||||
mockUseResizeObserver.mockImplementation(() => ({}));
|
||||
|
||||
const mockUseTimelineEvents: jest.Mock = useTimelineEvents as jest.Mock;
|
||||
jest.mock('../../../timelines/containers');
|
||||
|
||||
const from = '2019-08-26T22:10:56.791Z';
|
||||
const to = '2019-08-27T22:10:56.794Z';
|
||||
|
||||
const defaultMocks = {
|
||||
browserFields: mockBrowserFields,
|
||||
docValueFields: mockDocValueFields,
|
||||
runtimeMappings: mockRuntimeMappings,
|
||||
indexPattern: mockIndexPattern,
|
||||
loading: false,
|
||||
selectedPatterns: mockIndexNames,
|
||||
};
|
||||
|
||||
const utilityBar = (refetch: inputsModel.Refetch, totalCount: number) => (
|
||||
<div data-test-subj="mock-utility-bar" />
|
||||
);
|
||||
|
||||
const eventsViewerDefaultProps = {
|
||||
browserFields: {},
|
||||
columns: [],
|
||||
dataProviders: [],
|
||||
deletedEventIds: [],
|
||||
docValueFields: [],
|
||||
end: to,
|
||||
entityType: EntityType.ALERTS,
|
||||
filters: [],
|
||||
id: TimelineId.detectionsPage,
|
||||
indexNames: mockIndexNames,
|
||||
indexPattern: mockIndexPattern,
|
||||
isLive: false,
|
||||
isLoadingIndexPattern: false,
|
||||
itemsPerPage: 10,
|
||||
itemsPerPageOptions: [],
|
||||
kqlMode: 'filter' as KqlMode,
|
||||
query: {
|
||||
query: '',
|
||||
language: 'kql',
|
||||
},
|
||||
renderCellValue: DefaultCellRenderer,
|
||||
rowRenderers: defaultRowRenderers,
|
||||
runtimeMappings: {},
|
||||
start: from,
|
||||
sort: [
|
||||
{
|
||||
columnId: 'foo',
|
||||
columnType: 'number',
|
||||
sortDirection: 'asc' as SortDirection,
|
||||
},
|
||||
],
|
||||
scopeId: SourcererScopeName.timeline,
|
||||
utilityBar,
|
||||
};
|
||||
|
||||
describe('EventsViewer', () => {
|
||||
const mount = useMountAppended();
|
||||
|
||||
let testProps = {
|
||||
defaultCellActions,
|
||||
defaultModel: eventsDefaultModel,
|
||||
end: to,
|
||||
entityType: EntityType.ALERTS,
|
||||
id: TimelineId.test,
|
||||
renderCellValue: DefaultCellRenderer,
|
||||
rowRenderers: defaultRowRenderers,
|
||||
start: from,
|
||||
scopeId: SourcererScopeName.timeline,
|
||||
};
|
||||
beforeEach(() => {
|
||||
mockUseTimelineEvents.mockReset();
|
||||
});
|
||||
beforeAll(() => {
|
||||
mockUseSourcererDataView.mockImplementation(() => defaultMocks);
|
||||
});
|
||||
|
||||
describe('event details', () => {
|
||||
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
|
||||
beforeEach(() => {
|
||||
mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponseWithEvents]);
|
||||
});
|
||||
|
||||
test('call the right reduce action to show event details', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulEventsViewer {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
wrapper.find(`[data-test-subj="expand-event"]`).first().simulate('click');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockDispatch).toBeCalledTimes(3);
|
||||
expect(mockDispatch.mock.calls[1][0]).toEqual({
|
||||
payload: {
|
||||
id: 'test',
|
||||
isLoading: false,
|
||||
},
|
||||
type: 'x-pack/timelines/t-grid/UPDATE_LOADING',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('rendering', () => {
|
||||
beforeEach(() => {
|
||||
mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]);
|
||||
});
|
||||
|
||||
test('it renders the "Showing..." subtitle with the expected event count by default', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulEventsViewer {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().text()).toEqual(
|
||||
'Showing: 12 events'
|
||||
);
|
||||
});
|
||||
|
||||
test('should not render the "Showing..." subtitle with the expected event count if showTotalCount is set to false ', () => {
|
||||
const disableSubTitle = {
|
||||
...eventsViewerDefaultProps,
|
||||
showTotalCount: false,
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer {...disableSubTitle} graphEventId="a valid id" />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().text()).toEqual('');
|
||||
});
|
||||
|
||||
test('it renders the Fields Browser as a settings gear', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulEventsViewer {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="field-browser"]`).first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders the footer containing the pagination', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulEventsViewer {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="timeline-pagination"]`).first().exists()).toBe(true);
|
||||
});
|
||||
|
||||
defaultHeaders.forEach((header) => {
|
||||
test(`it renders the ${header.id} default EventsViewer column header`, () => {
|
||||
testProps = {
|
||||
...testProps,
|
||||
// Update with a new id, to force columns back to default.
|
||||
id: TimelineId.alternateTest,
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulEventsViewer {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
defaultHeaders.forEach((h) => {
|
||||
expect(wrapper.find(`[data-test-subj="header-text-${header.id}"]`).first().exists()).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('loading', () => {
|
||||
beforeAll(() => {
|
||||
mockUseSourcererDataView.mockImplementation(() => ({ ...defaultMocks, loading: true }));
|
||||
});
|
||||
beforeEach(() => {
|
||||
mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]);
|
||||
});
|
||||
|
||||
test('it does NOT render fetch index pattern is loading', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulEventsViewer {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test('it does NOT render when start is empty', () => {
|
||||
testProps = {
|
||||
...testProps,
|
||||
start: '',
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulEventsViewer {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test('it does NOT render when end is empty', () => {
|
||||
testProps = {
|
||||
...testProps,
|
||||
end: '',
|
||||
};
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<StatefulEventsViewer {...testProps} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('headerFilterGroup', () => {
|
||||
beforeEach(() => {
|
||||
mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]);
|
||||
});
|
||||
|
||||
test('it renders the provided headerFilterGroup', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer
|
||||
{...eventsViewerDefaultProps}
|
||||
graphEventId={undefined}
|
||||
headerFilterGroup={
|
||||
<AlertsTableFilterGroup status={'open'} onFilterGroupChanged={jest.fn()} />
|
||||
}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="alerts-table-filter-group"]`).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it has a visible HeaderFilterGroupWrapper when Resolver is NOT showing, because graphEventId is undefined', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer
|
||||
{...eventsViewerDefaultProps}
|
||||
graphEventId={undefined}
|
||||
headerFilterGroup={
|
||||
<AlertsTableFilterGroup status={'open'} onFilterGroupChanged={jest.fn()} />
|
||||
}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
|
||||
).not.toHaveStyleRule('visibility', 'hidden');
|
||||
});
|
||||
|
||||
test('it has a visible HeaderFilterGroupWrapper when Resolver is NOT showing, because graphEventId is an empty string', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer
|
||||
{...eventsViewerDefaultProps}
|
||||
graphEventId=""
|
||||
headerFilterGroup={
|
||||
<AlertsTableFilterGroup status={'open'} onFilterGroupChanged={jest.fn()} />
|
||||
}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
|
||||
).not.toHaveStyleRule('visibility', 'hidden');
|
||||
});
|
||||
|
||||
test('it does NOT have a visible HeaderFilterGroupWrapper when Resolver is showing, because graphEventId is a valid id', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer
|
||||
{...eventsViewerDefaultProps}
|
||||
graphEventId="a valid id"
|
||||
headerFilterGroup={
|
||||
<AlertsTableFilterGroup status={'open'} onFilterGroupChanged={jest.fn()} />
|
||||
}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(
|
||||
wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
|
||||
).toHaveStyleRule('visibility', 'hidden');
|
||||
});
|
||||
|
||||
test('it (still) renders an invisible headerFilterGroup (to maintain state while hidden) when Resolver is showing, because graphEventId is a valid id', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer
|
||||
{...eventsViewerDefaultProps}
|
||||
graphEventId="a valid id"
|
||||
headerFilterGroup={
|
||||
<AlertsTableFilterGroup status={'open'} onFilterGroupChanged={jest.fn()} />
|
||||
}
|
||||
/>
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="alerts-table-filter-group"]`).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('utilityBar', () => {
|
||||
beforeEach(() => {
|
||||
mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]);
|
||||
});
|
||||
|
||||
test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is undefined', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer {...eventsViewerDefaultProps} graphEventId={undefined} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is an empty string', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer {...eventsViewerDefaultProps} graphEventId="" />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it does NOT render the provided utilityBar when Resolver is showing, because graphEventId is a valid id', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer {...eventsViewerDefaultProps} graphEventId="a valid id" />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('header inspect button', () => {
|
||||
beforeEach(() => {
|
||||
mockUseTimelineEvents.mockReturnValue([false, mockEventViewerResponse]);
|
||||
});
|
||||
|
||||
test('it renders the inspect button when Resolver is NOT showing, because graphEventId is undefined', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer {...eventsViewerDefaultProps} graphEventId={undefined} />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders the inspect button when Resolver is NOT showing, because graphEventId is an empty string', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer {...eventsViewerDefaultProps} graphEventId="" />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(true);
|
||||
});
|
||||
|
||||
test('it does NOT render the inspect button when Resolver is showing, because graphEventId is a valid id', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<EventsViewer {...eventsViewerDefaultProps} graphEventId="a valid id" />
|
||||
</TestProviders>
|
||||
);
|
||||
expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,395 +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 { isEmpty } from 'lodash/fp';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { DataViewBase, Filter, Query } from '@kbn/es-query';
|
||||
import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { Direction } from '../../../../common/search_strategy';
|
||||
import { BrowserFields, DocValueFields } from '../../containers/source';
|
||||
import { useTimelineEvents } from '../../../timelines/containers';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { KqlMode } from '../../../timelines/store/timeline/model';
|
||||
import { HeaderSection } from '../header_section';
|
||||
import { defaultHeaders } from '../../../timelines/components/timeline/body/column_headers/default_headers';
|
||||
import { Sort } from '../../../timelines/components/timeline/body/sort';
|
||||
import { StatefulBody } from '../../../timelines/components/timeline/body';
|
||||
import { DataProvider } from '../../../timelines/components/timeline/data_providers/data_provider';
|
||||
import { Footer, footerHeight } from '../../../timelines/components/timeline/footer';
|
||||
import {
|
||||
calculateTotalPages,
|
||||
combineQueries,
|
||||
resolverIsShowing,
|
||||
} from '../../../timelines/components/timeline/helpers';
|
||||
import { TimelineRefetch } from '../../../timelines/components/timeline/refetch_timeline';
|
||||
import { EventDetailsWidthProvider } from './event_details_width_context';
|
||||
import * as i18n from './translations';
|
||||
import { esQuery } from '../../../../../../../src/plugins/data/public';
|
||||
import { inputsModel } from '../../store';
|
||||
import { ExitFullScreen } from '../exit_full_screen';
|
||||
import { useGlobalFullScreen } from '../../containers/use_full_screen';
|
||||
import {
|
||||
ColumnHeaderOptions,
|
||||
ControlColumnProps,
|
||||
RowRenderer,
|
||||
TimelineId,
|
||||
TimelineTabs,
|
||||
} from '../../../../common/types/timeline';
|
||||
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
|
||||
import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
|
||||
import { SELECTOR_TIMELINE_GLOBAL_CONTAINER } from '../../../timelines/components/timeline/styles';
|
||||
import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline';
|
||||
import { useDeepEqualSelector } from '../../hooks/use_selector';
|
||||
import { defaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
|
||||
import { TimelineContext } from '../../../../../timelines/public';
|
||||
|
||||
export const EVENTS_VIEWER_HEADER_HEIGHT = 90; // px
|
||||
const UTILITY_BAR_HEIGHT = 19; // px
|
||||
const COMPACT_HEADER_HEIGHT = EVENTS_VIEWER_HEADER_HEIGHT - UTILITY_BAR_HEIGHT; // px
|
||||
|
||||
const UtilityBar = styled.div`
|
||||
height: ${UTILITY_BAR_HEIGHT}px;
|
||||
`;
|
||||
|
||||
const TitleText = styled.span`
|
||||
margin-right: 12px;
|
||||
`;
|
||||
|
||||
const StyledEuiPanel = styled(EuiPanel)<{ $isFullScreen: boolean }>`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
${({ $isFullScreen }) =>
|
||||
$isFullScreen &&
|
||||
`
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
`}
|
||||
`;
|
||||
|
||||
const TitleFlexGroup = styled(EuiFlexGroup)`
|
||||
margin-top: 8px;
|
||||
`;
|
||||
|
||||
const EventsContainerLoading = styled.div.attrs(({ className = '' }) => ({
|
||||
className: `${SELECTOR_TIMELINE_GLOBAL_CONTAINER} ${className}`,
|
||||
}))`
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`;
|
||||
|
||||
const FullWidthFlexGroup = styled(EuiFlexGroup)<{ $visible: boolean }>`
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
display: ${({ $visible }) => ($visible ? 'flex' : 'none')};
|
||||
`;
|
||||
|
||||
const ScrollableFlexItem = styled(EuiFlexItem)`
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Hides stateful headerFilterGroup implementations, but prevents the component
|
||||
* from being unmounted, to preserve the state of the component
|
||||
*/
|
||||
const HeaderFilterGroupWrapper = styled.header<{ show: boolean }>`
|
||||
${({ show }) => (show ? '' : 'visibility: hidden;')}
|
||||
`;
|
||||
|
||||
interface Props {
|
||||
browserFields: BrowserFields;
|
||||
columns: ColumnHeaderOptions[];
|
||||
dataProviders: DataProvider[];
|
||||
deletedEventIds: Readonly<string[]>;
|
||||
docValueFields: DocValueFields[];
|
||||
end: string;
|
||||
filters: Filter[];
|
||||
headerFilterGroup?: React.ReactNode;
|
||||
id: TimelineId;
|
||||
indexNames: string[];
|
||||
indexPattern: DataViewBase;
|
||||
isLive: boolean;
|
||||
isLoadingIndexPattern: boolean;
|
||||
itemsPerPage: number;
|
||||
itemsPerPageOptions: number[];
|
||||
kqlMode: KqlMode;
|
||||
query: Query;
|
||||
onRuleChange?: () => void;
|
||||
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
|
||||
rowRenderers: RowRenderer[];
|
||||
runtimeMappings: MappingRuntimeFields;
|
||||
start: string;
|
||||
sort: Sort[];
|
||||
showTotalCount?: boolean;
|
||||
utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode;
|
||||
// If truthy, the graph viewer (Resolver) is showing
|
||||
graphEventId: string | undefined;
|
||||
}
|
||||
|
||||
const EventsViewerComponent: React.FC<Props> = ({
|
||||
browserFields,
|
||||
columns,
|
||||
dataProviders,
|
||||
deletedEventIds,
|
||||
docValueFields,
|
||||
end,
|
||||
filters,
|
||||
headerFilterGroup,
|
||||
id,
|
||||
indexNames,
|
||||
indexPattern,
|
||||
isLive,
|
||||
isLoadingIndexPattern,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
onRuleChange,
|
||||
query,
|
||||
renderCellValue,
|
||||
rowRenderers,
|
||||
runtimeMappings,
|
||||
start,
|
||||
sort,
|
||||
showTotalCount = true,
|
||||
utilityBar,
|
||||
graphEventId,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const { globalFullScreen, setGlobalFullScreen } = useGlobalFullScreen();
|
||||
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
|
||||
const kibana = useKibana();
|
||||
const [isQueryLoading, setIsQueryLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(timelineActions.updateIsLoading({ id, isLoading: isQueryLoading }));
|
||||
}, [dispatch, id, isQueryLoading]);
|
||||
|
||||
const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
|
||||
const unit = useMemo(() => (n: number) => i18n.UNIT(n), []);
|
||||
const { queryFields, title } = useDeepEqualSelector((state) => getManageTimeline(state, id));
|
||||
|
||||
const justTitle = useMemo(() => <TitleText data-test-subj="title">{title}</TitleText>, [title]);
|
||||
|
||||
const titleWithExitFullScreen = useMemo(
|
||||
() => (
|
||||
<TitleFlexGroup alignItems="center" data-test-subj="title-flex-group" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>{justTitle}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ExitFullScreen fullScreen={globalFullScreen} setFullScreen={setGlobalFullScreen} />
|
||||
</EuiFlexItem>
|
||||
</TitleFlexGroup>
|
||||
),
|
||||
[globalFullScreen, justTitle, setGlobalFullScreen]
|
||||
);
|
||||
|
||||
const combinedQueries = combineQueries({
|
||||
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
|
||||
dataProviders,
|
||||
indexPattern,
|
||||
browserFields,
|
||||
filters,
|
||||
kqlQuery: query,
|
||||
kqlMode,
|
||||
isEventViewer: true,
|
||||
});
|
||||
|
||||
const canQueryTimeline = useMemo(
|
||||
() =>
|
||||
combinedQueries != null &&
|
||||
isLoadingIndexPattern != null &&
|
||||
!isLoadingIndexPattern &&
|
||||
!isEmpty(start) &&
|
||||
!isEmpty(end),
|
||||
[isLoadingIndexPattern, combinedQueries, start, end]
|
||||
);
|
||||
|
||||
const fields = useMemo(
|
||||
() => [...columnsHeader.map((c) => c.id), ...(queryFields ?? [])],
|
||||
[columnsHeader, queryFields]
|
||||
);
|
||||
|
||||
const sortField = useMemo(
|
||||
() =>
|
||||
sort.map(({ columnId, columnType, sortDirection }) => ({
|
||||
field: columnId,
|
||||
type: columnType,
|
||||
direction: sortDirection as Direction,
|
||||
})),
|
||||
[sort]
|
||||
);
|
||||
|
||||
const [loading, { events, updatedAt, inspect, loadPage, pageInfo, refetch, totalCount = 0 }] =
|
||||
useTimelineEvents({
|
||||
docValueFields,
|
||||
fields,
|
||||
filterQuery: combinedQueries?.filterQuery,
|
||||
id,
|
||||
indexNames,
|
||||
limit: itemsPerPage,
|
||||
runtimeMappings,
|
||||
sort: sortField,
|
||||
startDate: start,
|
||||
endDate: end,
|
||||
skip: !canQueryTimeline || combinedQueries?.filterQuery === undefined, // When the filterQuery comes back as undefined, it means an error has been thrown and the request should be skipped
|
||||
});
|
||||
|
||||
const totalCountMinusDeleted = useMemo(
|
||||
() => (totalCount > 0 ? totalCount - deletedEventIds.length : 0),
|
||||
[deletedEventIds.length, totalCount]
|
||||
);
|
||||
|
||||
const subtitle = useMemo(
|
||||
() =>
|
||||
showTotalCount
|
||||
? `${i18n.SHOWING}: ${totalCountMinusDeleted.toLocaleString()} ${unit(
|
||||
totalCountMinusDeleted
|
||||
)}`
|
||||
: null,
|
||||
[showTotalCount, totalCountMinusDeleted, unit]
|
||||
);
|
||||
|
||||
const nonDeletedEvents = useMemo(
|
||||
() => events.filter((e) => !deletedEventIds.includes(e._id)),
|
||||
[deletedEventIds, events]
|
||||
);
|
||||
|
||||
const HeaderSectionContent = useMemo(
|
||||
() =>
|
||||
headerFilterGroup && (
|
||||
<HeaderFilterGroupWrapper
|
||||
data-test-subj="header-filter-group-wrapper"
|
||||
show={!resolverIsShowing(graphEventId)}
|
||||
>
|
||||
{headerFilterGroup}
|
||||
</HeaderFilterGroupWrapper>
|
||||
),
|
||||
[graphEventId, headerFilterGroup]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setIsQueryLoading(loading);
|
||||
}, [loading]);
|
||||
|
||||
const leadingControlColumns: ControlColumnProps[] = [defaultControlColumn];
|
||||
const trailingControlColumns: ControlColumnProps[] = [];
|
||||
const timelineContext = useMemo(() => ({ timelineId: id }), [id]);
|
||||
return (
|
||||
<StyledEuiPanel
|
||||
data-test-subj="events-viewer-panel"
|
||||
$isFullScreen={globalFullScreen && id !== TimelineId.active}
|
||||
hasBorder
|
||||
>
|
||||
{canQueryTimeline ? (
|
||||
<EventDetailsWidthProvider>
|
||||
<>
|
||||
<HeaderSection
|
||||
id={!resolverIsShowing(graphEventId) ? id : undefined}
|
||||
height={headerFilterGroup ? COMPACT_HEADER_HEIGHT : EVENTS_VIEWER_HEADER_HEIGHT}
|
||||
subtitle={utilityBar ? undefined : subtitle}
|
||||
title={globalFullScreen ? titleWithExitFullScreen : justTitle}
|
||||
isInspectDisabled={combinedQueries?.filterQuery === undefined}
|
||||
>
|
||||
{HeaderSectionContent}
|
||||
</HeaderSection>
|
||||
{utilityBar && !resolverIsShowing(graphEventId) && (
|
||||
<UtilityBar>{utilityBar?.(refetch, totalCountMinusDeleted)}</UtilityBar>
|
||||
)}
|
||||
<TimelineContext.Provider value={timelineContext}>
|
||||
<EventsContainerLoading
|
||||
data-timeline-id={id}
|
||||
data-test-subj={`events-container-loading-${loading}`}
|
||||
>
|
||||
<TimelineRefetch
|
||||
id={id}
|
||||
inputId="global"
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
refetch={refetch}
|
||||
/>
|
||||
|
||||
{graphEventId && <GraphOverlay timelineId={id} />}
|
||||
<FullWidthFlexGroup $visible={!graphEventId} gutterSize="none">
|
||||
<ScrollableFlexItem grow={1}>
|
||||
<StatefulBody
|
||||
activePage={pageInfo.activePage}
|
||||
browserFields={browserFields}
|
||||
data={nonDeletedEvents}
|
||||
id={id}
|
||||
isEventViewer={true}
|
||||
onRuleChange={onRuleChange}
|
||||
refetch={refetch}
|
||||
renderCellValue={renderCellValue}
|
||||
rowRenderers={rowRenderers}
|
||||
sort={sort}
|
||||
tabType={TimelineTabs.query}
|
||||
totalPages={calculateTotalPages({
|
||||
itemsCount: totalCountMinusDeleted,
|
||||
itemsPerPage,
|
||||
})}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
trailingControlColumns={trailingControlColumns}
|
||||
/>
|
||||
<Footer
|
||||
activePage={pageInfo.activePage}
|
||||
data-test-subj="events-viewer-footer"
|
||||
updatedAt={updatedAt}
|
||||
height={footerHeight}
|
||||
id={id}
|
||||
isLive={isLive}
|
||||
isLoading={loading}
|
||||
itemsCount={nonDeletedEvents.length}
|
||||
itemsPerPage={itemsPerPage}
|
||||
itemsPerPageOptions={itemsPerPageOptions}
|
||||
onChangePage={loadPage}
|
||||
totalCount={totalCountMinusDeleted}
|
||||
/>
|
||||
</ScrollableFlexItem>
|
||||
</FullWidthFlexGroup>
|
||||
</EventsContainerLoading>
|
||||
</TimelineContext.Provider>
|
||||
</>
|
||||
</EventDetailsWidthProvider>
|
||||
) : null}
|
||||
</StyledEuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export const EventsViewer = React.memo(
|
||||
EventsViewerComponent,
|
||||
// eslint-disable-next-line complexity
|
||||
(prevProps, nextProps) =>
|
||||
deepEqual(prevProps.browserFields, nextProps.browserFields) &&
|
||||
prevProps.columns === nextProps.columns &&
|
||||
deepEqual(prevProps.docValueFields, nextProps.docValueFields) &&
|
||||
prevProps.dataProviders === nextProps.dataProviders &&
|
||||
prevProps.deletedEventIds === nextProps.deletedEventIds &&
|
||||
prevProps.end === nextProps.end &&
|
||||
deepEqual(prevProps.filters, nextProps.filters) &&
|
||||
prevProps.headerFilterGroup === nextProps.headerFilterGroup &&
|
||||
prevProps.id === nextProps.id &&
|
||||
deepEqual(prevProps.indexPattern, nextProps.indexPattern) &&
|
||||
prevProps.isLive === nextProps.isLive &&
|
||||
prevProps.itemsPerPage === nextProps.itemsPerPage &&
|
||||
prevProps.itemsPerPageOptions === nextProps.itemsPerPageOptions &&
|
||||
prevProps.kqlMode === nextProps.kqlMode &&
|
||||
deepEqual(prevProps.query, nextProps.query) &&
|
||||
prevProps.renderCellValue === nextProps.renderCellValue &&
|
||||
prevProps.rowRenderers === nextProps.rowRenderers &&
|
||||
prevProps.start === nextProps.start &&
|
||||
deepEqual(prevProps.sort, nextProps.sort) &&
|
||||
prevProps.utilityBar === nextProps.utilityBar &&
|
||||
prevProps.graphEventId === nextProps.graphEventId
|
||||
);
|
|
@ -21,6 +21,7 @@ import { TimelineId } from '../../../../common/types/timeline';
|
|||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
|
||||
import { useTimelineEvents } from '../../../timelines/containers';
|
||||
import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
|
||||
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
|
||||
import { defaultCellActions } from '../../lib/cell_actions/default_cell_actions';
|
||||
|
||||
|
@ -38,6 +39,7 @@ mockUseResizeObserver.mockImplementation(() => ({}));
|
|||
|
||||
const from = '2019-08-27T22:10:56.794Z';
|
||||
const to = '2019-08-26T22:10:56.791Z';
|
||||
const ACTION_BUTTON_COUNT = 4;
|
||||
|
||||
const testProps = {
|
||||
defaultCellActions,
|
||||
|
@ -46,6 +48,7 @@ const testProps = {
|
|||
entityType: EntityType.ALERTS,
|
||||
indexNames: [],
|
||||
id: TimelineId.test,
|
||||
leadingControlColumns: getDefaultControlColumn(ACTION_BUTTON_COUNT),
|
||||
renderCellValue: DefaultCellRenderer,
|
||||
rowRenderers: defaultRowRenderers,
|
||||
scopeId: SourcererScopeName.default,
|
||||
|
|
|
@ -9,8 +9,6 @@ import React, { useCallback, useMemo, useEffect } from 'react';
|
|||
import { connect, ConnectedProps, useDispatch } from 'react-redux';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { inputsModel, inputsSelectors, State } from '../../store';
|
||||
import { inputsActions } from '../../store/actions';
|
||||
import { ControlColumnProps, RowRenderer, TimelineId } from '../../../../common/types/timeline';
|
||||
|
@ -28,18 +26,9 @@ import { TGridCellAction } from '../../../../../timelines/common/types';
|
|||
import { DetailsPanel } from '../../../timelines/components/side_panel';
|
||||
import { CellValueElementProps } from '../../../timelines/components/timeline/cell_rendering';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { defaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
|
||||
import { EventsViewer } from './events_viewer';
|
||||
import * as i18n from './translations';
|
||||
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
|
||||
|
||||
const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = [];
|
||||
const leadingControlColumns: ControlColumnProps[] = [
|
||||
{
|
||||
...defaultControlColumn,
|
||||
headerCellRender: () => <>{i18n.ACTIONS}</>,
|
||||
},
|
||||
];
|
||||
|
||||
const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>`
|
||||
height: ${({ $isFullScreen }) => ($isFullScreen ? '100%' : undefined)};
|
||||
|
@ -54,6 +43,7 @@ export interface OwnProps {
|
|||
end: string;
|
||||
entityType: EntityType;
|
||||
id: TimelineId;
|
||||
leadingControlColumns: ControlColumnProps[];
|
||||
scopeId: SourcererScopeName;
|
||||
start: string;
|
||||
showTotalCount?: boolean;
|
||||
|
@ -93,6 +83,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
|
|||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
leadingControlColumns,
|
||||
pageFilters,
|
||||
currentFilter,
|
||||
onRuleChange,
|
||||
|
@ -124,8 +115,6 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
|
|||
} = useSourcererDataView(scopeId);
|
||||
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
// TODO: Once we are past experimental phase this code should be removed
|
||||
const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled');
|
||||
const tGridEventRenderedViewEnabled = useIsExperimentalFeatureEnabled(
|
||||
'tGridEventRenderedViewEnabled'
|
||||
);
|
||||
|
@ -179,75 +168,45 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
|
|||
<>
|
||||
<FullScreenContainer $isFullScreen={globalFullScreen}>
|
||||
<InspectButtonContainer>
|
||||
{tGridEnabled ? (
|
||||
timelinesUi.getTGrid<'embedded'>({
|
||||
additionalFilters,
|
||||
browserFields,
|
||||
bulkActions,
|
||||
columns,
|
||||
dataProviders,
|
||||
defaultCellActions,
|
||||
deletedEventIds,
|
||||
docValueFields,
|
||||
end,
|
||||
entityType,
|
||||
filters: globalFilters,
|
||||
filterStatus: currentFilter,
|
||||
globalFullScreen,
|
||||
graphEventId,
|
||||
graphOverlay,
|
||||
hasAlertsCrud,
|
||||
id,
|
||||
indexNames: selectedPatterns,
|
||||
indexPattern,
|
||||
isLive,
|
||||
isLoadingIndexPattern,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
leadingControlColumns,
|
||||
onRuleChange,
|
||||
query,
|
||||
renderCellValue,
|
||||
rowRenderers,
|
||||
runtimeMappings,
|
||||
setQuery,
|
||||
sort,
|
||||
start,
|
||||
tGridEventRenderedViewEnabled,
|
||||
trailingControlColumns,
|
||||
type: 'embedded',
|
||||
unit,
|
||||
})
|
||||
) : (
|
||||
<EventsViewer
|
||||
browserFields={browserFields}
|
||||
columns={columns}
|
||||
docValueFields={docValueFields}
|
||||
id={id}
|
||||
dataProviders={dataProviders}
|
||||
deletedEventIds={deletedEventIds}
|
||||
end={end}
|
||||
isLoadingIndexPattern={isLoadingIndexPattern}
|
||||
filters={globalFilters}
|
||||
indexNames={selectedPatterns}
|
||||
indexPattern={indexPattern}
|
||||
isLive={isLive}
|
||||
itemsPerPage={itemsPerPage}
|
||||
itemsPerPageOptions={itemsPerPageOptions}
|
||||
kqlMode={kqlMode}
|
||||
query={query}
|
||||
onRuleChange={onRuleChange}
|
||||
renderCellValue={renderCellValue}
|
||||
rowRenderers={rowRenderers}
|
||||
runtimeMappings={runtimeMappings}
|
||||
start={start}
|
||||
sort={sort}
|
||||
showTotalCount={isEmpty(graphEventId) ? true : false}
|
||||
utilityBar={utilityBar}
|
||||
graphEventId={graphEventId}
|
||||
/>
|
||||
)}
|
||||
{timelinesUi.getTGrid<'embedded'>({
|
||||
additionalFilters,
|
||||
browserFields,
|
||||
bulkActions,
|
||||
columns,
|
||||
dataProviders,
|
||||
defaultCellActions,
|
||||
deletedEventIds,
|
||||
docValueFields,
|
||||
end,
|
||||
entityType,
|
||||
filters: globalFilters,
|
||||
filterStatus: currentFilter,
|
||||
globalFullScreen,
|
||||
graphEventId,
|
||||
graphOverlay,
|
||||
hasAlertsCrud,
|
||||
id,
|
||||
indexNames: selectedPatterns,
|
||||
indexPattern,
|
||||
isLive,
|
||||
isLoadingIndexPattern,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
leadingControlColumns,
|
||||
onRuleChange,
|
||||
query,
|
||||
renderCellValue,
|
||||
rowRenderers,
|
||||
runtimeMappings,
|
||||
setQuery,
|
||||
sort,
|
||||
start,
|
||||
tGridEventRenderedViewEnabled,
|
||||
trailingControlColumns,
|
||||
type: 'embedded',
|
||||
unit,
|
||||
})}
|
||||
</InspectButtonContainer>
|
||||
</FullScreenContainer>
|
||||
<DetailsPanel
|
||||
|
@ -338,6 +297,7 @@ export const StatefulEventsViewer = connector(
|
|||
prevProps.itemsPerPage === nextProps.itemsPerPage &&
|
||||
deepEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) &&
|
||||
prevProps.kqlMode === nextProps.kqlMode &&
|
||||
prevProps.leadingControlColumns === nextProps.leadingControlColumns &&
|
||||
deepEqual(prevProps.query, nextProps.query) &&
|
||||
prevProps.renderCellValue === nextProps.renderCellValue &&
|
||||
prevProps.rowRenderers === nextProps.rowRenderers &&
|
||||
|
|
|
@ -28,6 +28,7 @@ import { inputsModel, inputsSelectors, State } from '../../../common/store';
|
|||
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
|
||||
import * as i18nCommon from '../../../common/translations';
|
||||
import { DEFAULT_COLUMN_MIN_WIDTH } from '../../../timelines/components/timeline/body/constants';
|
||||
import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
|
||||
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
|
||||
import { combineQueries } from '../../../timelines/components/timeline/helpers';
|
||||
import { timelineActions, timelineSelectors } from '../../../timelines/store/timeline';
|
||||
|
@ -106,6 +107,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
const { addWarning } = useAppToasts();
|
||||
// TODO: Once we are past experimental phase this code should be removed
|
||||
const ruleRegistryEnabled = useIsExperimentalFeatureEnabled('ruleRegistryEnabled');
|
||||
const ACTION_BUTTON_COUNT = 4;
|
||||
|
||||
const getGlobalQuery = useCallback(
|
||||
(customFilters: Filter[]) => {
|
||||
|
@ -369,6 +371,8 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
);
|
||||
}, [dispatch, defaultTimelineModel, filterManager, tGridEnabled, timelineId]);
|
||||
|
||||
const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []);
|
||||
|
||||
if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) {
|
||||
return null;
|
||||
}
|
||||
|
@ -383,6 +387,7 @@ export const AlertsTableComponent: React.FC<AlertsTableComponentProps> = ({
|
|||
entityType="events"
|
||||
hasAlertsCrud={hasIndexWrite && hasIndexMaintenance}
|
||||
id={timelineId}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
onRuleChange={onRuleChange}
|
||||
pageFilters={defaultFiltersMemo}
|
||||
renderCellValue={RenderCellValue}
|
||||
|
|
|
@ -15,7 +15,7 @@ import { get } from 'lodash/fp';
|
|||
import { useRouteSpy } from '../../../../common/utils/route/use_route_spy';
|
||||
import { buildGetAlertByIdQuery } from '../../../../common/components/exceptions/helpers';
|
||||
import { EventsTdContent } from '../../../../timelines/components/timeline/styles';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../../../timelines/components/timeline/helpers';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH } from '../../../../../../timelines/public';
|
||||
import { Ecs } from '../../../../../common/ecs';
|
||||
import {
|
||||
AddExceptionModal,
|
||||
|
@ -185,7 +185,7 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps & PropsFromRedux
|
|||
{addToCaseActionProps && timelinesUi.getAddToCaseAction(addToCaseActionProps)}
|
||||
{items.length > 0 && (
|
||||
<div key="actions-context-menu">
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
<EuiPopover
|
||||
id="singlePanel"
|
||||
button={button}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
|
@ -21,6 +21,7 @@ import { MatrixHistogram } from '../../../common/components/matrix_histogram';
|
|||
import { useGlobalFullScreen } from '../../../common/containers/use_full_screen';
|
||||
import * as i18n from '../translations';
|
||||
import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution';
|
||||
import { getDefaultControlColumn } from '../../../timelines/components/timeline/body/control_columns';
|
||||
import { defaultRowRenderers } from '../../../timelines/components/timeline/body/renderers';
|
||||
import { DefaultCellRenderer } from '../../../timelines/components/timeline/cell_rendering/default_cell_renderer';
|
||||
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
|
||||
|
@ -69,7 +70,7 @@ const EventsQueryTabBodyComponent: React.FC<HostsComponentsQueryProps> = ({
|
|||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const { globalFullScreen } = useGlobalFullScreen();
|
||||
|
||||
const ACTION_BUTTON_COUNT = 3;
|
||||
const tGridEnabled = useIsExperimentalFeatureEnabled('tGridEnabled');
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -96,6 +97,8 @@ const EventsQueryTabBodyComponent: React.FC<HostsComponentsQueryProps> = ({
|
|||
};
|
||||
}, [deleteQuery]);
|
||||
|
||||
const leadingControlColumns = useMemo(() => getDefaultControlColumn(ACTION_BUTTON_COUNT), []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!globalFullScreen && (
|
||||
|
@ -115,6 +118,7 @@ const EventsQueryTabBodyComponent: React.FC<HostsComponentsQueryProps> = ({
|
|||
end={endDate}
|
||||
entityType="events"
|
||||
id={TimelineId.hostsPageEvents}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
pageFilters={pageFilters}
|
||||
renderCellValue={DefaultCellRenderer}
|
||||
rowRenderers={defaultRowRenderers}
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { MouseEvent } from 'react';
|
|||
import { EuiContextMenuItem, EuiButtonIcon, EuiToolTip, EuiText } from '@elastic/eui';
|
||||
|
||||
import { EventsTdContent } from '../../styles';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH } from '../../../../../../../timelines/public';
|
||||
|
||||
interface ActionIconItemProps {
|
||||
ariaLabel?: string;
|
||||
|
@ -24,7 +24,7 @@ interface ActionIconItemProps {
|
|||
}
|
||||
|
||||
const ActionIconItemComponent: React.FC<ActionIconItemProps> = ({
|
||||
width = DEFAULT_ICON_BUTTON_WIDTH,
|
||||
width = DEFAULT_ACTION_BUTTON_WIDTH,
|
||||
dataTestSubj,
|
||||
content,
|
||||
ariaLabel,
|
||||
|
@ -46,6 +46,7 @@ const ActionIconItemComponent: React.FC<ActionIconItemProps> = ({
|
|||
iconType={iconType}
|
||||
isDisabled={isDisabled}
|
||||
onClick={onClick}
|
||||
size="s"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
)}
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
useGlobalFullScreen,
|
||||
useTimelineFullScreen,
|
||||
} from '../../../../../common/containers/use_full_screen';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH } from '../../../../../../../timelines/public';
|
||||
import { StatefulRowRenderersBrowser } from '../../../row_renderers_browser';
|
||||
import { EventsTh, EventsThContent } from '../../styles';
|
||||
import { EventsSelect } from '../column_headers/events_select';
|
||||
|
@ -166,7 +166,7 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = ({
|
|||
<ActionsContainer>
|
||||
{showSelectAllCheckbox && (
|
||||
<EventsTh role="checkbox">
|
||||
<EventsThContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
<EventsThContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
<EuiCheckbox
|
||||
data-test-subj="select-all-events"
|
||||
id={'select-all-events'}
|
||||
|
@ -195,7 +195,7 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = ({
|
|||
</EventsTh>
|
||||
|
||||
<EventsTh role="button">
|
||||
<EventsThContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
<EventsThContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
<EuiToolTip content={fullScreen ? EXIT_FULL_SCREEN : i18n.FULL_SCREEN}>
|
||||
<EuiButtonIcon
|
||||
aria-label={
|
||||
|
@ -218,7 +218,7 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = ({
|
|||
</EventsTh>
|
||||
{tabType !== TimelineTabs.eql && (
|
||||
<EventsTh role="button" data-test-subj="timeline-sorting-fields">
|
||||
<EventsThContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
<EventsThContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
<EuiToolTip content={i18n.SORT_FIELDS}>
|
||||
<SortingColumnsContainer>{ColumnSorting}</SortingColumnsContainer>
|
||||
</EuiToolTip>
|
||||
|
@ -228,7 +228,7 @@ const HeaderActionsComponent: React.FC<HeaderActionProps> = ({
|
|||
|
||||
{showEventsSelect && (
|
||||
<EventsTh role="button">
|
||||
<EventsThContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
<EventsThContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
<EventsSelect checkState="unchecked" timelineId={timelineId} />
|
||||
</EventsThContent>
|
||||
</EventsTh>
|
||||
|
|
|
@ -19,7 +19,7 @@ import { AddEventNoteAction } from './add_note_icon_item';
|
|||
import { PinEventAction } from './pin_event_action';
|
||||
import { EventsTdContent } from '../../styles';
|
||||
import * as i18n from '../translations';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH } from '../../../../../../../timelines/public';
|
||||
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import {
|
||||
setActiveTabTimeline,
|
||||
|
@ -136,7 +136,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
<ActionsContainer>
|
||||
{showCheckboxes && !tGridEnabled && (
|
||||
<div key="select-event-container" data-test-subj="select-event-container">
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
{loadingEventIds.includes(eventId) ? (
|
||||
<EuiLoadingSpinner size="m" data-test-subj="event-loader" />
|
||||
) : (
|
||||
|
@ -152,13 +152,14 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
</div>
|
||||
)}
|
||||
<div key="expand-event">
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
<EuiToolTip data-test-subj="expand-event-tool-tip" content={i18n.VIEW_DETAILS}>
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.VIEW_DETAILS_FOR_ROW({ ariaRowindex, columnValues })}
|
||||
data-test-subj="expand-event"
|
||||
iconType="expand"
|
||||
onClick={onEventDetailsPanelOpened}
|
||||
size="s"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EventsTdContent>
|
||||
|
@ -204,7 +205,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
/>
|
||||
{isDisabled === false ? (
|
||||
<div>
|
||||
<EventsTdContent textAlign="center" width={36}>
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
<EuiToolTip
|
||||
data-test-subj="view-in-analyzer-tool-tip"
|
||||
content={i18n.ACTION_INVESTIGATE_IN_RESOLVER}
|
||||
|
@ -217,6 +218,7 @@ const ActionsComponent: React.FC<ActionProps> = ({
|
|||
data-test-subj="view-in-analyzer"
|
||||
iconType="analyzeEvent"
|
||||
onClick={handleClick}
|
||||
size="s"
|
||||
/>
|
||||
</EuiToolTip>
|
||||
</EventsTdContent>
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { useMemo } from 'react';
|
|||
import { EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { EventsTdContent } from '../../styles';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../../helpers';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH } from '../../../../../../../timelines/public';
|
||||
import { eventHasNotes, getPinTooltip } from '../helpers';
|
||||
import { Pin } from '../../pin';
|
||||
import { TimelineType } from '../../../../../../common/types/timeline';
|
||||
|
@ -41,7 +41,7 @@ const PinEventActionComponent: React.FC<PinEventActionProps> = ({
|
|||
|
||||
return (
|
||||
<div key="timeline-action-pin-tool-tip">
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ICON_BUTTON_WIDTH}>
|
||||
<EventsTdContent textAlign="center" width={DEFAULT_ACTION_BUTTON_WIDTH}>
|
||||
<EuiToolTip data-test-subj="timeline-action-pin-tool-tip" content={tooltipContent}>
|
||||
<Pin
|
||||
ariaLabel={ariaLabel}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={72}
|
||||
actionsColumnWidth={124}
|
||||
browserFields={
|
||||
Object {
|
||||
"agent": Object {
|
||||
|
@ -522,7 +522,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
|
|||
"compare": null,
|
||||
"type": [Function],
|
||||
},
|
||||
"width": 140,
|
||||
"width": 124,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,13 +8,8 @@
|
|||
import { mockBrowserFields } from '../../../../../common/containers/source/mock';
|
||||
|
||||
import { defaultHeaders } from './default_headers';
|
||||
import { getActionsColumnWidth, getColumnWidthFromType, getColumnHeaders } from './helpers';
|
||||
import {
|
||||
DEFAULT_COLUMN_MIN_WIDTH,
|
||||
DEFAULT_DATE_COLUMN_MIN_WIDTH,
|
||||
MINIMUM_ACTIONS_COLUMN_WIDTH,
|
||||
SHOW_CHECK_BOXES_COLUMN_WIDTH,
|
||||
} from '../constants';
|
||||
import { getColumnWidthFromType, getColumnHeaders } from './helpers';
|
||||
import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../constants';
|
||||
import '../../../../../common/mock/match_media';
|
||||
|
||||
describe('helpers', () => {
|
||||
|
@ -28,28 +23,6 @@ describe('helpers', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getActionsColumnWidth', () => {
|
||||
test('returns the default actions column width when isEventViewer is false', () => {
|
||||
expect(getActionsColumnWidth(false)).toEqual(MINIMUM_ACTIONS_COLUMN_WIDTH);
|
||||
});
|
||||
|
||||
test('returns the minimum actions column width + checkbox width when isEventViewer is false and showCheckboxes is true', () => {
|
||||
expect(getActionsColumnWidth(false, true)).toEqual(
|
||||
MINIMUM_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
|
||||
);
|
||||
});
|
||||
|
||||
test('returns the minimum actions column width when isEventViewer is true', () => {
|
||||
expect(getActionsColumnWidth(true)).toEqual(MINIMUM_ACTIONS_COLUMN_WIDTH);
|
||||
});
|
||||
|
||||
test('returns the minimum actions column width + checkbox width when isEventViewer is true and showCheckboxes is true', () => {
|
||||
expect(getActionsColumnWidth(true, true)).toEqual(
|
||||
MINIMUM_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getColumnHeaders', () => {
|
||||
test('should return a full object of ColumnHeader from the default header', () => {
|
||||
const expectedData = [
|
||||
|
|
|
@ -9,14 +9,7 @@ import { get } from 'lodash/fp';
|
|||
import { ColumnHeaderOptions } from '../../../../../../common';
|
||||
|
||||
import { BrowserFields } from '../../../../../common/containers/source';
|
||||
import {
|
||||
DEFAULT_COLUMN_MIN_WIDTH,
|
||||
DEFAULT_DATE_COLUMN_MIN_WIDTH,
|
||||
SHOW_CHECK_BOXES_COLUMN_WIDTH,
|
||||
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH,
|
||||
DEFAULT_ACTIONS_COLUMN_WIDTH,
|
||||
MINIMUM_ACTIONS_COLUMN_WIDTH,
|
||||
} from '../constants';
|
||||
import { DEFAULT_COLUMN_MIN_WIDTH, DEFAULT_DATE_COLUMN_MIN_WIDTH } from '../constants';
|
||||
|
||||
/** Enriches the column headers with field details from the specified browserFields */
|
||||
export const getColumnHeaders = (
|
||||
|
@ -40,20 +33,3 @@ export const getColumnHeaders = (
|
|||
|
||||
export const getColumnWidthFromType = (type: string): number =>
|
||||
type !== 'date' ? DEFAULT_COLUMN_MIN_WIDTH : DEFAULT_DATE_COLUMN_MIN_WIDTH;
|
||||
|
||||
/** Returns the (fixed) width of the Actions column */
|
||||
export const getActionsColumnWidth = (
|
||||
isEventViewer: boolean,
|
||||
showCheckboxes = false,
|
||||
additionalActionWidth = 0
|
||||
): number => {
|
||||
const checkboxesWidth = showCheckboxes ? SHOW_CHECK_BOXES_COLUMN_WIDTH : 0;
|
||||
const actionsColumnWidth =
|
||||
checkboxesWidth +
|
||||
(isEventViewer ? EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH : DEFAULT_ACTIONS_COLUMN_WIDTH) +
|
||||
additionalActionWidth;
|
||||
|
||||
return actionsColumnWidth > MINIMUM_ACTIONS_COLUMN_WIDTH + checkboxesWidth
|
||||
? actionsColumnWidth
|
||||
: MINIMUM_ACTIONS_COLUMN_WIDTH + checkboxesWidth;
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ import { shallow } from 'enzyme';
|
|||
import React from 'react';
|
||||
|
||||
import '../../../../../common/mock/match_media';
|
||||
import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants';
|
||||
import { getActionsColumnWidth } from '../../../../../../../timelines/public';
|
||||
import { defaultHeaders } from './default_headers';
|
||||
import { mockBrowserFields } from '../../../../../common/containers/source/mock';
|
||||
import { Sort } from '../sort';
|
||||
|
@ -21,8 +21,9 @@ import { cloneDeep } from 'lodash/fp';
|
|||
import { timelineActions } from '../../../../store/timeline';
|
||||
import { TimelineTabs } from '../../../../../../common/types/timeline';
|
||||
import { Direction } from '../../../../../../common/search_strategy';
|
||||
import { defaultControlColumn } from '../control_columns';
|
||||
import { getDefaultControlColumn } from '../control_columns';
|
||||
import { testTrailingControlColumns } from '../../../../../common/mock/mock_timeline_control_columns';
|
||||
import { HeaderActions } from '../actions/header_actions';
|
||||
|
||||
jest.mock('../../../../../common/lib/kibana');
|
||||
|
||||
|
@ -39,6 +40,12 @@ const timelineId = 'test';
|
|||
|
||||
describe('ColumnHeaders', () => {
|
||||
const mount = useMountAppended();
|
||||
const ACTION_BUTTON_COUNT = 4;
|
||||
const actionsColumnWidth = getActionsColumnWidth(ACTION_BUTTON_COUNT);
|
||||
const leadingControlColumns = getDefaultControlColumn(ACTION_BUTTON_COUNT).map((x) => ({
|
||||
...x,
|
||||
headerCellRender: HeaderActions,
|
||||
}));
|
||||
|
||||
describe('rendering', () => {
|
||||
const sort: Sort[] = [
|
||||
|
@ -53,7 +60,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={defaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -63,7 +70,7 @@ describe('ColumnHeaders', () => {
|
|||
sort={sort}
|
||||
tabType={TimelineTabs.query}
|
||||
timelineId={timelineId}
|
||||
leadingControlColumns={[defaultControlColumn]}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
trailingControlColumns={[]}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -75,7 +82,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={defaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -85,7 +92,7 @@ describe('ColumnHeaders', () => {
|
|||
sort={sort}
|
||||
tabType={TimelineTabs.query}
|
||||
timelineId={timelineId}
|
||||
leadingControlColumns={[defaultControlColumn]}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
trailingControlColumns={[]}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -98,7 +105,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={defaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -108,7 +115,7 @@ describe('ColumnHeaders', () => {
|
|||
sort={sort}
|
||||
tabType={TimelineTabs.query}
|
||||
timelineId={timelineId}
|
||||
leadingControlColumns={[defaultControlColumn]}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
trailingControlColumns={[]}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -159,7 +166,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={mockDefaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -169,7 +176,7 @@ describe('ColumnHeaders', () => {
|
|||
sort={mockSort}
|
||||
tabType={TimelineTabs.query}
|
||||
timelineId={timelineId}
|
||||
leadingControlColumns={[defaultControlColumn]}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
trailingControlColumns={[]}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -203,7 +210,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={mockDefaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -213,7 +220,7 @@ describe('ColumnHeaders', () => {
|
|||
sort={mockSort}
|
||||
tabType={TimelineTabs.query}
|
||||
timelineId={timelineId}
|
||||
leadingControlColumns={[defaultControlColumn]}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
trailingControlColumns={[]}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -242,7 +249,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={mockDefaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -252,7 +259,7 @@ describe('ColumnHeaders', () => {
|
|||
sort={mockSort}
|
||||
tabType={TimelineTabs.query}
|
||||
timelineId={timelineId}
|
||||
leadingControlColumns={[defaultControlColumn]}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
trailingControlColumns={[]}
|
||||
/>
|
||||
</TestProviders>
|
||||
|
@ -280,7 +287,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={mockDefaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
|
|
@ -5,20 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
/** The minimum (fixed) width of the Actions column */
|
||||
export const MINIMUM_ACTIONS_COLUMN_WIDTH = 100; // px;
|
||||
|
||||
/** Additional column width to include when checkboxes are shown **/
|
||||
export const SHOW_CHECK_BOXES_COLUMN_WIDTH = 24; // px;
|
||||
|
||||
/** The (fixed) width of the Actions column */
|
||||
export const DEFAULT_ACTIONS_COLUMN_WIDTH = SHOW_CHECK_BOXES_COLUMN_WIDTH * 3; // px;
|
||||
/**
|
||||
* The (fixed) width of the Actions column when the timeline body is used as
|
||||
* an events viewer, which has fewer actions than a regular events viewer
|
||||
*/
|
||||
export const EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH = SHOW_CHECK_BOXES_COLUMN_WIDTH * 2; // px;
|
||||
|
||||
/** The default minimum width of a column (when a width for the column type is not specified) */
|
||||
export const DEFAULT_COLUMN_MIN_WIDTH = 180; // px
|
||||
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { getDefaultControlColumn } from '.';
|
||||
|
||||
describe('control columns', () => {
|
||||
describe('getDefaultControlColumn', () => {
|
||||
const ACTION_BUTTON_COUNT = 5;
|
||||
|
||||
test('it returns the expected defaults', () => {
|
||||
expect(getDefaultControlColumn(ACTION_BUTTON_COUNT)).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"headerCellRender": [Function],
|
||||
"id": "default-timeline-control-column",
|
||||
"rowCellRender": Object {
|
||||
"$$typeof": Symbol(react.memo),
|
||||
"compare": null,
|
||||
"type": [Function],
|
||||
},
|
||||
"width": 152,
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,15 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { ControlColumnProps } from '../../../../../../common/types/timeline';
|
||||
import { Actions } from '../actions';
|
||||
import { HeaderActions } from '../actions/header_actions';
|
||||
import { getActionsColumnWidth } from '../../../../../../../timelines/public';
|
||||
import * as i18n from '../../../../../common/components/events_viewer/translations';
|
||||
|
||||
const DEFAULT_CONTROL_COLUMN_WIDTH = 140;
|
||||
|
||||
export const defaultControlColumn: ControlColumnProps = {
|
||||
id: 'default-timeline-control-column',
|
||||
width: DEFAULT_CONTROL_COLUMN_WIDTH,
|
||||
headerCellRender: HeaderActions,
|
||||
rowCellRender: Actions,
|
||||
};
|
||||
export const getDefaultControlColumn = (actionButtonCount: number): ControlColumnProps[] => [
|
||||
{
|
||||
headerCellRender: () => <>{i18n.ACTIONS}</>,
|
||||
id: 'default-timeline-control-column',
|
||||
rowCellRender: Actions,
|
||||
width: getActionsColumnWidth(actionButtonCount),
|
||||
},
|
||||
];
|
||||
|
|
|
@ -13,12 +13,14 @@ import { DefaultCellRenderer } from '../../cell_rendering/default_cell_renderer'
|
|||
import '../../../../../common/mock/match_media';
|
||||
import { mockTimelineData } from '../../../../../common/mock';
|
||||
import { defaultHeaders } from '../column_headers/default_headers';
|
||||
import { defaultControlColumn } from '../control_columns';
|
||||
import { getDefaultControlColumn } from '../control_columns';
|
||||
|
||||
import { DataDrivenColumns } from '.';
|
||||
|
||||
describe('Columns', () => {
|
||||
const headersSansTimestamp = defaultHeaders.filter((h) => h.id !== '@timestamp');
|
||||
const ACTION_BUTTON_COUNT = 4;
|
||||
const leadingControlColumns = getDefaultControlColumn(ACTION_BUTTON_COUNT);
|
||||
|
||||
test('it renders the expected columns', () => {
|
||||
const wrapper = shallow(
|
||||
|
@ -45,7 +47,7 @@ describe('Columns', () => {
|
|||
toggleShowNotes={jest.fn()}
|
||||
refetch={jest.fn()}
|
||||
eventIdToNoteIds={{}}
|
||||
leadingControlColumns={[defaultControlColumn]}
|
||||
leadingControlColumns={leadingControlColumns}
|
||||
trailingControlColumns={[]}
|
||||
setEventsLoading={jest.fn()}
|
||||
setEventsDeleted={jest.fn()}
|
||||
|
|
|
@ -9,7 +9,6 @@ import { mount } from 'enzyme';
|
|||
import React from 'react';
|
||||
|
||||
import { TestProviders } from '../../../../../common/mock';
|
||||
import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
import { EventColumnView } from './event_column_view';
|
||||
|
@ -17,9 +16,10 @@ import { DefaultCellRenderer } from '../../cell_rendering/default_cell_renderer'
|
|||
import { TimelineTabs, TimelineType, TimelineId } from '../../../../../../common/types/timeline';
|
||||
import { useShallowEqualSelector } from '../../../../../common/hooks/use_selector';
|
||||
import { useIsExperimentalFeatureEnabled } from '../../../../../common/hooks/use_experimental_features';
|
||||
import { defaultControlColumn } from '../control_columns';
|
||||
import { getDefaultControlColumn } from '../control_columns';
|
||||
import { testLeadingControlColumn } from '../../../../../common/mock/mock_timeline_control_columns';
|
||||
import { mockTimelines } from '../../../../../common/mock/mock_timelines_plugin';
|
||||
import { getActionsColumnWidth } from '../../../../../../../timelines/public';
|
||||
|
||||
jest.mock('../../../../../common/hooks/use_experimental_features');
|
||||
const useIsExperimentalFeatureEnabledMock = useIsExperimentalFeatureEnabled as jest.Mock;
|
||||
|
@ -57,11 +57,13 @@ jest.mock(
|
|||
describe('EventColumnView', () => {
|
||||
useIsExperimentalFeatureEnabledMock.mockReturnValue(false);
|
||||
(useShallowEqualSelector as jest.Mock).mockReturnValue(TimelineType.default);
|
||||
const ACTION_BUTTON_COUNT = 4;
|
||||
const leadingControlColumns = getDefaultControlColumn(ACTION_BUTTON_COUNT);
|
||||
|
||||
const props = {
|
||||
ariaRowindex: 2,
|
||||
id: 'event-id',
|
||||
actionsColumnWidth: DEFAULT_ACTIONS_COLUMN_WIDTH,
|
||||
actionsColumnWidth: getActionsColumnWidth(ACTION_BUTTON_COUNT),
|
||||
associateNote: jest.fn(),
|
||||
columnHeaders: [],
|
||||
columnRenderers: [],
|
||||
|
@ -91,7 +93,7 @@ describe('EventColumnView', () => {
|
|||
toggleShowNotes: jest.fn(),
|
||||
updateNote: jest.fn(),
|
||||
isEventPinned: false,
|
||||
leadingControlColumns: [defaultControlColumn],
|
||||
leadingControlColumns,
|
||||
trailingControlColumns: [],
|
||||
setEventsLoading: jest.fn(),
|
||||
setEventsDeleted: jest.fn(),
|
||||
|
@ -145,7 +147,7 @@ describe('EventColumnView', () => {
|
|||
<EventColumnView
|
||||
{...props}
|
||||
timelineId="timeline-test"
|
||||
leadingControlColumns={[testLeadingControlColumn, defaultControlColumn]}
|
||||
leadingControlColumns={[testLeadingControlColumn, ...leadingControlColumns]}
|
||||
/>,
|
||||
{
|
||||
wrappingComponent: TestProviders,
|
||||
|
|
|
@ -19,7 +19,7 @@ import { useAppToastsMock } from '../../../../common/hooks/use_app_toasts.mock';
|
|||
|
||||
import { BodyComponent, StatefulBodyProps } from '.';
|
||||
import { Sort } from './sort';
|
||||
import { defaultControlColumn } from './control_columns';
|
||||
import { getDefaultControlColumn } from './control_columns';
|
||||
import { useMountAppended } from '../../../../common/utils/use_mount_appended';
|
||||
import { timelineActions } from '../../../store/timeline';
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
|
@ -119,6 +119,8 @@ describe('Body', () => {
|
|||
(useAppToasts as jest.Mock).mockReturnValue(appToastsMock);
|
||||
});
|
||||
|
||||
const ACTION_BUTTON_COUNT = 4;
|
||||
|
||||
const props: StatefulBodyProps = {
|
||||
activePage: 0,
|
||||
browserFields: mockBrowserFields,
|
||||
|
@ -140,7 +142,7 @@ describe('Body', () => {
|
|||
showCheckboxes: false,
|
||||
tabType: TimelineTabs.query,
|
||||
totalPages: 1,
|
||||
leadingControlColumns: [defaultControlColumn],
|
||||
leadingControlColumns: getDefaultControlColumn(ACTION_BUTTON_COUNT),
|
||||
trailingControlColumns: [],
|
||||
};
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
ARIA_COLINDEX_ATTRIBUTE,
|
||||
ARIA_ROWINDEX_ATTRIBUTE,
|
||||
onKeyDownFocusHandler,
|
||||
getActionsColumnWidth,
|
||||
} from '../../../../../../timelines/public';
|
||||
import { CellValueElementProps } from '../cell_rendering';
|
||||
import { DEFAULT_COLUMN_MIN_WIDTH } from './constants';
|
||||
|
@ -34,14 +35,13 @@ import { TimelineModel } from '../../../store/timeline/model';
|
|||
import { timelineDefaults } from '../../../store/timeline/defaults';
|
||||
import { timelineActions, timelineSelectors } from '../../../store/timeline';
|
||||
import { OnRowSelected, OnSelectAll } from '../events';
|
||||
import { getActionsColumnWidth, getColumnHeaders } from './column_headers/helpers';
|
||||
import { getColumnHeaders } from './column_headers/helpers';
|
||||
import { getEventIdToDataMapping } from './helpers';
|
||||
import { Sort } from './sort';
|
||||
import { plainRowRenderer } from './renderers/plain_row_renderer';
|
||||
import { EventsTable, TimelineBody, TimelineBodyGlobalStyle } from '../styles';
|
||||
import { ColumnHeaders } from './column_headers';
|
||||
import { Events } from './events';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
|
||||
interface OwnProps {
|
||||
|
@ -61,15 +61,11 @@ interface OwnProps {
|
|||
onRuleChange?: () => void;
|
||||
}
|
||||
|
||||
const NUM_OF_ICON_IN_TIMELINE_ROW = 2;
|
||||
|
||||
export const hasAdditionalActions = (id: TimelineId): boolean =>
|
||||
[TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage, TimelineId.active].includes(
|
||||
id
|
||||
);
|
||||
|
||||
const EXTRA_WIDTH = 4; // px
|
||||
|
||||
export type StatefulBodyProps = OwnProps & PropsFromRedux;
|
||||
|
||||
/**
|
||||
|
@ -108,6 +104,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
const { queryFields, selectAll } = useDeepEqualSelector((state) =>
|
||||
getManageTimeline(state, id)
|
||||
);
|
||||
const ACTION_BUTTON_COUNT = 5;
|
||||
|
||||
const onRowSelected: OnRowSelected = useCallback(
|
||||
({ eventIds, isSelected }: { eventIds: string[]; isSelected: boolean }) => {
|
||||
|
@ -158,17 +155,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
return rowRenderers.filter((rowRenderer) => !excludedRowRendererIds.includes(rowRenderer.id));
|
||||
}, [excludedRowRendererIds, rowRenderers]);
|
||||
|
||||
const actionsColumnWidth = useMemo(
|
||||
() =>
|
||||
getActionsColumnWidth(
|
||||
isEventViewer,
|
||||
showCheckboxes,
|
||||
hasAdditionalActions(id as TimelineId)
|
||||
? DEFAULT_ICON_BUTTON_WIDTH * NUM_OF_ICON_IN_TIMELINE_ROW + EXTRA_WIDTH
|
||||
: 0
|
||||
),
|
||||
[isEventViewer, showCheckboxes, id]
|
||||
);
|
||||
const actionsColumnWidth = useMemo(() => getActionsColumnWidth(ACTION_BUTTON_COUNT), []);
|
||||
|
||||
const columnWidths = useMemo(
|
||||
() =>
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
StyledContent,
|
||||
} from '../../../../common/lib/cell_actions/expanded_cell_value_actions';
|
||||
|
||||
const FIELDS_WITHOUT_CELL_ACTIONS = ['@timestamp', 'signal.rule.risk_score', 'signal.reason'];
|
||||
const FIELDS_WITHOUT_CELL_ACTIONS = ['signal.rule.risk_score', 'signal.reason'];
|
||||
const hasCellActions = (columnId?: string) => {
|
||||
return columnId && FIELDS_WITHOUT_CELL_ACTIONS.indexOf(columnId) < 0;
|
||||
};
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import React, { useEffect, useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Dispatch } from 'redux';
|
||||
import { connect, ConnectedProps, useDispatch } from 'react-redux';
|
||||
|
@ -54,7 +54,8 @@ import { useTimelineFullScreen } from '../../../../common/containers/use_full_sc
|
|||
import { activeTimeline } from '../../../containers/active_timeline_context';
|
||||
import { DetailsPanel } from '../../side_panel';
|
||||
import { EqlQueryBarTimeline } from '../query_bar/eql';
|
||||
import { defaultControlColumn } from '../body/control_columns';
|
||||
import { HeaderActions } from '../body/actions/header_actions';
|
||||
import { getDefaultControlColumn } from '../body/control_columns';
|
||||
import { Sort } from '../body/sort';
|
||||
import { Sourcerer } from '../../../../common/components/sourcerer';
|
||||
|
||||
|
@ -151,6 +152,8 @@ export type Props = OwnProps & PropsFromRedux;
|
|||
|
||||
const NO_SORTING: Sort[] = [];
|
||||
|
||||
const trailingControlColumns: ControlColumnProps[] = []; // stable reference
|
||||
|
||||
export const EqlTabContentComponent: React.FC<Props> = ({
|
||||
activeTab,
|
||||
columns,
|
||||
|
@ -181,6 +184,7 @@ export const EqlTabContentComponent: React.FC<Props> = ({
|
|||
runtimeMappings,
|
||||
selectedPatterns,
|
||||
} = useSourcererDataView(SourcererScopeName.timeline);
|
||||
const ACTION_BUTTON_COUNT = 5;
|
||||
|
||||
const isBlankTimeline: boolean = isEmpty(eqlQuery);
|
||||
|
||||
|
@ -244,8 +248,14 @@ export const EqlTabContentComponent: React.FC<Props> = ({
|
|||
);
|
||||
}, [loadingSourcerer, timelineId, isQueryLoading, dispatch]);
|
||||
|
||||
const leadingControlColumns: ControlColumnProps[] = [defaultControlColumn];
|
||||
const trailingControlColumns: ControlColumnProps[] = [];
|
||||
const leadingControlColumns = useMemo(
|
||||
() =>
|
||||
getDefaultControlColumn(ACTION_BUTTON_COUNT).map((x) => ({
|
||||
...x,
|
||||
headerCellRender: HeaderActions,
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -223,8 +223,6 @@ export const combineQueries = ({
|
|||
*/
|
||||
export const STATEFUL_EVENT_CSS_CLASS_NAME = 'event-column-view';
|
||||
|
||||
export const DEFAULT_ICON_BUTTON_WIDTH = 24;
|
||||
|
||||
export const resolverIsShowing = (graphEventId: string | undefined): boolean =>
|
||||
graphEventId != null && graphEventId !== '';
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonIcon, IconSize } from '@elastic/eui';
|
||||
import { EuiButtonIcon } from '@elastic/eui';
|
||||
import { noop } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
||||
|
@ -20,14 +20,13 @@ export const getPinIcon = (pinned: boolean): PinIcon => (pinned ? 'pinFilled' :
|
|||
interface Props {
|
||||
ariaLabel?: string;
|
||||
allowUnpinning: boolean;
|
||||
iconSize?: IconSize;
|
||||
timelineType?: TimelineTypeLiteral;
|
||||
onClick?: () => void;
|
||||
pinned: boolean;
|
||||
}
|
||||
|
||||
export const Pin = React.memo<Props>(
|
||||
({ ariaLabel, allowUnpinning, iconSize = 'm', onClick = noop, pinned, timelineType }) => {
|
||||
({ ariaLabel, allowUnpinning, onClick = noop, pinned, timelineType }) => {
|
||||
const isTemplate = timelineType === TimelineType.template;
|
||||
const defaultAriaLabel = isTemplate ? i18n.DISABLE_PIN : pinned ? i18n.PINNED : i18n.UNPINNED;
|
||||
const pinAriaLabel = ariaLabel != null ? ariaLabel : defaultAriaLabel;
|
||||
|
@ -36,10 +35,10 @@ export const Pin = React.memo<Props>(
|
|||
<EuiButtonIcon
|
||||
aria-label={pinAriaLabel}
|
||||
data-test-subj="pin"
|
||||
iconSize={iconSize}
|
||||
iconType={getPinIcon(pinned)}
|
||||
onClick={onClick}
|
||||
isDisabled={isTemplate || !allowUnpinning}
|
||||
size="s"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { connect, ConnectedProps } from 'react-redux';
|
|||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { timelineActions, timelineSelectors } from '../../../store/timeline';
|
||||
import { HeaderActions } from '../body/actions/header_actions';
|
||||
import { CellValueElementProps } from '../cell_rendering';
|
||||
import { Direction } from '../../../../../common/search_strategy';
|
||||
import { useTimelineEvents } from '../../../containers/index';
|
||||
|
@ -37,7 +38,7 @@ import {
|
|||
} from '../../../../../common/types/timeline';
|
||||
import { DetailsPanel } from '../../side_panel';
|
||||
import { ExitFullScreen } from '../../../../common/components/exit_full_screen';
|
||||
import { defaultControlColumn } from '../body/control_columns';
|
||||
import { getDefaultControlColumn } from '../body/control_columns';
|
||||
|
||||
const StyledEuiFlyoutBody = styled(EuiFlyoutBody)`
|
||||
overflow-y: hidden;
|
||||
|
@ -101,6 +102,8 @@ interface PinnedFilter {
|
|||
|
||||
export type Props = OwnProps & PropsFromRedux;
|
||||
|
||||
const trailingControlColumns: ControlColumnProps[] = []; // stable reference
|
||||
|
||||
export const PinnedTabContentComponent: React.FC<Props> = ({
|
||||
columns,
|
||||
timelineId,
|
||||
|
@ -121,6 +124,7 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
|
|||
selectedPatterns,
|
||||
} = useSourcererDataView(SourcererScopeName.timeline);
|
||||
const { setTimelineFullScreen, timelineFullScreen } = useTimelineFullScreen();
|
||||
const ACTION_BUTTON_COUNT = 5;
|
||||
|
||||
const filterQuery = useMemo(() => {
|
||||
if (isEmpty(pinnedEventIds)) {
|
||||
|
@ -197,8 +201,14 @@ export const PinnedTabContentComponent: React.FC<Props> = ({
|
|||
onEventClosed({ tabType: TimelineTabs.pinned, timelineId });
|
||||
}, [timelineId, onEventClosed]);
|
||||
|
||||
const leadingControlColumns: ControlColumnProps[] = [defaultControlColumn];
|
||||
const trailingControlColumns: ControlColumnProps[] = [];
|
||||
const leadingControlColumns = useMemo(
|
||||
() =>
|
||||
getDefaultControlColumn(ACTION_BUTTON_COUNT).map((x) => ({
|
||||
...x,
|
||||
headerCellRender: HeaderActions,
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -116,6 +116,7 @@ const SmallNotesButton = React.memo<SmallNotesButtonProps>(
|
|||
data-test-subj="timeline-notes-button-small"
|
||||
iconType="editorComment"
|
||||
onClick={toggleShowNotes}
|
||||
size="s"
|
||||
isDisabled={isTemplate}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -58,7 +58,8 @@ import { useTimelineFullScreen } from '../../../../common/containers/use_full_sc
|
|||
import { activeTimeline } from '../../../containers/active_timeline_context';
|
||||
import { DetailsPanel } from '../../side_panel';
|
||||
import { ExitFullScreen } from '../../../../common/components/exit_full_screen';
|
||||
import { defaultControlColumn } from '../body/control_columns';
|
||||
import { HeaderActions } from '../body/actions/header_actions';
|
||||
import { getDefaultControlColumn } from '../body/control_columns';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import { Sourcerer } from '../../../../common/components/sourcerer';
|
||||
|
||||
|
@ -156,6 +157,8 @@ const EMPTY_EVENTS: TimelineItem[] = [];
|
|||
|
||||
export type Props = OwnProps & PropsFromRedux;
|
||||
|
||||
const trailingControlColumns: ControlColumnProps[] = []; // stable reference
|
||||
|
||||
export const QueryTabContentComponent: React.FC<Props> = ({
|
||||
activeTab,
|
||||
columns,
|
||||
|
@ -194,6 +197,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({
|
|||
selectedPatterns,
|
||||
} = useSourcererDataView(SourcererScopeName.timeline);
|
||||
const { uiSettings } = useKibana().services;
|
||||
const ACTION_BUTTON_COUNT = 5;
|
||||
|
||||
const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
|
||||
const { filterManager: activeFilterManager } = useDeepEqualSelector((state) =>
|
||||
|
@ -314,8 +318,14 @@ export const QueryTabContentComponent: React.FC<Props> = ({
|
|||
return (combinedQueries && combinedQueries.kqlError != null) || false;
|
||||
}, [combinedQueries]);
|
||||
|
||||
const leadingControlColumns: ControlColumnProps[] = [defaultControlColumn];
|
||||
const trailingControlColumns: ControlColumnProps[] = [];
|
||||
const leadingControlColumns = useMemo(
|
||||
() =>
|
||||
getDefaultControlColumn(ACTION_BUTTON_COUNT).map((x) => ({
|
||||
...x,
|
||||
headerCellRender: HeaderActions,
|
||||
})),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -104,8 +104,6 @@ interface AdditionalControlColumnProps {
|
|||
// Override these type definitions to support either a generic custom component or the one used in security_solution today.
|
||||
headerCellRender: HeaderCellRender;
|
||||
rowCellRender: RowCellRender;
|
||||
// If not provided, calculated dynamically
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export type ControlColumnProps = Omit<
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { MouseEvent } from 'react';
|
|||
import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
|
||||
|
||||
import { EventsTdContent } from '../t_grid/styles';
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../t_grid/helpers';
|
||||
import { DEFAULT_ACTION_BUTTON_WIDTH } from '../t_grid/body/constants';
|
||||
|
||||
interface ActionIconItemProps {
|
||||
ariaLabel?: string;
|
||||
|
@ -23,7 +23,7 @@ interface ActionIconItemProps {
|
|||
}
|
||||
|
||||
const ActionIconItemComponent: React.FC<ActionIconItemProps> = ({
|
||||
width = DEFAULT_ICON_BUTTON_WIDTH,
|
||||
width = DEFAULT_ACTION_BUTTON_WIDTH,
|
||||
dataTestSubj,
|
||||
content,
|
||||
ariaLabel,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = `
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={120}
|
||||
actionsColumnWidth={124}
|
||||
browserFields={
|
||||
Object {
|
||||
"agent": Object {
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme';
|
||||
import { mount } from 'enzyme';
|
||||
import { omit, set } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
@ -17,11 +18,9 @@ import {
|
|||
getSchema,
|
||||
} from './helpers';
|
||||
import {
|
||||
DEFAULT_ACTION_BUTTON_WIDTH,
|
||||
DEFAULT_COLUMN_MIN_WIDTH,
|
||||
DEFAULT_DATE_COLUMN_MIN_WIDTH,
|
||||
DEFAULT_ACTIONS_COLUMN_WIDTH,
|
||||
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH,
|
||||
SHOW_CHECK_BOXES_COLUMN_WIDTH,
|
||||
} from '../constants';
|
||||
import { mockBrowserFields } from '../../../../mock/browser_fields';
|
||||
import { ColumnHeaderOptions } from '../../../../../common';
|
||||
|
@ -47,28 +46,6 @@ describe('helpers', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getActionsColumnWidth', () => {
|
||||
test('returns the default actions column width when isEventViewer is false', () => {
|
||||
expect(getActionsColumnWidth(false)).toEqual(DEFAULT_ACTIONS_COLUMN_WIDTH);
|
||||
});
|
||||
|
||||
test('returns the default actions column width + checkbox width when isEventViewer is false and showCheckboxes is true', () => {
|
||||
expect(getActionsColumnWidth(false, true)).toEqual(
|
||||
DEFAULT_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
|
||||
);
|
||||
});
|
||||
|
||||
test('returns the events viewer actions column width when isEventViewer is true', () => {
|
||||
expect(getActionsColumnWidth(true)).toEqual(EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH);
|
||||
});
|
||||
|
||||
test('returns the events viewer actions column width + checkbox width when isEventViewer is true and showCheckboxes is true', () => {
|
||||
expect(getActionsColumnWidth(true, true)).toEqual(
|
||||
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH + SHOW_CHECK_BOXES_COLUMN_WIDTH
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSchema', () => {
|
||||
const expected: Record<string, BUILT_IN_SCHEMA> = {
|
||||
date: 'datetime',
|
||||
|
@ -343,4 +320,35 @@ describe('helpers', () => {
|
|||
).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getActionsColumnWidth', () => {
|
||||
// ideally the following implementation detail wouldn't be part of these tests,
|
||||
// but without it, the test would be brittle when `euiDataGridCellPaddingM` changes:
|
||||
const expectedPadding = parseInt(euiThemeVars.euiDataGridCellPaddingM, 10) * 2;
|
||||
|
||||
test('it returns the expected width', () => {
|
||||
const ACTION_BUTTON_COUNT = 5;
|
||||
const expectedContentWidth = ACTION_BUTTON_COUNT * DEFAULT_ACTION_BUTTON_WIDTH;
|
||||
|
||||
expect(getActionsColumnWidth(ACTION_BUTTON_COUNT)).toEqual(
|
||||
expectedContentWidth + expectedPadding
|
||||
);
|
||||
});
|
||||
|
||||
test('it returns the minimum width when the button count is zero', () => {
|
||||
const ACTION_BUTTON_COUNT = 0;
|
||||
|
||||
expect(getActionsColumnWidth(ACTION_BUTTON_COUNT)).toEqual(
|
||||
DEFAULT_ACTION_BUTTON_WIDTH + expectedPadding
|
||||
);
|
||||
});
|
||||
|
||||
test('it returns the minimum width when the button count is negative', () => {
|
||||
const ACTION_BUTTON_COUNT = -1;
|
||||
|
||||
expect(getActionsColumnWidth(ACTION_BUTTON_COUNT)).toEqual(
|
||||
DEFAULT_ACTION_BUTTON_WIDTH + expectedPadding
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme';
|
||||
import { EuiDataGridColumnActions } from '@elastic/eui';
|
||||
import { get, keyBy } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
|
@ -15,12 +16,9 @@ import type {
|
|||
} from '../../../../../common/search_strategy/index_fields';
|
||||
import type { ColumnHeaderOptions } from '../../../../../common/types/timeline';
|
||||
import {
|
||||
DEFAULT_ACTION_BUTTON_WIDTH,
|
||||
DEFAULT_COLUMN_MIN_WIDTH,
|
||||
DEFAULT_DATE_COLUMN_MIN_WIDTH,
|
||||
SHOW_CHECK_BOXES_COLUMN_WIDTH,
|
||||
EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH,
|
||||
DEFAULT_ACTIONS_COLUMN_WIDTH,
|
||||
MINIMUM_ACTIONS_COLUMN_WIDTH,
|
||||
} from '../constants';
|
||||
import { allowSorting } from '../helpers';
|
||||
|
||||
|
@ -127,19 +125,25 @@ export const getColumnHeaders = (
|
|||
export const getColumnWidthFromType = (type: string): number =>
|
||||
type !== 'date' ? DEFAULT_COLUMN_MIN_WIDTH : DEFAULT_DATE_COLUMN_MIN_WIDTH;
|
||||
|
||||
/** Returns the (fixed) width of the Actions column */
|
||||
export const getActionsColumnWidth = (
|
||||
isEventViewer: boolean,
|
||||
showCheckboxes = false,
|
||||
additionalActionWidth = 0
|
||||
): number => {
|
||||
const checkboxesWidth = showCheckboxes ? SHOW_CHECK_BOXES_COLUMN_WIDTH : 0;
|
||||
const actionsColumnWidth =
|
||||
checkboxesWidth +
|
||||
(isEventViewer ? EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH : DEFAULT_ACTIONS_COLUMN_WIDTH) +
|
||||
additionalActionWidth;
|
||||
/**
|
||||
* Returns the width of the Actions column based on the number of buttons being
|
||||
* displayed
|
||||
*
|
||||
* NOTE: This function is necessary because `width` is a required property of
|
||||
* the `EuiDataGridControlColumn` interface, so it must be calculated before
|
||||
* content is rendered. (The width of a `EuiDataGridControlColumn` does not
|
||||
* automatically size itself to fit all the content.)
|
||||
*/
|
||||
export const getActionsColumnWidth = (actionButtonCount: number): number => {
|
||||
const contentWidth =
|
||||
actionButtonCount > 0
|
||||
? actionButtonCount * DEFAULT_ACTION_BUTTON_WIDTH
|
||||
: DEFAULT_ACTION_BUTTON_WIDTH;
|
||||
|
||||
return actionsColumnWidth > MINIMUM_ACTIONS_COLUMN_WIDTH + checkboxesWidth
|
||||
? actionsColumnWidth
|
||||
: MINIMUM_ACTIONS_COLUMN_WIDTH + checkboxesWidth;
|
||||
// `EuiDataGridRowCell` applies additional `padding-left` and
|
||||
// `padding-right`, which must be added to the content width to prevent the
|
||||
// content from being partially hidden due to the space occupied by padding:
|
||||
const leftRightCellPadding = parseInt(euiThemeVars.euiDataGridCellPaddingM, 10) * 2; // parseInt ignores the trailing `px`, e.g. `6px`
|
||||
|
||||
return contentWidth + leftRightCellPadding;
|
||||
};
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
import { shallow } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants';
|
||||
import { getActionsColumnWidth } from './helpers';
|
||||
|
||||
import { defaultHeaders } from './default_headers';
|
||||
import { Sort } from '../sort';
|
||||
|
||||
|
@ -51,6 +52,8 @@ const timelineId = 'test';
|
|||
|
||||
describe('ColumnHeaders', () => {
|
||||
const mount = useMountAppended();
|
||||
const ACTION_BUTTON_COUNT = 4;
|
||||
const actionsColumnWidth = getActionsColumnWidth(ACTION_BUTTON_COUNT);
|
||||
|
||||
describe('rendering', () => {
|
||||
const sort: Sort[] = [
|
||||
|
@ -65,7 +68,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = shallow(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={defaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -88,7 +91,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={defaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -111,7 +114,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={defaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -172,7 +175,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={mockDefaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -216,7 +219,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={mockDefaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -255,7 +258,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={mockDefaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
@ -293,7 +296,7 @@ describe('ColumnHeaders', () => {
|
|||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<ColumnHeadersComponent
|
||||
actionsColumnWidth={DEFAULT_ACTIONS_COLUMN_WIDTH}
|
||||
actionsColumnWidth={actionsColumnWidth}
|
||||
browserFields={mockBrowserFields}
|
||||
columnHeaders={mockDefaultHeaders}
|
||||
isSelectAllChecked={false}
|
||||
|
|
|
@ -5,20 +5,28 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
/** The minimum (fixed) width of the Actions column */
|
||||
export const MINIMUM_ACTIONS_COLUMN_WIDTH = 50; // px;
|
||||
import { euiThemeVars } from '@kbn/ui-shared-deps-src/theme';
|
||||
|
||||
/**
|
||||
* This is the effective width in pixels of an action button used with
|
||||
* `EuiDataGrid` `leadingControlColumns`. (See Notes below for details)
|
||||
*
|
||||
* Notes:
|
||||
* 1) This constant is necessary because `width` is a required property of
|
||||
* the `EuiDataGridControlColumn` interface, so it must be calculated before
|
||||
* content is rendered. (The width of a `EuiDataGridControlColumn` does not
|
||||
* automatically size itself to fit all the content.)
|
||||
*
|
||||
* 2) This is the *effective* width, because at the time of this writing,
|
||||
* `EuiButtonIcon` has a `margin-left: -4px`, which is subtracted from the
|
||||
* `width`
|
||||
*/
|
||||
export const DEFAULT_ACTION_BUTTON_WIDTH =
|
||||
parseInt(euiThemeVars.euiSizeXL, 10) - parseInt(euiThemeVars.euiSizeXS, 10); // px
|
||||
|
||||
/** Additional column width to include when checkboxes are shown **/
|
||||
export const SHOW_CHECK_BOXES_COLUMN_WIDTH = 24; // px;
|
||||
|
||||
/** The (fixed) width of the Actions column */
|
||||
export const DEFAULT_ACTIONS_COLUMN_WIDTH = SHOW_CHECK_BOXES_COLUMN_WIDTH * 5; // px;
|
||||
/**
|
||||
* The (fixed) width of the Actions column when the timeline body is used as
|
||||
* an events viewer, which has fewer actions than a regular events viewer
|
||||
*/
|
||||
export const EVENTS_VIEWER_ACTIONS_COLUMN_WIDTH = SHOW_CHECK_BOXES_COLUMN_WIDTH * 4; // px;
|
||||
|
||||
/** The default minimum width of a column (when a width for the column type is not specified) */
|
||||
export const DEFAULT_COLUMN_MIN_WIDTH = 180; // px
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { mount } from 'enzyme';
|
||||
import React from 'react';
|
||||
|
||||
import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../constants';
|
||||
import { getActionsColumnWidth } from '../column_headers/helpers';
|
||||
|
||||
import { EventColumnView } from './event_column_view';
|
||||
import { TestCellRenderer } from '../../../../mock/cell_renderer';
|
||||
|
@ -23,10 +23,11 @@ jest.mock('../../../../hooks/use_selector', () => ({
|
|||
}));
|
||||
|
||||
describe('EventColumnView', () => {
|
||||
const ACTION_BUTTON_COUNT = 4;
|
||||
const props = {
|
||||
ariaRowindex: 2,
|
||||
id: 'event-id',
|
||||
actionsColumnWidth: DEFAULT_ACTIONS_COLUMN_WIDTH,
|
||||
actionsColumnWidth: getActionsColumnWidth(ACTION_BUTTON_COUNT),
|
||||
associateNote: jest.fn(),
|
||||
columnHeaders: [],
|
||||
columnRenderers: [],
|
||||
|
|
|
@ -50,7 +50,7 @@ import {
|
|||
|
||||
import type { TimelineItem, TimelineNonEcsData } from '../../../../common/search_strategy/timeline';
|
||||
|
||||
import { getActionsColumnWidth, getColumnHeaders } from './column_headers/helpers';
|
||||
import { getColumnHeaders } from './column_headers/helpers';
|
||||
import {
|
||||
addBuildingBlockStyle,
|
||||
getEventIdToDataMapping,
|
||||
|
@ -58,7 +58,6 @@ import {
|
|||
mapSortingColumns,
|
||||
} from './helpers';
|
||||
|
||||
import { DEFAULT_ICON_BUTTON_WIDTH } from '../helpers';
|
||||
import type { BrowserFields } from '../../../../common/search_strategy/index_fields';
|
||||
import type { OnRowSelected, OnSelectAll } from '../types';
|
||||
import type { Refetch } from '../../../store/t_grid/inputs';
|
||||
|
@ -119,19 +118,14 @@ interface OwnProps {
|
|||
}
|
||||
|
||||
const defaultUnit = (n: number) => i18n.ALERTS_UNIT(n);
|
||||
const NUM_OF_ICON_IN_TIMELINE_ROW = 2;
|
||||
|
||||
export const hasAdditionalActions = (id: TimelineId): boolean =>
|
||||
[TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage, TimelineId.active].includes(
|
||||
id
|
||||
);
|
||||
|
||||
const EXTRA_WIDTH = 4; // px
|
||||
|
||||
const ES_LIMIT_COUNT = 9999;
|
||||
|
||||
const MIN_ACTION_COLUMN_WIDTH = 96; // px
|
||||
|
||||
const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = [];
|
||||
|
||||
const EmptyHeaderCellRender: ComponentType = () => null;
|
||||
|
@ -157,7 +151,6 @@ const FIELDS_WITHOUT_CELL_ACTIONS = [
|
|||
const hasCellActions = (columnId?: string) =>
|
||||
columnId && FIELDS_WITHOUT_CELL_ACTIONS.indexOf(columnId) < 0;
|
||||
const transformControlColumns = ({
|
||||
actionColumnsWidth,
|
||||
columnHeaders,
|
||||
controlColumns,
|
||||
data,
|
||||
|
@ -179,7 +172,6 @@ const transformControlColumns = ({
|
|||
setEventsDeleted,
|
||||
hasAlertsCrudPermissions,
|
||||
}: {
|
||||
actionColumnsWidth: number;
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
controlColumns: ControlColumnProps[];
|
||||
data: TimelineItem[];
|
||||
|
@ -216,7 +208,7 @@ const transformControlColumns = ({
|
|||
<>
|
||||
{HeaderActions && (
|
||||
<HeaderActions
|
||||
width={width ?? MIN_ACTION_COLUMN_WIDTH}
|
||||
width={width}
|
||||
browserFields={browserFields}
|
||||
columnHeaders={columnHeaders}
|
||||
isEventViewer={isEventViewer}
|
||||
|
@ -283,13 +275,13 @@ const transformControlColumns = ({
|
|||
showCheckboxes={showCheckboxes}
|
||||
tabType={tabType}
|
||||
timelineId={timelineId}
|
||||
width={width ?? MIN_ACTION_COLUMN_WIDTH}
|
||||
width={width}
|
||||
setEventsLoading={setEventsLoading}
|
||||
setEventsDeleted={setEventsDeleted}
|
||||
/>
|
||||
);
|
||||
},
|
||||
width: width ?? actionColumnsWidth,
|
||||
width,
|
||||
})
|
||||
);
|
||||
|
||||
|
@ -618,13 +610,6 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
controlColumns,
|
||||
data,
|
||||
isEventViewer,
|
||||
actionColumnsWidth: hasAdditionalActions(id as TimelineId)
|
||||
? getActionsColumnWidth(
|
||||
isEventViewer,
|
||||
showCheckboxes,
|
||||
DEFAULT_ICON_BUTTON_WIDTH * NUM_OF_ICON_IN_TIMELINE_ROW + EXTRA_WIDTH
|
||||
)
|
||||
: controlColumns.reduce((acc, c) => acc + (c.width ?? MIN_ACTION_COLUMN_WIDTH), 0),
|
||||
loadingEventIds,
|
||||
onRowSelected,
|
||||
onRuleChange,
|
||||
|
|
|
@ -241,8 +241,6 @@ export const getCombinedFilterQuery = ({
|
|||
*/
|
||||
export const STATEFUL_EVENT_CSS_CLASS_NAME = 'event-column-view';
|
||||
|
||||
export const DEFAULT_ICON_BUTTON_WIDTH = 24;
|
||||
|
||||
export const resolverIsShowing = (graphEventId: string | undefined): boolean =>
|
||||
graphEventId != null && graphEventId !== '';
|
||||
|
||||
|
|
|
@ -57,6 +57,8 @@ export {
|
|||
addFieldToTimelineColumns,
|
||||
getTimelineIdFromColumnDroppableId,
|
||||
} from './components/drag_and_drop/helpers';
|
||||
export { getActionsColumnWidth } from './components/t_grid/body/column_headers/helpers';
|
||||
export { DEFAULT_ACTION_BUTTON_WIDTH } from './components/t_grid/body/constants';
|
||||
export { StatefulFieldsBrowser } from './components/t_grid/toolbar/fields_browser';
|
||||
export { useStatusBulkActionItems } from './hooks/use_status_bulk_action_items';
|
||||
// This exports static code and TypeScript types,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue