[RAC] [o11y] add permission in alerts table from kibana privilege/consumer (#109759)
* add alert permission in o11y * review I * review II * fix selection all when checkbox disabled * fix selected on bulk actions
This commit is contained in:
parent
a161c2b7d8
commit
6a1a38b346
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { RecursiveReadonly } from '@kbn/utility-types';
|
||||
import { Capabilities } from '../../../../../src/core/types';
|
||||
|
||||
export interface UseGetUserAlertsPermissionsProps {
|
||||
crud: boolean;
|
||||
|
@ -15,8 +16,29 @@ export interface UseGetUserAlertsPermissionsProps {
|
|||
featureId: string | null;
|
||||
}
|
||||
|
||||
export const getAlertsPermissions = (
|
||||
uiCapabilities: RecursiveReadonly<Capabilities>,
|
||||
featureId: string
|
||||
) => {
|
||||
if (!featureId || !uiCapabilities[featureId]) {
|
||||
return {
|
||||
crud: false,
|
||||
read: false,
|
||||
loading: false,
|
||||
featureId,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
crud: uiCapabilities[featureId].save as boolean,
|
||||
read: uiCapabilities[featureId].show as boolean,
|
||||
loading: false,
|
||||
featureId,
|
||||
};
|
||||
};
|
||||
|
||||
export const useGetUserAlertsPermissions = (
|
||||
uiCapabilities: RecursiveReadonly<Record<string, any>>,
|
||||
uiCapabilities: RecursiveReadonly<Capabilities>,
|
||||
featureId?: string
|
||||
): UseGetUserAlertsPermissionsProps => {
|
||||
const [alertsPermissions, setAlertsPermissions] = useState<UseGetUserAlertsPermissionsProps>({
|
||||
|
@ -39,20 +61,7 @@ export const useGetUserAlertsPermissions = (
|
|||
if (currentAlertPermissions.featureId === featureId) {
|
||||
return currentAlertPermissions;
|
||||
}
|
||||
const capabilitiesCanUserCRUD: boolean =
|
||||
typeof uiCapabilities[featureId].save === 'boolean'
|
||||
? uiCapabilities[featureId].save
|
||||
: false;
|
||||
const capabilitiesCanUserRead: boolean =
|
||||
typeof uiCapabilities[featureId].show === 'boolean'
|
||||
? uiCapabilities[featureId].show
|
||||
: false;
|
||||
return {
|
||||
crud: capabilitiesCanUserCRUD,
|
||||
read: capabilitiesCanUserRead,
|
||||
loading: false,
|
||||
featureId,
|
||||
};
|
||||
return getAlertsPermissions(uiCapabilities, featureId);
|
||||
});
|
||||
}
|
||||
}, [alertsPermissions.featureId, featureId, uiCapabilities]);
|
||||
|
|
|
@ -40,7 +40,10 @@ import { i18n } from '@kbn/i18n';
|
|||
import styled from 'styled-components';
|
||||
import React, { Suspense, useMemo, useState, useCallback } from 'react';
|
||||
import { get } from 'lodash';
|
||||
import { useGetUserAlertsPermissions } from '../../hooks/use_alert_permission';
|
||||
import {
|
||||
getAlertsPermissions,
|
||||
useGetUserAlertsPermissions,
|
||||
} from '../../hooks/use_alert_permission';
|
||||
import type { TimelinesUIStart, TGridType, SortDirection } from '../../../../timelines/public';
|
||||
import { useStatusBulkActionItems } from '../../../../timelines/public';
|
||||
import type { TopAlert } from './';
|
||||
|
@ -279,12 +282,22 @@ function ObservabilityActions({
|
|||
|
||||
export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
||||
const { indexNames, rangeFrom, rangeTo, kuery, workflowStatus, setRefetch, addToQuery } = props;
|
||||
const { timelines } = useKibana<{ timelines: TimelinesUIStart }>().services;
|
||||
const {
|
||||
timelines,
|
||||
application: { capabilities },
|
||||
} = useKibana<CoreStart & { timelines: TimelinesUIStart }>().services;
|
||||
|
||||
const [flyoutAlert, setFlyoutAlert] = useState<TopAlert | undefined>(undefined);
|
||||
|
||||
const casePermissions = useGetUserCasesPermissions();
|
||||
|
||||
const hasAlertsCrudPermissions = useCallback(
|
||||
(featureId: string) => {
|
||||
return getAlertsPermissions(capabilities, featureId).crud;
|
||||
},
|
||||
[capabilities]
|
||||
);
|
||||
|
||||
const leadingControlColumns = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
|
@ -324,6 +337,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
defaultCellActions: getDefaultCellActions({ addToQuery }),
|
||||
end: rangeTo,
|
||||
filters: [],
|
||||
hasAlertsCrudPermissions,
|
||||
indexNames,
|
||||
itemsPerPageOptions: [10, 25, 50],
|
||||
loadingText: i18n.translate('xpack.observability.alertsTable.loadingTextLabel', {
|
||||
|
@ -358,14 +372,15 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
|
|||
};
|
||||
}, [
|
||||
casePermissions,
|
||||
indexNames,
|
||||
kuery,
|
||||
leadingControlColumns,
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
setRefetch,
|
||||
workflowStatus,
|
||||
addToQuery,
|
||||
rangeTo,
|
||||
hasAlertsCrudPermissions,
|
||||
indexNames,
|
||||
workflowStatus,
|
||||
kuery,
|
||||
rangeFrom,
|
||||
setRefetch,
|
||||
leadingControlColumns,
|
||||
]);
|
||||
const handleFlyoutClose = () => setFlyoutAlert(undefined);
|
||||
const { observabilityRuleTypeRegistry } = usePluginContext();
|
||||
|
|
|
@ -30,6 +30,7 @@ export interface TimelineNonEcsData {
|
|||
}
|
||||
|
||||
export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse {
|
||||
consumers: Record<string, number>;
|
||||
edges: TimelineEdges[];
|
||||
totalCount: number;
|
||||
pageInfo: Pick<PaginationInputPaginated, 'activePage' | 'querySize'>;
|
||||
|
|
|
@ -20,6 +20,7 @@ export interface ActionProps {
|
|||
columnId: string;
|
||||
columnValues: string;
|
||||
checked: boolean;
|
||||
disabled?: boolean;
|
||||
onRowSelected: OnRowSelected;
|
||||
eventId: string;
|
||||
loadingEventIds: Readonly<string[]>;
|
||||
|
|
|
@ -16,15 +16,19 @@ export const RowCheckBox = ({
|
|||
checked,
|
||||
ariaRowindex,
|
||||
columnValues,
|
||||
disabled,
|
||||
loadingEventIds,
|
||||
}: ActionProps) => {
|
||||
const handleSelectEvent = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onRowSelected({
|
||||
eventIds: [eventId],
|
||||
isSelected: event.currentTarget.checked,
|
||||
}),
|
||||
[eventId, onRowSelected]
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (!disabled) {
|
||||
onRowSelected({
|
||||
eventIds: [eventId],
|
||||
isSelected: event.currentTarget.checked,
|
||||
});
|
||||
}
|
||||
},
|
||||
[eventId, onRowSelected, disabled]
|
||||
);
|
||||
|
||||
return loadingEventIds.includes(eventId) ? (
|
||||
|
@ -33,7 +37,8 @@ export const RowCheckBox = ({
|
|||
<EuiCheckbox
|
||||
data-test-subj="select-event"
|
||||
id={eventId}
|
||||
checked={checked}
|
||||
checked={checked && !disabled}
|
||||
disabled={disabled}
|
||||
onChange={handleSelectEvent}
|
||||
aria-label={i18n.CHECKBOX_FOR_ROW({ ariaRowindex, columnValues, checked })}
|
||||
/>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
|
||||
import { EuiDataGridCellValueElementProps } from '@elastic/eui';
|
||||
|
@ -39,12 +40,24 @@ export const stringifyEvent = (ecs: Ecs): string => JSON.stringify(ecs, omitType
|
|||
export const getEventIdToDataMapping = (
|
||||
timelineData: TimelineItem[],
|
||||
eventIds: string[],
|
||||
fieldsToKeep: string[]
|
||||
fieldsToKeep: string[],
|
||||
hasAlertsCrud: boolean,
|
||||
hasAlertsCrudPermissionsByFeatureId?: (featureId: string) => boolean
|
||||
): Record<string, TimelineNonEcsData[]> =>
|
||||
timelineData.reduce((acc, v) => {
|
||||
const fvm = eventIds.includes(v._id)
|
||||
? { [v._id]: v.data.filter((ti) => fieldsToKeep.includes(ti.field)) }
|
||||
: {};
|
||||
// FUTURE DEVELOPER
|
||||
// We only have one featureId for security solution therefore we can just use hasAlertsCrud
|
||||
// but for o11y we can multiple featureIds so we need to check every consumer
|
||||
// of the alert to see if they have the permission to update the alert
|
||||
const alertConsumers = v.data.find((d) => d.field === ALERT_RULE_CONSUMER)?.value ?? [];
|
||||
const hasPermissions = hasAlertsCrudPermissionsByFeatureId
|
||||
? alertConsumers.some((consumer) => hasAlertsCrudPermissionsByFeatureId(consumer))
|
||||
: hasAlertsCrud;
|
||||
|
||||
const fvm =
|
||||
hasPermissions && eventIds.includes(v._id)
|
||||
? { [v._id]: v.data.filter((ti) => fieldsToKeep.includes(ti.field)) }
|
||||
: {};
|
||||
return {
|
||||
...acc,
|
||||
...fvm,
|
||||
|
|
|
@ -32,6 +32,7 @@ import React, {
|
|||
import { connect, ConnectedProps, useDispatch } from 'react-redux';
|
||||
|
||||
import { ThemeContext } from 'styled-components';
|
||||
import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
|
||||
import {
|
||||
TGridCellAction,
|
||||
BulkActionsProp,
|
||||
|
@ -103,6 +104,8 @@ interface OwnProps {
|
|||
trailingControlColumns?: ControlColumnProps[];
|
||||
unit?: (total: number) => React.ReactNode;
|
||||
hasAlertsCrud?: boolean;
|
||||
hasAlertsCrudPermissions?: (featureId: string) => boolean;
|
||||
totalSelectAllAlerts?: number;
|
||||
}
|
||||
|
||||
const defaultUnit = (n: number) => i18n.ALERTS_UNIT(n);
|
||||
|
@ -143,6 +146,7 @@ const transformControlColumns = ({
|
|||
theme,
|
||||
setEventsLoading,
|
||||
setEventsDeleted,
|
||||
hasAlertsCrudPermissions,
|
||||
}: {
|
||||
actionColumnsWidth: number;
|
||||
columnHeaders: ColumnHeaderOptions[];
|
||||
|
@ -163,6 +167,7 @@ const transformControlColumns = ({
|
|||
theme: EuiTheme;
|
||||
setEventsLoading: SetEventsLoading;
|
||||
setEventsDeleted: SetEventsDeleted;
|
||||
hasAlertsCrudPermissions?: (featureId: string) => boolean;
|
||||
}): EuiDataGridControlColumn[] =>
|
||||
controlColumns.map(
|
||||
({ id: columnId, headerCellRender = EmptyHeaderCellRender, rowCellRender, width }, i) => ({
|
||||
|
@ -200,6 +205,12 @@ const transformControlColumns = ({
|
|||
setCellProps,
|
||||
}: EuiDataGridCellValueElementProps) => {
|
||||
addBuildingBlockStyle(data[rowIndex].ecs, theme, setCellProps);
|
||||
let disabled = false;
|
||||
if (columnId === 'checkbox-control-column' && hasAlertsCrudPermissions != null) {
|
||||
const alertConsumers =
|
||||
data[rowIndex].data.find((d) => d.field === ALERT_RULE_CONSUMER)?.value ?? [];
|
||||
disabled = alertConsumers.some((consumer) => !hasAlertsCrudPermissions(consumer));
|
||||
}
|
||||
|
||||
return (
|
||||
<RowAction
|
||||
|
@ -207,6 +218,7 @@ const transformControlColumns = ({
|
|||
columnHeaders={columnHeaders}
|
||||
controlColumn={controlColumns[i]}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
index={i}
|
||||
isDetails={isDetails}
|
||||
isExpanded={isExpanded}
|
||||
|
@ -275,6 +287,8 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
trailingControlColumns = EMPTY_CONTROL_COLUMNS,
|
||||
unit = defaultUnit,
|
||||
hasAlertsCrud,
|
||||
hasAlertsCrudPermissions,
|
||||
totalSelectAllAlerts,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const getManageTimeline = useMemo(() => tGridSelectors.getManageTimelineById(), []);
|
||||
|
@ -294,12 +308,18 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
({ eventIds, isSelected }: { eventIds: string[]; isSelected: boolean }) => {
|
||||
setSelected({
|
||||
id,
|
||||
eventIds: getEventIdToDataMapping(data, eventIds, queryFields),
|
||||
eventIds: getEventIdToDataMapping(
|
||||
data,
|
||||
eventIds,
|
||||
queryFields,
|
||||
hasAlertsCrud ?? false,
|
||||
hasAlertsCrudPermissions
|
||||
),
|
||||
isSelected,
|
||||
isSelectAllChecked: isSelected && selectedCount + 1 === data.length,
|
||||
});
|
||||
},
|
||||
[setSelected, id, data, selectedCount, queryFields]
|
||||
[setSelected, id, data, queryFields, hasAlertsCrud, hasAlertsCrudPermissions, selectedCount]
|
||||
);
|
||||
|
||||
const onSelectPage: OnSelectAll = useCallback(
|
||||
|
@ -310,13 +330,15 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
eventIds: getEventIdToDataMapping(
|
||||
data,
|
||||
data.map((event) => event._id),
|
||||
queryFields
|
||||
queryFields,
|
||||
hasAlertsCrud ?? false,
|
||||
hasAlertsCrudPermissions
|
||||
),
|
||||
isSelected,
|
||||
isSelectAllChecked: isSelected,
|
||||
})
|
||||
: clearSelected({ id }),
|
||||
[setSelected, clearSelected, id, data, queryFields]
|
||||
[setSelected, id, data, queryFields, hasAlertsCrud, hasAlertsCrudPermissions, clearSelected]
|
||||
);
|
||||
|
||||
// Sync to selectAll so parent components can select all events
|
||||
|
@ -363,7 +385,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
<StatefulAlertStatusBulkActions
|
||||
data-test-subj="bulk-actions"
|
||||
id={id}
|
||||
totalItems={totalItems}
|
||||
totalItems={totalSelectAllAlerts ?? totalItems}
|
||||
filterStatus={filterStatus}
|
||||
query={filterQuery}
|
||||
indexName={indexNames.join()}
|
||||
|
@ -386,6 +408,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
refetch,
|
||||
showBulkActions,
|
||||
totalItems,
|
||||
totalSelectAllAlerts,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -400,7 +423,7 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
<StatefulAlertStatusBulkActions
|
||||
data-test-subj="bulk-actions"
|
||||
id={id}
|
||||
totalItems={totalItems}
|
||||
totalItems={totalSelectAllAlerts ?? totalItems}
|
||||
filterStatus={filterStatus}
|
||||
query={filterQuery}
|
||||
indexName={indexNames.join()}
|
||||
|
@ -438,19 +461,20 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
showStyleSelector: false,
|
||||
}),
|
||||
[
|
||||
id,
|
||||
alertCountText,
|
||||
showBulkActions,
|
||||
id,
|
||||
totalSelectAllAlerts,
|
||||
totalItems,
|
||||
filterStatus,
|
||||
filterQuery,
|
||||
browserFields,
|
||||
indexNames,
|
||||
columnHeaders,
|
||||
additionalControls,
|
||||
showBulkActions,
|
||||
onAlertStatusActionSuccess,
|
||||
onAlertStatusActionFailure,
|
||||
refetch,
|
||||
additionalControls,
|
||||
browserFields,
|
||||
columnHeaders,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -544,28 +568,30 @@ export const BodyComponent = React.memo<StatefulBodyProps>(
|
|||
theme,
|
||||
setEventsLoading,
|
||||
setEventsDeleted,
|
||||
hasAlertsCrudPermissions,
|
||||
})
|
||||
);
|
||||
}, [
|
||||
showCheckboxes,
|
||||
leadingControlColumns,
|
||||
trailingControlColumns,
|
||||
columnHeaders,
|
||||
data,
|
||||
id,
|
||||
isEventViewer,
|
||||
leadingControlColumns,
|
||||
id,
|
||||
loadingEventIds,
|
||||
onRowSelected,
|
||||
onRuleChange,
|
||||
selectedEventIds,
|
||||
showCheckboxes,
|
||||
tabType,
|
||||
trailingControlColumns,
|
||||
isSelectAllChecked,
|
||||
sort,
|
||||
browserFields,
|
||||
onSelectPage,
|
||||
sort,
|
||||
theme,
|
||||
setEventsLoading,
|
||||
setEventsDeleted,
|
||||
hasAlertsCrudPermissions,
|
||||
]);
|
||||
|
||||
const columnsWithCellActions: EuiDataGridColumn[] = useMemo(
|
||||
|
|
|
@ -26,6 +26,7 @@ type Props = EuiDataGridCellValueElementProps & {
|
|||
columnHeaders: ColumnHeaderOptions[];
|
||||
controlColumn: ControlColumnProps;
|
||||
data: TimelineItem[];
|
||||
disabled: boolean;
|
||||
index: number;
|
||||
isEventViewer: boolean;
|
||||
loadingEventIds: Readonly<string[]>;
|
||||
|
@ -44,6 +45,7 @@ const RowActionComponent = ({
|
|||
columnHeaders,
|
||||
controlColumn,
|
||||
data,
|
||||
disabled,
|
||||
index,
|
||||
isEventViewer,
|
||||
loadingEventIds,
|
||||
|
@ -114,6 +116,7 @@ const RowActionComponent = ({
|
|||
columnValues={columnValues}
|
||||
data={timelineNonEcsData}
|
||||
data-test-subj="actions"
|
||||
disabled={disabled}
|
||||
ecsData={ecsData}
|
||||
eventId={eventId}
|
||||
index={index}
|
||||
|
|
|
@ -93,6 +93,7 @@ export interface TGridStandaloneProps {
|
|||
filters: Filter[];
|
||||
footerText: React.ReactNode;
|
||||
filterStatus: AlertStatus;
|
||||
hasAlertsCrudPermissions: (featureId: string) => boolean;
|
||||
height?: number;
|
||||
indexNames: string[];
|
||||
itemsPerPageOptions: number[];
|
||||
|
@ -124,6 +125,7 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
|||
filters,
|
||||
footerText,
|
||||
filterStatus,
|
||||
hasAlertsCrudPermissions,
|
||||
indexNames,
|
||||
itemsPerPageOptions,
|
||||
onRuleChange,
|
||||
|
@ -202,7 +204,7 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
|||
|
||||
const [
|
||||
loading,
|
||||
{ events, updatedAt, loadPage, pageInfo, refetch, totalCount = 0, inspect },
|
||||
{ consumers, events, updatedAt, loadPage, pageInfo, refetch, totalCount = 0, inspect },
|
||||
] = useTimelineEvents({
|
||||
docValueFields: [],
|
||||
entityType,
|
||||
|
@ -220,6 +222,27 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
|||
});
|
||||
setRefetch(refetch);
|
||||
|
||||
const { hasAlertsCrud, totalSelectAllAlerts } = useMemo(() => {
|
||||
return Object.entries(consumers).reduce<{
|
||||
hasAlertsCrud: boolean;
|
||||
totalSelectAllAlerts: number;
|
||||
}>(
|
||||
(acc, [featureId, nbrAlerts]) => {
|
||||
const featureHasPermission = hasAlertsCrudPermissions(featureId);
|
||||
return {
|
||||
hasAlertsCrud: featureHasPermission || acc.hasAlertsCrud,
|
||||
totalSelectAllAlerts: featureHasPermission
|
||||
? nbrAlerts + acc.totalSelectAllAlerts
|
||||
: acc.totalSelectAllAlerts,
|
||||
};
|
||||
},
|
||||
{
|
||||
hasAlertsCrud: false,
|
||||
totalSelectAllAlerts: 0,
|
||||
}
|
||||
);
|
||||
}, [consumers, hasAlertsCrudPermissions]);
|
||||
|
||||
const totalCountMinusDeleted = useMemo(
|
||||
() => (totalCount > 0 ? totalCount - deletedEventIds.length : 0),
|
||||
[deletedEventIds.length, totalCount]
|
||||
|
@ -322,6 +345,8 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
|||
data={nonDeletedEvents}
|
||||
defaultCellActions={defaultCellActions}
|
||||
filterQuery={filterQuery}
|
||||
hasAlertsCrud={hasAlertsCrud}
|
||||
hasAlertsCrudPermissions={hasAlertsCrudPermissions}
|
||||
id={STANDALONE_ID}
|
||||
indexNames={indexNames}
|
||||
isEventViewer={true}
|
||||
|
@ -340,6 +365,7 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
|
|||
itemsPerPage: itemsPerPageStore,
|
||||
})}
|
||||
totalItems={totalCountMinusDeleted}
|
||||
totalSelectAllAlerts={totalSelectAllAlerts}
|
||||
unit={unit}
|
||||
filterStatus={filterStatus}
|
||||
trailingControlColumns={trailingControlColumns}
|
||||
|
|
|
@ -51,6 +51,7 @@ export const detectionsTimelineIds = [
|
|||
type Refetch = () => void;
|
||||
|
||||
export interface TimelineArgs {
|
||||
consumers: Record<string, number>;
|
||||
events: TimelineItem[];
|
||||
id: string;
|
||||
inspect: InspectResponse;
|
||||
|
@ -170,6 +171,7 @@ export const useTimelineEvents = ({
|
|||
);
|
||||
|
||||
const [timelineResponse, setTimelineResponse] = useState<TimelineArgs>({
|
||||
consumers: {},
|
||||
id,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
|
@ -215,6 +217,7 @@ export const useTimelineEvents = ({
|
|||
setTimelineResponse((prevResponse) => {
|
||||
const newTimelineResponse = {
|
||||
...prevResponse,
|
||||
consumers: response.consumers,
|
||||
events: getTimelineEvents(response.edges),
|
||||
inspect: getInspectResponse(response, prevResponse.inspect),
|
||||
pageInfo: response.pageInfo,
|
||||
|
@ -346,6 +349,7 @@ export const useTimelineEvents = ({
|
|||
useEffect(() => {
|
||||
if (isEmpty(filterQuery)) {
|
||||
setTimelineResponse({
|
||||
consumers: {},
|
||||
id,
|
||||
inspect: {
|
||||
dsl: [],
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
|
||||
// import { CTI_ROW_RENDERER_FIELDS } from '../../../../../../common/cti/constants';
|
||||
|
||||
// TODO: share with security_solution/common/cti/constants.ts
|
||||
|
@ -40,6 +41,7 @@ export const CTI_ROW_RENDERER_FIELDS = [
|
|||
];
|
||||
|
||||
export const TIMELINE_EVENTS_FIELDS = [
|
||||
ALERT_RULE_CONSUMER,
|
||||
'@timestamp',
|
||||
'signal.status',
|
||||
'signal.group.id',
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
import { cloneDeep, getOr } from 'lodash/fp';
|
||||
import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants';
|
||||
import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common';
|
||||
import {
|
||||
|
@ -38,6 +38,7 @@ export const timelineEventsAll: TimelineFactory<TimelineEventsQueries.all> = {
|
|||
let { fieldRequested, ...queryOptions } = cloneDeep(options);
|
||||
queryOptions.fields = buildFieldsRequest(fieldRequested, queryOptions.excludeEcsData);
|
||||
const { activePage, querySize } = options.pagination;
|
||||
const buckets = getOr([], 'aggregations.consumers.buckets', response.rawResponse);
|
||||
const totalCount = response.rawResponse.hits.total || 0;
|
||||
const hits = response.rawResponse.hits.hits;
|
||||
|
||||
|
@ -61,12 +62,21 @@ export const timelineEventsAll: TimelineFactory<TimelineEventsQueries.all> = {
|
|||
)
|
||||
);
|
||||
|
||||
const consumers = buckets.reduce(
|
||||
(acc: Record<string, number>, b: { key: string; doc_count: number }) => ({
|
||||
...acc,
|
||||
[b.key]: b.doc_count,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
|
||||
const inspect = {
|
||||
dsl: [inspectStringifyObject(buildTimelineEventsAllQuery(queryOptions))],
|
||||
};
|
||||
|
||||
return {
|
||||
...response,
|
||||
consumers,
|
||||
inspect,
|
||||
edges,
|
||||
// @ts-expect-error code doesn't handle TotalHits
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
|
||||
import {
|
||||
|
@ -67,6 +68,11 @@ export const buildTimelineEventsAllQuery = ({
|
|||
ignoreUnavailable: true,
|
||||
body: {
|
||||
...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}),
|
||||
aggregations: {
|
||||
consumers: {
|
||||
terms: { field: ALERT_RULE_CONSUMER },
|
||||
},
|
||||
},
|
||||
query: {
|
||||
bool: {
|
||||
filter,
|
||||
|
|
|
@ -54,6 +54,8 @@ const AppRoot = React.memo(
|
|||
refetch.current = _refetch;
|
||||
}, []);
|
||||
|
||||
const hasAlertsCrudPermissions = useCallback(() => true, []);
|
||||
|
||||
return (
|
||||
<I18nProvider>
|
||||
<Router history={parameters.history}>
|
||||
|
@ -73,6 +75,7 @@ const AppRoot = React.memo(
|
|||
end: '',
|
||||
footerText: 'Events',
|
||||
filters: [],
|
||||
hasAlertsCrudPermissions,
|
||||
itemsPerPageOptions: [1, 2, 3],
|
||||
loadingText: 'Loading events',
|
||||
renderCellValue: () => <div data-test-subj="timeline-wrapper">test</div>,
|
||||
|
|
Loading…
Reference in a new issue