Skip to content

Commit

Permalink
[RAC] Enable workflow status filtering (#108215)
Browse files Browse the repository at this point in the history
Co-authored-by: Jason Rhodes <jason.matthew.rhodes@gmail.com>
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
3 people committed Aug 20, 2021
1 parent 61a993b commit 50f72f5
Show file tree
Hide file tree
Showing 16 changed files with 162 additions and 156 deletions.
8 changes: 6 additions & 2 deletions x-pack/plugins/observability/common/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@ import * as t from 'io-ts';

export type Maybe<T> = T | null | undefined;

export const alertStatusRt = t.union([t.literal('all'), t.literal('open'), t.literal('closed')]);
export type AlertStatus = t.TypeOf<typeof alertStatusRt>;
export const alertWorkflowStatusRt = t.keyof({
open: null,
acknowledged: null,
closed: null,
});
export type AlertWorkflowStatus = t.TypeOf<typeof alertWorkflowStatusRt>;

export interface ApmIndicesConfig {
/* eslint-disable @typescript-eslint/naming-convention */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,22 @@
import {
AlertConsumers as AlertConsumersTyped,
ALERT_DURATION as ALERT_DURATION_TYPED,
ALERT_STATUS as ALERT_STATUS_TYPED,
ALERT_REASON as ALERT_REASON_TYPED,
ALERT_RULE_CONSUMER,
ALERT_STATUS as ALERT_STATUS_TYPED,
ALERT_WORKFLOW_STATUS as ALERT_WORKFLOW_STATUS_TYPED,
} from '@kbn/rule-data-utils';
// @ts-expect-error importing from a place other than root because we want to limit what we import from this package
import { AlertConsumers as AlertConsumersNonTyped } from '@kbn/rule-data-utils/target_node/alerts_as_data_rbac';
import {
ALERT_DURATION as ALERT_DURATION_NON_TYPED,
ALERT_STATUS as ALERT_STATUS_NON_TYPED,
ALERT_REASON as ALERT_REASON_NON_TYPED,
ALERT_STATUS as ALERT_STATUS_NON_TYPED,
ALERT_WORKFLOW_STATUS as ALERT_WORKFLOW_STATUS_NON_TYPED,
TIMESTAMP,
// @ts-expect-error importing from a place other than root because we want to limit what we import from this package
} from '@kbn/rule-data-utils/target_node/technical_field_names';

// @ts-expect-error importing from a place other than root because we want to limit what we import from this package
import { AlertConsumers as AlertConsumersNonTyped } from '@kbn/rule-data-utils/target_node/alerts_as_data_rbac';

import {
EuiButtonIcon,
EuiDataGridColumn,
Expand All @@ -47,7 +48,7 @@ import type { TopAlert } from './';
import { useKibana } from '../../../../../../src/plugins/kibana_react/public';
import type {
ActionProps,
AlertStatus,
AlertWorkflowStatus,
ColumnHeaderOptions,
RowRenderer,
} from '../../../../timelines/common';
Expand All @@ -63,21 +64,22 @@ import { CoreStart } from '../../../../../../src/core/public';

const AlertConsumers: typeof AlertConsumersTyped = AlertConsumersNonTyped;
const ALERT_DURATION: typeof ALERT_DURATION_TYPED = ALERT_DURATION_NON_TYPED;
const ALERT_STATUS: typeof ALERT_STATUS_TYPED = ALERT_STATUS_NON_TYPED;
const ALERT_REASON: typeof ALERT_REASON_TYPED = ALERT_REASON_NON_TYPED;
const ALERT_STATUS: typeof ALERT_STATUS_TYPED = ALERT_STATUS_NON_TYPED;
const ALERT_WORKFLOW_STATUS: typeof ALERT_WORKFLOW_STATUS_TYPED = ALERT_WORKFLOW_STATUS_NON_TYPED;

interface AlertsTableTGridProps {
indexName: string;
rangeFrom: string;
rangeTo: string;
kuery: string;
status: string;
workflowStatus: AlertWorkflowStatus;
setRefetch: (ref: () => void) => void;
addToQuery: (value: string) => void;
}

interface ObservabilityActionsProps extends ActionProps {
currentStatus: AlertStatus;
currentStatus: AlertWorkflowStatus;
setFlyoutAlert: React.Dispatch<React.SetStateAction<TopAlert | undefined>>;
}

Expand Down Expand Up @@ -288,7 +290,7 @@ function ObservabilityActions({
}

export function AlertsTableTGrid(props: AlertsTableTGridProps) {
const { indexName, rangeFrom, rangeTo, kuery, status, setRefetch, addToQuery } = props;
const { indexName, rangeFrom, rangeTo, kuery, workflowStatus, setRefetch, addToQuery } = props;
const { timelines } = useKibana<{ timelines: TimelinesUIStart }>().services;

const [flyoutAlert, setFlyoutAlert] = useState<TopAlert | undefined>(undefined);
Expand All @@ -313,14 +315,14 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
return (
<ObservabilityActions
{...actionProps}
currentStatus={status as AlertStatus}
currentStatus={workflowStatus}
setFlyoutAlert={setFlyoutAlert}
/>
);
},
},
];
}, [status]);
}, [workflowStatus]);

const tGridProps = useMemo(() => {
const type: TGridType = 'standalone';
Expand All @@ -345,7 +347,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
defaultMessage: 'alerts',
}),
query: {
query: `${ALERT_STATUS}: ${status}${kuery !== '' ? ` and ${kuery}` : ''}`,
query: `${ALERT_WORKFLOW_STATUS}: ${workflowStatus}${kuery !== '' ? ` and ${kuery}` : ''}`,
language: 'kuery',
},
renderCellValue: getRenderCellValue({ rangeFrom, rangeTo, setFlyoutAlert }),
Expand All @@ -359,7 +361,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
sortDirection,
},
],
filterStatus: status as AlertStatus,
filterStatus: workflowStatus as AlertWorkflowStatus,
leadingControlColumns,
trailingControlColumns,
unit: (totalAlerts: number) =>
Expand All @@ -376,7 +378,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
rangeFrom,
rangeTo,
setRefetch,
status,
workflowStatus,
addToQuery,
]);
const handleFlyoutClose = () => setFlyoutAlert(undefined);
Expand Down
50 changes: 24 additions & 26 deletions x-pack/plugins/observability/public/pages/alerts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ import { i18n } from '@kbn/i18n';
import React, { useCallback, useMemo, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import { ParsedTechnicalFields } from '../../../../rule_registry/common/parse_technical_fields';
import type { AlertStatus } from '../../../common/typings';
import type { AlertWorkflowStatus } from '../../../common/typings';
import { ExperimentalBadge } from '../../components/shared/experimental_badge';
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
import { useFetcher } from '../../hooks/use_fetcher';
import { usePluginContext } from '../../hooks/use_plugin_context';
import { RouteParams } from '../../routes';
import { callObservabilityApi } from '../../services/call_observability_api';
import { AlertsSearchBar } from './alerts_search_bar';
import { AlertsTableTGrid } from './alerts_table_t_grid';
import { StatusFilter } from './status_filter';
import { useFetcher } from '../../hooks/use_fetcher';
import { callObservabilityApi } from '../../services/call_observability_api';
import { WorkflowStatusFilter } from './workflow_status_filter';
import './styles.scss';

export interface TopAlert {
Expand All @@ -44,7 +44,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
rangeFrom = 'now-15m',
rangeTo = 'now',
kuery = 'kibana.alert.status: "open"', // TODO change hardcoded values as part of another PR
status = 'open',
workflowStatus = 'open',
},
} = routeParams;

Expand Down Expand Up @@ -72,10 +72,10 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
[dynamicIndexPatternResp]
);

const setStatusFilter = useCallback(
(value: AlertStatus) => {
const setWorkflowStatusFilter = useCallback(
(value: AlertWorkflowStatus) => {
const nextSearchParams = new URLSearchParams(history.location.search);
nextSearchParams.set('status', value);
nextSearchParams.set('workflowStatus', value);
history.push({
...history.location,
search: nextSearchParams.toString(),
Expand Down Expand Up @@ -172,28 +172,26 @@ export function AlertsPage({ routeParams }: AlertsPageProps) {
onQueryChange={onQueryChange}
/>
</EuiFlexItem>

<EuiFlexItem>
<EuiFlexGroup direction="column">
<EuiFlexItem>
<EuiFlexGroup justifyContent="flexStart">
<EuiFlexItem grow={false}>
<StatusFilter status={status} onChange={setStatusFilter} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem>
<AlertsTableTGrid
indexName={dynamicIndexPattern.length > 0 ? dynamicIndexPattern[0].title : ''}
rangeFrom={rangeFrom}
rangeTo={rangeTo}
kuery={kuery}
status={status}
setRefetch={setRefetch}
addToQuery={addToQuery}
/>
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
<EuiFlexItem grow={false}>
<WorkflowStatusFilter status={workflowStatus} onChange={setWorkflowStatusFilter} />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>

<EuiFlexItem>
<AlertsTableTGrid
indexName={dynamicIndexPattern.length > 0 ? dynamicIndexPattern[0].title : ''}
rangeFrom={rangeFrom}
rangeTo={rangeTo}
kuery={kuery}
workflowStatus={workflowStatus}
setRefetch={setRefetch}
addToQuery={addToQuery}
/>
</EuiFlexItem>
</EuiFlexGroup>
</ObservabilityPageTemplate>
);
Expand Down
56 changes: 0 additions & 56 deletions x-pack/plugins/observability/public/pages/alerts/status_filter.tsx

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@
*/

import React, { ComponentProps, useState } from 'react';
import type { AlertStatus } from '../../../common/typings';
import { StatusFilter } from './status_filter';
import type { AlertWorkflowStatus } from '../../../common/typings';
import { WorkflowStatusFilter } from './workflow_status_filter';

type Args = ComponentProps<typeof StatusFilter>;
type Args = ComponentProps<typeof WorkflowStatusFilter>;

export default {
title: 'app/Alerts/StatusFilter',
component: StatusFilter,
component: WorkflowStatusFilter,
argTypes: {
onChange: { action: 'change' },
},
};

export function Example({ onChange }: Args) {
const [status, setStatus] = useState<AlertStatus>('open');
const [status, setStatus] = useState<AlertWorkflowStatus>('open');

return (
<StatusFilter
<WorkflowStatusFilter
status={status}
onChange={(value) => {
setStatus(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,27 @@

import { render } from '@testing-library/react';
import React from 'react';
import type { AlertStatus } from '../../../common/typings';
import { StatusFilter } from './status_filter';
import type { AlertWorkflowStatus } from '../../../common/typings';
import { WorkflowStatusFilter } from './workflow_status_filter';

describe('StatusFilter', () => {
describe('render', () => {
it('renders', () => {
const onChange = jest.fn();
const status: AlertStatus = 'all';
const status: AlertWorkflowStatus = 'open';
const props = { onChange, status };

expect(() => render(<StatusFilter {...props} />)).not.toThrowError();
expect(() => render(<WorkflowStatusFilter {...props} />)).not.toThrowError();
});

(['all', 'open', 'closed'] as AlertStatus[]).map((status) => {
(['open', 'acknowledged', 'closed'] as AlertWorkflowStatus[]).map((status) => {
describe(`when clicking the ${status} button`, () => {
it('calls the onChange callback with "${status}"', () => {
const onChange = jest.fn();
const props = { onChange, status };

const { getByTestId } = render(<StatusFilter {...props} />);
const button = getByTestId(`StatusFilter ${status} button`);
const { getByTestId } = render(<WorkflowStatusFilter {...props} />);
const button = getByTestId(`WorkflowStatusFilter ${status} button`);

button.click();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import React from 'react';
import type { AlertWorkflowStatus } from '../../../common/typings';

export interface WorkflowStatusFilterProps {
status: AlertWorkflowStatus;
onChange: (value: AlertWorkflowStatus) => void;
}

const options: Array<EuiButtonGroupOptionProps & { id: AlertWorkflowStatus }> = [
{
id: 'open',
label: i18n.translate('xpack.observability.alerts.workflowStatusFilter.openButtonLabel', {
defaultMessage: 'Open',
}),
'data-test-subj': 'WorkflowStatusFilter open button',
},
{
id: 'acknowledged',
label: i18n.translate(
'xpack.observability.alerts.workflowStatusFilter.acknowledgedButtonLabel',
{
defaultMessage: 'Acknowledged',
}
),
'data-test-subj': 'WorkflowStatusFilter acknowledged button',
},
{
id: 'closed',
label: i18n.translate('xpack.observability.alerts.workflowStatusFilter.closedButtonLabel', {
defaultMessage: 'Closed',
}),
'data-test-subj': 'WorkflowStatusFilter closed button',
},
];

export function WorkflowStatusFilter({ status = 'open', onChange }: WorkflowStatusFilterProps) {
return (
<EuiButtonGroup
legend="Filter by"
color="primary"
options={options}
idSelected={status}
onChange={(id) => onChange(id as AlertWorkflowStatus)}
/>
);
}
Loading

0 comments on commit 50f72f5

Please sign in to comment.