Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RAC][Alert Triage][Timeline] Update the Alerts Table and Timeline APIs #94520

Open
20 of 39 tasks
andrew-goldstein opened this issue Mar 12, 2021 · 5 comments
Open
20 of 39 tasks
Labels
Feature:Detection Alerts Security Solution Detection Alerts Feature Feature:Timeline Security Solution Timeline feature Team:Detections and Resp Security Detection Response Team Team:Observability Team label for Observability Team (for things that are handled across all of observability) Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:Threat Hunting Security Solution Threat Hunting Team Theme: rac label obsolete

Comments

@andrew-goldstein
Copy link
Contributor

[RAC][Alert Triage][Timeline] Update the Alerts Table and Timeline APIs

Timeline-based tables provide APIs for configuring columns, customizing actions, and integrating with Kibana APIs for other concerns such as applying filters to the KQL bar at the top of the page, querying for alerts / events, and persisting investigations via saved objects.

The purpose of this issue is to update the public APIs for the Alerts Table and Timeline for consumers outside of the Security Solution.

The updated APIs must continue to provide the following functionality:

  • Timeline-based tables are configured via an API to provide a custom set of default columns for different views, e.g. the Alerts table on the Detections page, Rule Details Pages, Events / External alerts tables, and Timeline itself

    • The API enables custom rendering of columns, that, for example, render an external link to an IP reputation site, or open a flyout to view details
  • Static, page-level query filters are applied via the API to implement Rule Detail, Host Detail, and Network pages

  • The common View details row-level action integrates with a flyout to display alert / event details

  • The API allows each table to provide custom row-level actions

    • The Alerts view integrates with the Detections API to update the status of alerts and add rule exceptions
  • Cases optionally link directly to Analyzer (Resolver) Graph Views via URLs

  • Table-level filtering enables the alerts table to be filtered by alert status

  • The API enables two-way integration with the KQL bar and Filters, which are located at the top of most pages

  • All fields on a page, including chart legends and table content, are (optionally) rendered via a component that includes a hover menu that affords a consistent experience for:

    • pivoting
    • filtering
    • performing Top N aggregations
    • copying the field + value as KQL snytax to the clipboard
  • Integration with the Detections API, enabling bulk-actions for changing the status of alerts (e.g. Open, Closed)

  • The API accepts a unique identifier, enabling user-customized columns to persist across page loads via localStorage

  • Timeline based tables combine query results from alert and non-alert indexes

  • Investigations containing notes and pinned events are persisted via saved objects in the form of saved Timelines

    • An API enables Timelines to be imported / exported as ndjson files

Timeline-based tables are configured via an API to provide a custom set of default columns

Timeline-based tables are configured via an API to provide a custom set of default columns for the:

  • Alerts table on the Detections page
  • Alerts table on the Rule Details page
  • Events and External alerts tables on the Host Details page
  • Events and External alerts tables on the Host Page
  • External alerts on the Network page
  • Timeline itself

The API implemented today enables each page where a Timeline-based table is used to specify a unique set of default columns. The configuration includes:

  • The field identifier, e.g. signal.rule.name
  • An optional internationalized (i18n) custom column label, e.g. Rule name
  • An optional hyperlink for the column value that may, for example, open a flyout containing the details of an alert, or link to an IP reputation site
  • The default width of the field

For example, the following array defines the default columns used by the Alerts table on the Detections page:

export const alertsHeaders: ColumnHeaderOptions[] = [
  {
    columnHeaderType: defaultColumnHeaderType,
    id: '@timestamp',
    width: DEFAULT_DATE_COLUMN_MIN_WIDTH + 5,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'signal.rule.name',
    label: i18n.ALERTS_HEADERS_RULE,
    linkField: 'signal.rule.id',
    width: DEFAULT_COLUMN_MIN_WIDTH,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'signal.rule.version',
    label: i18n.ALERTS_HEADERS_VERSION,
    width: 95,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'signal.rule.type',
    label: i18n.ALERTS_HEADERS_METHOD,
    width: 100,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'signal.rule.severity',
    label: i18n.ALERTS_HEADERS_SEVERITY,
    width: 105,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'signal.rule.risk_score',
    label: i18n.ALERTS_HEADERS_RISK_SCORE,
    width: 115,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'event.module',
    linkField: 'rule.reference',
    width: DEFAULT_COLUMN_MIN_WIDTH,
  },
  {
    category: 'event',
    columnHeaderType: defaultColumnHeaderType,
    id: 'event.action',
    type: 'string',
    aggregatable: true,
    width: 140,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'event.category',
    width: 150,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'host.name',
    width: 120,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'user.name',
    width: 120,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'source.ip',
    width: 120,
  },
  {
    columnHeaderType: defaultColumnHeaderType,
    id: 'destination.ip',
    width: 140,
  },
];

The column configuration above is used in the Alerts table on the Detections page below:

alerts

The Alerts table on the Detections page

  • The API must accept a configuration that specifies the default set of columns
  • The API must enable custom rendering of columns, that, for example, render an external link to an IP reputation site, or open a flyout to view details

Static, page-level query filters

Some pages specify static, page-level query filters to some Timeline-based tables.

The following views in the Security Solution are rendered by Timeline-based tables, which pass page-level context to the tables query (via the API):

signal.rule.id filters the Rule Details page's Timeline-based table

rule-details-page

A Rule Details page

host.name filters both the Events and External Alerts tables on Host Details pages

host-details

A Host Details page

event.kind": "alert" and source.ip: * and destination.ip: * filters the External Alerts table on the Network page

network-page

The Network page

  • The API must enable static, page-level filters, e.g. signal.rule.id, to be applied to the table's query

The common View details row-level flyout interaction

All Timeline-based tables provide a row level action that, when clicked, displays the details of the alert or event in a flyout:

view-details

The View details row-level action opens an Alert Details flyout

  • The API must enable consumers to override the default behavior of the View details row-level action

Custom row-level actions

Today, the API enables each Timeline-based table to display a custom set of row-level actions:

alerts-table-actions

Alerts table actions

events-table-actions

Events table actions

Timeline contains the superset of all table actions, including some actions that are exclusive to Timeline itself, i.e. the Add notes for this event and Pin event actions:

timeline-table-actions

Timeline's table actions

Additional actions are programmatically provided to each table via an array of JSX.Element[]:

additonal-actions-code

Row-level actions for updating alert status, and adding rule exceptions

The Alerts view integrates with the Detections API to update the status of alerts and add rule exceptions:

row-level-alert-actions

  • The API must allow table-specific row-level actions to be provided

Cases optionally link directly to Analyzer (Resolver) Graph Views via URLs

Timeline-based tables provide the Analyze Event row-level action shown below:

analyze-event

When users click the Analyze Event action, the (Resolver) process graph view is displayed as a table overlay:

analyze-event-graph

Users may attach cases that directly link to Timeline's Analyzer view, shown below:

link-directly-to-analyzer

  • The API must generate URLs that link directly to a saved Timeline's process graph

Table-level filtering

The API enables Timeline-based tables to implement table-level filtering.

For example, the Alerts table provides filtering by alert status:

table-filters

Table-level filtering

  • The API must enable (optional) table-level filtering by alert status

Two-Way integration with the page-level KQL bar and filters

Timeline-based tables use Kibana APIs for two-way integration with the KQL bar and it's Filters, which are located at the top of most pages:

two-way-filtering

Two-way filtering on the Detections page

An integration with Kibana APIs enables, in the screenshot above:

  • The Alerts table to be filtered by the KQL bar at the top of the page

  • The Alerts table to be filtered by Filters added to the KQL bar

  • Users may, via the hover menu on fields, add filters to the KQL bar at the top of the page

  • The API must enable the table to be filtered by a page-level KQL bar and its Filters

  • The API must enable filters in the table to be added to page-level Filters

Hover menu actions

All fields on a page, including chart legends and table content, are (optionally) rendered via a component that includes a hover menu that affords a consistent experience for:

  • pivoting
  • filtering
  • performing Top N aggregations
  • copying the field + value as KQL snytax to the clipboard

Every field rendered in a Timeline-based table provides the following actions via a hover menu:

  • Filter for value (applies a filter to the KQL bar)

filer-for-value

  • Filter out value (also via the KQL) bar

filter-out-value

  • Add to timeline investigation

add-to-timeline-iinvestigation

Clicking the button above programmatically drag-and-drops the field to Timeline's query area:

automatic-drag-and-drop

  • Show Top field

top-field-name

Clicking the button above displays an an interactive Top N popover, shown below:

top-user-name

The animated gif below shows the Top N popover appearing when user user clicks Show top event.action in the hover menu:

top-n

The legend items rendered in the popover also contain a hover menu proffering the same actions.

  • The API must enable consumers to specify the indexes included in Top N aggregations

  • Copy to clipboard

copy-to-clipboard

The Copy to clipboard action formats the field and value using KQL syntax, e.g.:

user.name: "NETWORK SERVICE"

Any field on a page, whether it's rendered in a table or not, can be wrapped in a React component that provides the hover menu functionality. By default, the field is rendered / styled as a draggable.

Some applications may not wish to render fields as draggables. Thus:

  • The API must enable fields to be rendered with the common hover menu, with drag-and-drop disabled

Integration with the Detections API, enabling bulk-actions for changing the status of alerts (e.g. Open, Closed)

The alerts table enables integrates with the Detections API, enabling bulk-actions for changing the status of alerts (e.g. Open, Closed):

bulk-actions

  • The API must provide consumers the option of enabling bulk status updates in the alerts table

User-customized columns are saved to localStorage to persist across page-loads

Users may customize the columns in a table via the Fields Browser, shown below:

fields-browser

The fields browser groups fields from multiple indexes / field mappings into a single view, organized by category, with descriptions. It also provides an affordance for resetting the table to it's specific set of default columns.

  • The API must allow consumers to specify the set of indexes that will be used to display fields in the Customize Columns view

Timeline-based tables persist user-customized columns to the browser's localStorage when:

  • Columns are added to the table via the Customize Columns view

  • Default columns are restored via the Reset fields action in the Customize Columns view

  • Columns are re-arranged via drag-and drop:

drag-and-drop-columns

  • Columns are added via the Table view in the Alert / Event details flyout:

add-column-via-alert-details

  • The API must enable a unique ID to be specified for each table instance, which will be used to persist user-customized columns to localStorage

Timeline based tables combine query results from alert and non-alert indexes

Timeline based tables combine query results from alert and non-alert indexes.

  • The API must allow consumers the specify the non-alert indexes to query

Today, Timeline-based tables directly query alert indexes, but a new abstraction layer for querying alerts may soon be available.

  • If available, Timeline-based tables should consume the new abstraction layer for querying alerts

Timelines persisted as saved objects

While performing an investigation, users may add inline notes to alerts and events in Timeline via Markdown:

inline-notes

Users may also pin events of interest:

pinned-events

When users are ready to share the notes and pinned events gathered during an investigation, they give the untitled timeline a name. The timeline is then in-turn persisted as a saved object in the Kibana space where it was created.

  • Timeline saved objects created in the Security Solution before the Timeline is moved to it's own plugin must still be readable, ideally without the need for a saved object migration

  • Timelines created in the same Kibana space must be readable by any app that embeds Timeline

  • An API must be provided for exporting Timelines as downlodable ndjson files

  • An API must be provided for importing Timeline ndjson files into a Kibana space

Acceptance Criteria

  • The API must accept a configuration that specifies the default set of columns
  • The API must enable custom rendering of columns, that, for example, render an external link to an IP reputation site, or open a flyout to view details
  • The API must enable static, page-level filters, e.g. signal.rule.id, to be applied to the table's query
  • The API must enable consumers to override the default behavior of the View details row-level action
  • The API must allow table-specific row-level actions to be provided
  • The API must generate URLs that link directly to a saved Timeline's process graph
  • The API must enable (optional) table-level filtering by alert status
  • The API must enable the table to be filtered by a page-level KQL bar and its Filters
  • The API must enable filters in the table to be added to page-level Filters
  • The API must enable consumers to specify the indexes included in Top N aggregations
  • The API must enable fields to be rendered with the common hover menu, with drag-and-drop disabled
  • The API must provide consumers the option of enabling bulk status updates in the alerts table
  • The API must allow consumers to specify the set of indexes that will be used to display fields in the Customize Columns view
  • The API must enable a unique ID to be specified for each table instance, which will be used to persist user-customized columns to localStorage
  • The API must allow consumers the specify the non-alert indexes to query
  • If available, Timeline-based tables should consume the new abstraction layer for querying alerts
  • Timelines created in the same Kibana space must be readable by any app that embeds Timeline
  • An API must be provided for exporting Timelines as downlodable ndjson files
  • An API must be provided for importing Timeline ndjson files into a Kibana space
@andrew-goldstein andrew-goldstein added Team:Observability Team label for Observability Team (for things that are handled across all of observability) Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) Team:Detections and Resp Security Detection Response Team Team:Threat Hunting Security Solution Threat Hunting Team Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Feature:Timeline Security Solution Timeline feature Feature:Detection Alerts Security Solution Detection Alerts Feature Theme: rac label obsolete labels Mar 12, 2021
@elasticmachine
Copy link
Contributor

Pinging @elastic/security-solution (Team: SecuritySolution)

@elasticmachine
Copy link
Contributor

Pinging @elastic/security-threat-hunting (Team:Threat Hunting)

@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-alerting-services (Team:Alerting Services)

@elasticmachine
Copy link
Contributor

Pinging @elastic/security-detections-response (Team:Detections and Resp)

andrew-goldstein added a commit to andrew-goldstein/kibana that referenced this issue Apr 1, 2021
… implement `renderCellValue`

- This PR implements a superset of the `renderCellValue` API from [EuiDataGrid](https://elastic.github.io/eui/#/tabular-content/data-grid) in the `TGrid` (Timeline grid) API

- The TGrid API was also updated to accept a collection of `RowRenderer`s as a prop

The API changes are summarized by the following screenshot:

<img width="1239" alt="render-cell-value" src="https://user-images.githubusercontent.com/4459398/113345484-c121f800-92ef-11eb-8a21-2b6dd8ef499b.png">

Related (RAC) issue: elastic#94520

### Details

The `StatefulEventsViewer` has been updated to accept `renderCellValue` as a (required) prop:

```
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
```

The type definition of `CellValueElementProps` is:

```
export type CellValueElementProps = EuiDataGridCellValueElementProps & {
  data: TimelineNonEcsData[];
  eventId: string; // _id
  header: ColumnHeaderOptions;
  linkValues: string[] | undefined;
  timelineId: string;
};
```

The `CellValueElementProps` type above is a _superset_ of `EuiDataGridCellValueElementProps`. The additional properties above include the `data` returned by the TGrid when it performs IO to retrieve alerts and events.

### Using `renderCellValue` to control rendering

The internal implementation of TGrid's cell rendering didn't change with this PR; it moved to

`x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx` as shown below:

```
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
  columnId,
  data,
  eventId,
  header,
  linkValues,
  setCellProps,
  timelineId,
}) => (
  <>
    {getColumnRenderer(header.id, columnRenderers, data).renderColumn({
      columnName: header.id,
      eventId,
      field: header,
      linkValues,
      timelineId,
      truncate: true,
      values: getMappedNonEcsValue({
        data,
        fieldName: header.id,
      }),
    })}
  </>
);
```

Any usages of TGrid were updated to pass `DefaultCellRenderer` as the value of the `renderCellValue` prop, as shown in the screenshot below:

<img width="1239" alt="render-cell-value" src="https://user-images.githubusercontent.com/4459398/113345484-c121f800-92ef-11eb-8a21-2b6dd8ef499b.png">

The `EuiDataGrid` [codesandbox example](https://codesandbox.io/s/nsmzs) provides the following example `renderCellValue` implementation, which highlights a cell green based on it's numeric value:

```
  const renderCellValue = useMemo(() => {
    return ({ rowIndex, columnId, setCellProps }) => {
      const data = useContext(DataContext);
      useEffect(() => {
        if (columnId === 'amount') {
          if (data.hasOwnProperty(rowIndex)) {
            const numeric = parseFloat(
              data[rowIndex][columnId].match(/\d+\.\d+/)[0],
              10
            );
            setCellProps({
              style: {
                backgroundColor: `rgba(0, 255, 0, ${numeric * 0.0002})`,
              },
            });
          }
        }
      }, [rowIndex, columnId, setCellProps, data]);

      function getFormatted() {
        return data[rowIndex][columnId].formatted
          ? data[rowIndex][columnId].formatted
          : data[rowIndex][columnId];
      }

      return data.hasOwnProperty(rowIndex)
        ? getFormatted(rowIndex, columnId)
        : null;
    };
  }, []);
```

The sample code above formats the `amount` column in the example `EuiDataGrid` with a green `backgroundColor` based on the value of the data, as shown in the screenshot below:

<img width="956" alt="datagrid-cell-formatting" src="https://user-images.githubusercontent.com/4459398/113348300-a782af80-92f3-11eb-896a-3d92cf4b9b53.png">

To demonstrate that similar styling can be applied to TGrid using the same technique illustrated by `EuiDataGrid`'s [codesandbox example](https://codesandbox.io/s/nsmzs), we can update the `DefaultCellRenderer` in `x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx` to apply a similar technique:

```
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
  columnId,
  data,
  eventId,
  header,
  linkValues,
  setCellProps,
  timelineId,
}) => {
  useEffect(() => {
    if (columnId === 'signal.rule.risk_score') {
      const value = getMappedNonEcsValue({
        data,
        fieldName: columnId,
      });
      if (Array.isArray(value) && value.length > 0) {
        const numeric = parseFloat(value[0]);
        setCellProps({
          style: {
            backgroundColor: `rgba(0, 255, 0, ${numeric * 0.002})`,
          },
        });
      }
    }
  }, [columnId, data, setCellProps]);

  return (
    <>
      {getMappedNonEcsValue({
        data,
        fieldName: columnId,
      })}
    </>
  );
};
```

The example code above renders the  `signal.rule.risk_score` column in the Alerts table with a green `backgroundColor` based on the value of the data, as shown in the screenshot below:

<img width="1231" alt="alerts" src="https://user-images.githubusercontent.com/4459398/113349015-a30ac680-92f4-11eb-8518-5c1b7465e76e.png">

Note: In the screenshot above, the values in the Alerts table are not rendered as draggables.
andrew-goldstein added a commit that referenced this issue Apr 5, 2021
…lement `renderCellValue` (#96098)

### [RAC][Alert Triage][TGrid] Update the Alerts Table (TGrid) API to implement `renderCellValue`

- This PR implements a superset of the `renderCellValue` API from [EuiDataGrid](https://elastic.github.io/eui/#/tabular-content/data-grid) in the `TGrid` (Timeline grid) API

- The TGrid API was also updated to accept a collection of `RowRenderer`s as a prop

The API changes are summarized by the following screenshot:

<img width="1239" alt="render-cell-value" src="https://user-images.githubusercontent.com/4459398/113345484-c121f800-92ef-11eb-8a21-2b6dd8ef499b.png">

The following screenshot shows the `signal.rule.risk_score` column in the Alerts table being rendered with a green background color, using the same technique illustrated by `EuiDataGrid`'s [codesandbox example](https://codesandbox.io/s/nsmzs):

<img width="1231" alt="alerts" src="https://user-images.githubusercontent.com/4459398/113349015-a30ac680-92f4-11eb-8518-5c1b7465e76e.png">

Note: In the screenshot above, the values in the Alerts table are also _not_ rendered as draggables.

Related (RAC) issue: #94520

### Details

The `StatefulEventsViewer` has been updated to accept `renderCellValue` as a (required) prop:

```
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
```

The type definition of `CellValueElementProps` is:

```
export type CellValueElementProps = EuiDataGridCellValueElementProps & {
  data: TimelineNonEcsData[];
  eventId: string; // _id
  header: ColumnHeaderOptions;
  linkValues: string[] | undefined;
  timelineId: string;
};
```

The `CellValueElementProps` type above is a _superset_ of `EuiDataGridCellValueElementProps`. The additional properties above include the `data` returned by the TGrid when it performs IO to retrieve alerts and events.

### Using `renderCellValue` to control rendering

The internal implementation of TGrid's cell rendering didn't change with this PR; it moved to

`x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx` as shown below:

```
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
  columnId,
  data,
  eventId,
  header,
  linkValues,
  setCellProps,
  timelineId,
}) => (
  <>
    {getColumnRenderer(header.id, columnRenderers, data).renderColumn({
      columnName: header.id,
      eventId,
      field: header,
      linkValues,
      timelineId,
      truncate: true,
      values: getMappedNonEcsValue({
        data,
        fieldName: header.id,
      }),
    })}
  </>
);
```

Any usages of TGrid were updated to pass `DefaultCellRenderer` as the value of the `renderCellValue` prop, as shown in the screenshot below:

<img width="1239" alt="render-cell-value" src="https://user-images.githubusercontent.com/4459398/113345484-c121f800-92ef-11eb-8a21-2b6dd8ef499b.png">

The `EuiDataGrid` [codesandbox example](https://codesandbox.io/s/nsmzs) provides the following example `renderCellValue` implementation, which highlights a cell green based on it's numeric value:

```
  const renderCellValue = useMemo(() => {
    return ({ rowIndex, columnId, setCellProps }) => {
      const data = useContext(DataContext);
      useEffect(() => {
        if (columnId === 'amount') {
          if (data.hasOwnProperty(rowIndex)) {
            const numeric = parseFloat(
              data[rowIndex][columnId].match(/\d+\.\d+/)[0],
              10
            );
            setCellProps({
              style: {
                backgroundColor: `rgba(0, 255, 0, ${numeric * 0.0002})`,
              },
            });
          }
        }
      }, [rowIndex, columnId, setCellProps, data]);

      function getFormatted() {
        return data[rowIndex][columnId].formatted
          ? data[rowIndex][columnId].formatted
          : data[rowIndex][columnId];
      }

      return data.hasOwnProperty(rowIndex)
        ? getFormatted(rowIndex, columnId)
        : null;
    };
  }, []);
```

The sample code above formats the `amount` column in the example `EuiDataGrid` with a green `backgroundColor` based on the value of the data, as shown in the screenshot below:

<img width="956" alt="datagrid-cell-formatting" src="https://user-images.githubusercontent.com/4459398/113348300-a782af80-92f3-11eb-896a-3d92cf4b9b53.png">

To demonstrate that similar styling can be applied to TGrid using the same technique illustrated by `EuiDataGrid`'s [codesandbox example](https://codesandbox.io/s/nsmzs), we can update the `DefaultCellRenderer` in `x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx` to apply a similar technique:

```
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
  columnId,
  data,
  eventId,
  header,
  linkValues,
  setCellProps,
  timelineId,
}) => {
  useEffect(() => {
    if (columnId === 'signal.rule.risk_score') {
      const value = getMappedNonEcsValue({
        data,
        fieldName: columnId,
      });
      if (Array.isArray(value) && value.length > 0) {
        const numeric = parseFloat(value[0]);
        setCellProps({
          style: {
            backgroundColor: `rgba(0, 255, 0, ${numeric * 0.002})`,
          },
        });
      }
    }
  }, [columnId, data, setCellProps]);

  return (
    <>
      {getMappedNonEcsValue({
        data,
        fieldName: columnId,
      })}
    </>
  );
};
```

The example code above renders the  `signal.rule.risk_score` column in the Alerts table with a green `backgroundColor` based on the value of the data, as shown in the screenshot below:

<img width="1231" alt="alerts" src="https://user-images.githubusercontent.com/4459398/113349015-a30ac680-92f4-11eb-8518-5c1b7465e76e.png">

Note: In the screenshot above, the values in the Alerts table are not rendered as draggables.
andrew-goldstein added a commit to andrew-goldstein/kibana that referenced this issue Apr 5, 2021
…lement `renderCellValue` (elastic#96098)

### [RAC][Alert Triage][TGrid] Update the Alerts Table (TGrid) API to implement `renderCellValue`

- This PR implements a superset of the `renderCellValue` API from [EuiDataGrid](https://elastic.github.io/eui/#/tabular-content/data-grid) in the `TGrid` (Timeline grid) API

- The TGrid API was also updated to accept a collection of `RowRenderer`s as a prop

The API changes are summarized by the following screenshot:

<img width="1239" alt="render-cell-value" src="https://user-images.githubusercontent.com/4459398/113345484-c121f800-92ef-11eb-8a21-2b6dd8ef499b.png">

The following screenshot shows the `signal.rule.risk_score` column in the Alerts table being rendered with a green background color, using the same technique illustrated by `EuiDataGrid`'s [codesandbox example](https://codesandbox.io/s/nsmzs):

<img width="1231" alt="alerts" src="https://user-images.githubusercontent.com/4459398/113349015-a30ac680-92f4-11eb-8518-5c1b7465e76e.png">

Note: In the screenshot above, the values in the Alerts table are also _not_ rendered as draggables.

Related (RAC) issue: elastic#94520

### Details

The `StatefulEventsViewer` has been updated to accept `renderCellValue` as a (required) prop:

```
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
```

The type definition of `CellValueElementProps` is:

```
export type CellValueElementProps = EuiDataGridCellValueElementProps & {
  data: TimelineNonEcsData[];
  eventId: string; // _id
  header: ColumnHeaderOptions;
  linkValues: string[] | undefined;
  timelineId: string;
};
```

The `CellValueElementProps` type above is a _superset_ of `EuiDataGridCellValueElementProps`. The additional properties above include the `data` returned by the TGrid when it performs IO to retrieve alerts and events.

### Using `renderCellValue` to control rendering

The internal implementation of TGrid's cell rendering didn't change with this PR; it moved to

`x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx` as shown below:

```
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
  columnId,
  data,
  eventId,
  header,
  linkValues,
  setCellProps,
  timelineId,
}) => (
  <>
    {getColumnRenderer(header.id, columnRenderers, data).renderColumn({
      columnName: header.id,
      eventId,
      field: header,
      linkValues,
      timelineId,
      truncate: true,
      values: getMappedNonEcsValue({
        data,
        fieldName: header.id,
      }),
    })}
  </>
);
```

Any usages of TGrid were updated to pass `DefaultCellRenderer` as the value of the `renderCellValue` prop, as shown in the screenshot below:

<img width="1239" alt="render-cell-value" src="https://user-images.githubusercontent.com/4459398/113345484-c121f800-92ef-11eb-8a21-2b6dd8ef499b.png">

The `EuiDataGrid` [codesandbox example](https://codesandbox.io/s/nsmzs) provides the following example `renderCellValue` implementation, which highlights a cell green based on it's numeric value:

```
  const renderCellValue = useMemo(() => {
    return ({ rowIndex, columnId, setCellProps }) => {
      const data = useContext(DataContext);
      useEffect(() => {
        if (columnId === 'amount') {
          if (data.hasOwnProperty(rowIndex)) {
            const numeric = parseFloat(
              data[rowIndex][columnId].match(/\d+\.\d+/)[0],
              10
            );
            setCellProps({
              style: {
                backgroundColor: `rgba(0, 255, 0, ${numeric * 0.0002})`,
              },
            });
          }
        }
      }, [rowIndex, columnId, setCellProps, data]);

      function getFormatted() {
        return data[rowIndex][columnId].formatted
          ? data[rowIndex][columnId].formatted
          : data[rowIndex][columnId];
      }

      return data.hasOwnProperty(rowIndex)
        ? getFormatted(rowIndex, columnId)
        : null;
    };
  }, []);
```

The sample code above formats the `amount` column in the example `EuiDataGrid` with a green `backgroundColor` based on the value of the data, as shown in the screenshot below:

<img width="956" alt="datagrid-cell-formatting" src="https://user-images.githubusercontent.com/4459398/113348300-a782af80-92f3-11eb-896a-3d92cf4b9b53.png">

To demonstrate that similar styling can be applied to TGrid using the same technique illustrated by `EuiDataGrid`'s [codesandbox example](https://codesandbox.io/s/nsmzs), we can update the `DefaultCellRenderer` in `x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx` to apply a similar technique:

```
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
  columnId,
  data,
  eventId,
  header,
  linkValues,
  setCellProps,
  timelineId,
}) => {
  useEffect(() => {
    if (columnId === 'signal.rule.risk_score') {
      const value = getMappedNonEcsValue({
        data,
        fieldName: columnId,
      });
      if (Array.isArray(value) && value.length > 0) {
        const numeric = parseFloat(value[0]);
        setCellProps({
          style: {
            backgroundColor: `rgba(0, 255, 0, ${numeric * 0.002})`,
          },
        });
      }
    }
  }, [columnId, data, setCellProps]);

  return (
    <>
      {getMappedNonEcsValue({
        data,
        fieldName: columnId,
      })}
    </>
  );
};
```

The example code above renders the  `signal.rule.risk_score` column in the Alerts table with a green `backgroundColor` based on the value of the data, as shown in the screenshot below:

<img width="1231" alt="alerts" src="https://user-images.githubusercontent.com/4459398/113349015-a30ac680-92f4-11eb-8518-5c1b7465e76e.png">

Note: In the screenshot above, the values in the Alerts table are not rendered as draggables.
andrew-goldstein added a commit that referenced this issue Apr 5, 2021
…lement `renderCellValue` (#96098) (#96233)

### [RAC][Alert Triage][TGrid] Update the Alerts Table (TGrid) API to implement `renderCellValue`

- This PR implements a superset of the `renderCellValue` API from [EuiDataGrid](https://elastic.github.io/eui/#/tabular-content/data-grid) in the `TGrid` (Timeline grid) API

- The TGrid API was also updated to accept a collection of `RowRenderer`s as a prop

The API changes are summarized by the following screenshot:

<img width="1239" alt="render-cell-value" src="https://user-images.githubusercontent.com/4459398/113345484-c121f800-92ef-11eb-8a21-2b6dd8ef499b.png">

The following screenshot shows the `signal.rule.risk_score` column in the Alerts table being rendered with a green background color, using the same technique illustrated by `EuiDataGrid`'s [codesandbox example](https://codesandbox.io/s/nsmzs):

<img width="1231" alt="alerts" src="https://user-images.githubusercontent.com/4459398/113349015-a30ac680-92f4-11eb-8518-5c1b7465e76e.png">

Note: In the screenshot above, the values in the Alerts table are also _not_ rendered as draggables.

Related (RAC) issue: #94520

### Details

The `StatefulEventsViewer` has been updated to accept `renderCellValue` as a (required) prop:

```
renderCellValue: (props: CellValueElementProps) => React.ReactNode;
```

The type definition of `CellValueElementProps` is:

```
export type CellValueElementProps = EuiDataGridCellValueElementProps & {
  data: TimelineNonEcsData[];
  eventId: string; // _id
  header: ColumnHeaderOptions;
  linkValues: string[] | undefined;
  timelineId: string;
};
```

The `CellValueElementProps` type above is a _superset_ of `EuiDataGridCellValueElementProps`. The additional properties above include the `data` returned by the TGrid when it performs IO to retrieve alerts and events.

### Using `renderCellValue` to control rendering

The internal implementation of TGrid's cell rendering didn't change with this PR; it moved to

`x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx` as shown below:

```
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
  columnId,
  data,
  eventId,
  header,
  linkValues,
  setCellProps,
  timelineId,
}) => (
  <>
    {getColumnRenderer(header.id, columnRenderers, data).renderColumn({
      columnName: header.id,
      eventId,
      field: header,
      linkValues,
      timelineId,
      truncate: true,
      values: getMappedNonEcsValue({
        data,
        fieldName: header.id,
      }),
    })}
  </>
);
```

Any usages of TGrid were updated to pass `DefaultCellRenderer` as the value of the `renderCellValue` prop, as shown in the screenshot below:

<img width="1239" alt="render-cell-value" src="https://user-images.githubusercontent.com/4459398/113345484-c121f800-92ef-11eb-8a21-2b6dd8ef499b.png">

The `EuiDataGrid` [codesandbox example](https://codesandbox.io/s/nsmzs) provides the following example `renderCellValue` implementation, which highlights a cell green based on it's numeric value:

```
  const renderCellValue = useMemo(() => {
    return ({ rowIndex, columnId, setCellProps }) => {
      const data = useContext(DataContext);
      useEffect(() => {
        if (columnId === 'amount') {
          if (data.hasOwnProperty(rowIndex)) {
            const numeric = parseFloat(
              data[rowIndex][columnId].match(/\d+\.\d+/)[0],
              10
            );
            setCellProps({
              style: {
                backgroundColor: `rgba(0, 255, 0, ${numeric * 0.0002})`,
              },
            });
          }
        }
      }, [rowIndex, columnId, setCellProps, data]);

      function getFormatted() {
        return data[rowIndex][columnId].formatted
          ? data[rowIndex][columnId].formatted
          : data[rowIndex][columnId];
      }

      return data.hasOwnProperty(rowIndex)
        ? getFormatted(rowIndex, columnId)
        : null;
    };
  }, []);
```

The sample code above formats the `amount` column in the example `EuiDataGrid` with a green `backgroundColor` based on the value of the data, as shown in the screenshot below:

<img width="956" alt="datagrid-cell-formatting" src="https://user-images.githubusercontent.com/4459398/113348300-a782af80-92f3-11eb-896a-3d92cf4b9b53.png">

To demonstrate that similar styling can be applied to TGrid using the same technique illustrated by `EuiDataGrid`'s [codesandbox example](https://codesandbox.io/s/nsmzs), we can update the `DefaultCellRenderer` in `x-pack/plugins/security_solution/public/timelines/components/timeline/cell_rendering/default_cell_renderer.tsx` to apply a similar technique:

```
export const DefaultCellRenderer: React.FC<CellValueElementProps> = ({
  columnId,
  data,
  eventId,
  header,
  linkValues,
  setCellProps,
  timelineId,
}) => {
  useEffect(() => {
    if (columnId === 'signal.rule.risk_score') {
      const value = getMappedNonEcsValue({
        data,
        fieldName: columnId,
      });
      if (Array.isArray(value) && value.length > 0) {
        const numeric = parseFloat(value[0]);
        setCellProps({
          style: {
            backgroundColor: `rgba(0, 255, 0, ${numeric * 0.002})`,
          },
        });
      }
    }
  }, [columnId, data, setCellProps]);

  return (
    <>
      {getMappedNonEcsValue({
        data,
        fieldName: columnId,
      })}
    </>
  );
};
```

The example code above renders the  `signal.rule.risk_score` column in the Alerts table with a green `backgroundColor` based on the value of the data, as shown in the screenshot below:

<img width="1231" alt="alerts" src="https://user-images.githubusercontent.com/4459398/113349015-a30ac680-92f4-11eb-8518-5c1b7465e76e.png">

Note: In the screenshot above, the values in the Alerts table are not rendered as draggables.

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
@andrew-goldstein
Copy link
Contributor Author

#98241 updates the TGrid API to implement a subset of the EuiDataGridColumn API to render the example column configuration shown in the screenshot below:

observability_alerts_example

@kobelb kobelb added the needs-team Issues missing a team label label Jan 31, 2022
@botelastic botelastic bot removed the needs-team Issues missing a team label label Jan 31, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:Detection Alerts Security Solution Detection Alerts Feature Feature:Timeline Security Solution Timeline feature Team:Detections and Resp Security Detection Response Team Team:Observability Team label for Observability Team (for things that are handled across all of observability) Team:ResponseOps Label for the ResponseOps team (formerly the Cases and Alerting teams) Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:Threat Hunting Security Solution Threat Hunting Team Theme: rac label obsolete
Projects
None yet
Development

No branches or pull requests

3 participants