Skip to content

Commit

Permalink
[SIEM] Adds actions and selection to Signals Table (elastic#53101) (e…
Browse files Browse the repository at this point in the history
…lastic#53551)

## Summary

This is `Part II` of `II` for adding the `Signals Table` to the main Detection Engine landing page ([meta issue](elastic#50405)).

`Part II` includes:
* Adding `selection`, `selectAll` & `selectAllGlobal` (i.e. query select) functionality to the EventsViewer
* Includes ability to specify a fieldset when storing selection state so it can be used by custom actions
 * Introduces following new Timeline state:
    * `deletedEventIds: string[]`
    * `loadingEventIds: string[]`
    *  `selectedEventIds: Record<string, TimelineNonEcsData[]>`
    * `showCheckboxes: boolean`
    * `showRowRenderers: boolean`
* Adds Send to Timeline overflow/batch action (detailed [here](elastic#50405 (comment)))
* Adds Update Signal Status overflow/batch action

Resolves elastic#51785

##### Selection / Update Signal Status
![update_signal_state](https://user-images.githubusercontent.com/2946766/70887496-61d59280-1f9b-11ea-8483-ab30e3936738.gif)

##### Send Signal to Timeline Action

### Checklist

Use ~~strikethroughs~~ to remove checklist items you don't feel are applicable to this PR.

- [ ] This was checked for cross-browser compatibility, [including a check against IE11](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility)
- [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/master/packages/kbn-i18n/README.md)
- [ ] [Documentation](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#writing-documentation) was added for features that require explanation or tutorials
- [ ] [Unit or functional tests](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#cross-browser-compatibility) were updated or added to match the most common scenarios
- [ ] ~This was checked for [keyboard-only and screenreader accessibility](https://developer.mozilla.org/en-US/docs/Learn/Tools_and_testing/Cross_browser_testing/Accessibility#Accessibility_testing_checklist)~

### For maintainers

- [ ] ~This was checked for breaking API changes and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~
- [ ] ~This includes a feature addition or change that requires a release note and was [labeled appropriately](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md#release-notes-process)~
  • Loading branch information
spong authored Dec 19, 2019
1 parent 1b6a87b commit 3b0e778
Show file tree
Hide file tree
Showing 48 changed files with 1,870 additions and 331 deletions.
1 change: 1 addition & 0 deletions x-pack/legacy/plugins/siem/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const DEFAULT_DATE_FORMAT = 'dateFormat';
export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz';
export const DEFAULT_DARK_MODE = 'theme:darkMode';
export const DEFAULT_INDEX_KEY = 'siem:defaultIndex';
export const DEFAULT_NUMBER_FORMAT = 'format:number:defaultPattern';
export const DEFAULT_TIME_RANGE = 'timepicker:timeDefaults';
export const DEFAULT_REFRESH_RATE_INTERVAL = 'timepicker:refreshIntervalDefaults';
export const DEFAULT_SIEM_TIME_RANGE = 'siem:timeDefaults';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const AlertsTable = React.memo(
const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]);
return (
<StatefulEventsViewer
defaultFilters={alertsFilter}
pageFilters={alertsFilter}
defaultModel={alertsDefaultModel}
end={endDate}
id={ALERTS_TABLE_ID}
Expand All @@ -73,8 +73,6 @@ export const AlertsTable = React.memo(
() => ({
documentType: i18n.ALERTS_DOCUMENT_TYPE,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
showCheckboxes: false,
showRowRenderers: false,
title: i18n.ALERTS_TABLE_TITLE,
}),
[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,5 @@ export const alertsHeaders: ColumnHeader[] = [
export const alertsDefaultModel: SubsetTimelineModel = {
...timelineDefaults,
columns: alertsHeaders,
showRowRenderers: false,
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
*/

import { EuiPanel } from '@elastic/eui';
import { getOr, isEmpty, isEqual } from 'lodash/fp';
import React from 'react';
import { getOr, isEmpty, isEqual, union } from 'lodash/fp';
import React, { useMemo } from 'react';
import styled from 'styled-components';

import { BrowserFields } from '../../containers/source';
Expand Down Expand Up @@ -46,6 +46,7 @@ interface Props {
browserFields: BrowserFields;
columns: ColumnHeader[];
dataProviders: DataProvider[];
deletedEventIds: Readonly<string[]>;
end: number;
filters: esFilters.Filter[];
headerFilterGroup?: React.ReactNode;
Expand All @@ -71,6 +72,7 @@ export const EventsViewer = React.memo<Props>(
browserFields,
columns,
dataProviders,
deletedEventIds,
end,
filters,
headerFilterGroup,
Expand Down Expand Up @@ -104,6 +106,14 @@ export const EventsViewer = React.memo<Props>(
end,
isEventViewer: true,
});
const queryFields = useMemo(
() =>
union(
columnsHeader.map(c => c.id),
timelineTypeContext.queryFields ?? []
),
[columnsHeader, timelineTypeContext.queryFields]
);

return (
<EuiPanel data-test-subj="events-viewer-panel" grow={false}>
Expand All @@ -119,7 +129,7 @@ export const EventsViewer = React.memo<Props>(

{combinedQueries != null ? (
<TimelineQuery
fields={columnsHeader.map(c => c.id)}
fields={queryFields}
filterQuery={combinedQueries.filterQuery}
id={id}
indexPattern={indexPattern}
Expand All @@ -139,73 +149,81 @@ export const EventsViewer = React.memo<Props>(
pageInfo,
refetch,
totalCount = 0,
}) => (
<>
<HeaderSection
id={id}
showInspect={showInspect}
subtitle={
utilityBar
? undefined
: `${i18n.SHOWING}: ${totalCount.toLocaleString()} ${i18n.UNIT(
totalCount
)}`
}
title={timelineTypeContext?.title ?? i18n.EVENTS}
>
{headerFilterGroup}
</HeaderSection>
}) => {
const totalCountMinusDeleted =
totalCount > 0 ? totalCount - deletedEventIds.length : 0;

// TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt)
return (
<>
<HeaderSection
id={id}
showInspect={showInspect}
subtitle={
utilityBar
? undefined
: `${
i18n.SHOWING
}: ${totalCountMinusDeleted.toLocaleString()} ${i18n.UNIT(
totalCountMinusDeleted
)}`
}
title={timelineTypeContext?.title ?? i18n.EVENTS}
>
{headerFilterGroup}
</HeaderSection>

{utilityBar?.(totalCount)}
{utilityBar?.(totalCountMinusDeleted)}

<div
data-test-subj={`events-container-loading-${loading}`}
style={{ width: `${width}px` }}
>
<ManageTimelineContext
loading={loading}
width={width}
type={timelineTypeContext}
<div
data-test-subj={`events-container-loading-${loading}`}
style={{ width: `${width}px` }}
>
<TimelineRefetch
id={id}
inputId="global"
inspect={inspect}
<ManageTimelineContext
loading={loading}
refetch={refetch}
/>
width={width}
type={timelineTypeContext}
>
<TimelineRefetch
id={id}
inputId="global"
inspect={inspect}
loading={loading}
refetch={refetch}
/>

<StatefulBody
browserFields={browserFields}
data={events}
id={id}
isEventViewer={true}
height={height}
sort={sort}
toggleColumn={toggleColumn}
/>
<StatefulBody
browserFields={browserFields}
data={events.filter(e => !deletedEventIds.includes(e._id))}
id={id}
isEventViewer={true}
height={height}
sort={sort}
toggleColumn={toggleColumn}
/>

<Footer
compact={isCompactFooter(width)}
getUpdatedAt={getUpdatedAt}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
isEventViewer={true}
isLive={isLive}
isLoading={loading}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
serverSideEventCount={totalCount}
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
/>
</ManageTimelineContext>
</div>
</>
)}
<Footer
compact={isCompactFooter(width)}
getUpdatedAt={getUpdatedAt}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
isEventViewer={true}
isLive={isLive}
isLoading={loading}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
itemsPerPageOptions={itemsPerPageOptions}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
serverSideEventCount={totalCountMinusDeleted}
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
/>
</ManageTimelineContext>
</div>
</>
);
}}
</TimelineQuery>
) : null}
</>
Expand All @@ -218,6 +236,7 @@ export const EventsViewer = React.memo<Props>(
prevProps.browserFields === nextProps.browserFields &&
prevProps.columns === nextProps.columns &&
prevProps.dataProviders === nextProps.dataProviders &&
prevProps.deletedEventIds === nextProps.deletedEventIds &&
prevProps.end === nextProps.end &&
isEqual(prevProps.filters, nextProps.filters) &&
prevProps.height === nextProps.height &&
Expand All @@ -230,6 +249,8 @@ export const EventsViewer = React.memo<Props>(
isEqual(prevProps.query, nextProps.query) &&
prevProps.showInspect === nextProps.showInspect &&
prevProps.start === nextProps.start &&
prevProps.sort === nextProps.sort
prevProps.sort === nextProps.sort &&
isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) &&
prevProps.utilityBar === nextProps.utilityBar
);
EventsViewer.displayName = 'EventsViewer';
Loading

0 comments on commit 3b0e778

Please sign in to comment.