From f755898941d65d38a11cf10c7d20690f5f2262dc Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Tue, 4 May 2021 10:58:08 -0500 Subject: [PATCH 01/14] Active/Recovered filter for observability alerts page --- .../public/pages/alerts/index.tsx | 22 +++++++-- .../pages/alerts/status_filter.stories.tsx | 33 +++++++++++++ .../public/pages/alerts/status_filter.tsx | 47 +++++++++++++++++++ 3 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/alerts/status_filter.stories.tsx create mode 100644 x-pack/plugins/observability/public/pages/alerts/status_filter.tsx diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx index a6d5c6926973e9..85962a657b9cc8 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -12,9 +12,10 @@ import { EuiFlexItem, EuiLink, EuiPageTemplate, + EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React from 'react'; +import React, { useState } from 'react'; import { useHistory } from 'react-router-dom'; import { format, parse } from 'url'; import { asDuration, asPercent } from '../../../common/utils/formatters'; @@ -25,6 +26,7 @@ import { RouteParams } from '../../routes'; import { callObservabilityApi } from '../../services/call_observability_api'; import type { ObservabilityAPIReturnType } from '../../services/call_observability_api/types'; import { getAbsoluteDateRange } from '../../utils/date'; +import { StatusFilter, Status } from './status_filter'; import { AlertsSearchBar } from './alerts_search_bar'; import { AlertsTable } from './alerts_table'; @@ -105,6 +107,8 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { [kuery, observabilityRuleRegistry, rangeFrom, rangeTo] ); + const [statusFilter, setStatusFilter] = useState('all'); + return ( - - - + + + + + + + + + + + + + ); diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.stories.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.stories.tsx new file mode 100644 index 00000000000000..f522c0e3d112db --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.stories.tsx @@ -0,0 +1,33 @@ +/* + * 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, { ComponentProps, useState } from 'react'; +import { StatusFilter, Status } from './status_filter'; + +type Args = ComponentProps; + +export default { + title: 'app/Alerts/StatusFilter', + component: StatusFilter, + argTypes: { + onChange: { action: 'change' }, + }, +}; + +export function Example({ onChange }: Args) { + const [status, setStatus] = useState('open'); + + return ( + { + setStatus(value); + onChange(value); + }} + /> + ); +} diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx new file mode 100644 index 00000000000000..e271b24ad80d2b --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx @@ -0,0 +1,47 @@ +/* + * 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 { EuiFilterButton, EuiFilterGroup } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export type Status = 'all' | 'open' | 'closed'; + +export interface StatusFilterProps { + status: Status; + onChange: (value: Status) => void; +} + +export function StatusFilter({ status = 'open', onChange }: StatusFilterProps) { + return ( + + onChange('all')} + withNext={true} + > + {i18n.translate('xpack.observability.alerts.statusFilter.allButtonLabel', { + defaultMessage: 'All', + })} + + onChange('open')} + withNext={true} + > + {i18n.translate('xpack.observability.alerts.statusFilter.activeButtonLabel', { + defaultMessage: 'Active', + })} + + onChange('closed')}> + {i18n.translate('xpack.observability.alerts.statusFilter.recoveredButtonLabel', { + defaultMessage: 'Recovered', + })} + + + ); +} From 73b25179d87d843e3299deb97e1795e3cc7639c5 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Tue, 4 May 2021 14:21:11 -0500 Subject: [PATCH 02/14] Client-side routing --- .../observability/public/pages/alerts/index.tsx | 13 ++++++++++--- .../plugins/observability/public/routes/index.tsx | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx index 85962a657b9cc8..d24e8dcad28ca8 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -48,7 +48,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { const { prepend } = core.http.basePath; const history = useHistory(); const { - query: { rangeFrom = 'now-15m', rangeTo = 'now', kuery = '' }, + query: { rangeFrom = 'now-15m', rangeTo = 'now', kuery = '', status = 'all' }, } = routeParams; // In a future milestone we'll have a page dedicated to rule management in @@ -107,7 +107,14 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { [kuery, observabilityRuleRegistry, rangeFrom, rangeTo] ); - const [statusFilter, setStatusFilter] = useState('all'); + function setStatusFilter(value: Status) { + const nextSearchParams = new URLSearchParams(history.location.search); + nextSearchParams.set('status', value); + history.push({ + ...history.location, + search: nextSearchParams.toString(), + }); + } return ( - + diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 3e5c3ddc553efa..c6fed98aef6062 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -105,6 +105,7 @@ export const routes = { rangeFrom: t.string, rangeTo: t.string, kuery: t.string, + status: t.union([t.literal('all'), t.literal('open'), t.literal('closed')]), refreshPaused: jsonRt.pipe(t.boolean), refreshInterval: jsonRt.pipe(t.number), }), From a9a1c2b9a9aa4b6c2c9eab266653c6b08954f1af Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 24 May 2021 10:32:59 -0500 Subject: [PATCH 03/14] Add status to backend and jest tests --- .../public/pages/alerts/index.tsx | 11 +++--- .../pages/alerts/status_filter.test.tsx | 38 +++++++++++++++++++ .../public/pages/alerts/status_filter.tsx | 8 +++- .../observability/server/routes/rules.ts | 1 + 4 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 x-pack/plugins/observability/public/pages/alerts/status_filter.test.tsx diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx index b83f9d6916e6ed..0473f6e5e81dfb 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -15,15 +15,15 @@ import { EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import React, { useState } from 'react'; -import { useHistory } from 'react-router-dom'; -import { format, parse } from 'url'; import { ALERT_START, EVENT_ACTION, RULE_ID, RULE_NAME, } from '@kbn/rule-data-utils/target/technical_field_names'; +import React from 'react'; +import { useHistory } from 'react-router-dom'; +import { format, parse } from 'url'; import { ParsedTechnicalFields, parseTechnicalFields, @@ -36,9 +36,9 @@ import { RouteParams } from '../../routes'; import { callObservabilityApi } from '../../services/call_observability_api'; import type { ObservabilityAPIReturnType } from '../../services/call_observability_api/types'; import { getAbsoluteDateRange } from '../../utils/date'; -import { StatusFilter, Status } from './status_filter'; import { AlertsSearchBar } from './alerts_search_bar'; import { AlertsTable } from './alerts_table'; +import { Status, StatusFilter } from './status_filter'; export type TopAlertResponse = ObservabilityAPIReturnType<'GET /api/observability/rules/alerts/top'>[number]; @@ -83,6 +83,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { start, end, kuery, + status, }, }, }).then((alerts) => { @@ -116,7 +117,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { }); }); }, - [kuery, observabilityRuleTypeRegistry, rangeFrom, rangeTo] + [kuery, observabilityRuleTypeRegistry, rangeFrom, rangeTo, status] ); function setStatusFilter(value: Status) { diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.test.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.test.tsx new file mode 100644 index 00000000000000..20a47fd4757998 --- /dev/null +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.test.tsx @@ -0,0 +1,38 @@ +/* + * 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 { render } from '@testing-library/react'; +import React from 'react'; +import { StatusFilter, Status } from './status_filter'; + +describe('StatusFilter', () => { + describe('render', () => { + it('renders', () => { + const onChange = jest.fn(); + const status: Status = 'all'; + const props = { onChange, status }; + + expect(() => render()).not.toThrowError(); + }); + + (['all', 'open', 'closed'] as Status[]).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(); + const button = getByTestId(`StatusFilter ${status} button`); + + button.click(); + + expect(onChange).toHaveBeenCalledWith(status); + }); + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx index e271b24ad80d2b..3840407b4ac8cb 100644 --- a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx @@ -20,6 +20,7 @@ export function StatusFilter({ status = 'open', onChange }: StatusFilterProps) { return ( onChange('all')} withNext={true} @@ -29,6 +30,7 @@ export function StatusFilter({ status = 'open', onChange }: StatusFilterProps) { })} onChange('open')} withNext={true} @@ -37,7 +39,11 @@ export function StatusFilter({ status = 'open', onChange }: StatusFilterProps) { defaultMessage: 'Active', })} - onChange('closed')}> + onChange('closed')} + > {i18n.translate('xpack.observability.alerts.statusFilter.recoveredButtonLabel', { defaultMessage: 'Recovered', })} diff --git a/x-pack/plugins/observability/server/routes/rules.ts b/x-pack/plugins/observability/server/routes/rules.ts index 1f500adff5dcfe..a8f232085a296f 100644 --- a/x-pack/plugins/observability/server/routes/rules.ts +++ b/x-pack/plugins/observability/server/routes/rules.ts @@ -23,6 +23,7 @@ const alertsListRoute = createObservabilityServerRoute({ }), t.partial({ kuery: t.string, + status: t.string, size: toNumberRt, }), ]), From baeeb6cef6c8ec48cb683734e775655b35688de0 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 24 May 2021 11:02:06 -0500 Subject: [PATCH 04/14] Status types --- x-pack/plugins/observability/README.md | 38 +++++++++++++++++++ .../plugins/observability/common/typings.ts | 4 ++ .../public/pages/alerts/index.tsx | 5 ++- .../pages/alerts/status_filter.test.tsx | 7 ++-- .../public/pages/alerts/status_filter.tsx | 7 ++-- .../observability/server/routes/rules.ts | 7 ++-- 6 files changed, 56 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/observability/README.md b/x-pack/plugins/observability/README.md index b882891921cde4..70f61ffc288d38 100644 --- a/x-pack/plugins/observability/README.md +++ b/x-pack/plugins/observability/README.md @@ -47,3 +47,41 @@ HTML coverage report can be found in target/coverage/jest after tests have run. ```bash open target/coverage/jest/index.html ``` + +## API integration testing + +API tests are separated in two suites: + +- a basic license test suite +- a trial license test suite (the equivalent of gold+) + +This requires separate test servers and test runners. + +### Basic + +``` +# Start server +node scripts/functional_tests_server --config x-pack/test/observability_api_integration/basic/config.ts + +# Run tests +node scripts/functional_test_runner --config x-pack/test/observability_api_integration/basic/config.ts +``` + +The API tests for "basic" are located in `x-pack/test/observability_api_integration/basic/tests`. + +### Trial + +``` +# Start server +node scripts/functional_tests_server --config x-pack/test/observability_api_integration/trial/config.ts + +# Run tests +node scripts/functional_test_runner --config x-pack/test/observability_api_integration/trial/config.ts +``` + +The API tests for "trial" are located in `x-pack/test/observability_api_integration/trial/tests`. + +### API test tips + +- For debugging access Elasticsearch on http://localhost:9220` (elastic/changeme) +- To update snapshots append `--updateSnapshots` to the functional_test_runner command diff --git a/x-pack/plugins/observability/common/typings.ts b/x-pack/plugins/observability/common/typings.ts index 2a7f9edffc4afd..bd10543ef389bd 100644 --- a/x-pack/plugins/observability/common/typings.ts +++ b/x-pack/plugins/observability/common/typings.ts @@ -4,5 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import * as t from 'io-ts'; export type Maybe = T | null | undefined; + +export const alertStatusRt = t.union([t.literal('all'), t.literal('open'), t.literal('closed')]); +export type AlertStatus = t.TypeOf; diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx index 0473f6e5e81dfb..e8f0f6080f3908 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -28,6 +28,7 @@ import { ParsedTechnicalFields, parseTechnicalFields, } from '../../../../rule_registry/common/parse_technical_fields'; +import type { AlertStatus } from '../../../common/typings'; import { asDuration, asPercent } from '../../../common/utils/formatters'; import { ExperimentalBadge } from '../../components/shared/experimental_badge'; import { useFetcher } from '../../hooks/use_fetcher'; @@ -38,7 +39,7 @@ import type { ObservabilityAPIReturnType } from '../../services/call_observabili import { getAbsoluteDateRange } from '../../utils/date'; import { AlertsSearchBar } from './alerts_search_bar'; import { AlertsTable } from './alerts_table'; -import { Status, StatusFilter } from './status_filter'; +import { StatusFilter } from './status_filter'; export type TopAlertResponse = ObservabilityAPIReturnType<'GET /api/observability/rules/alerts/top'>[number]; @@ -120,7 +121,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { [kuery, observabilityRuleTypeRegistry, rangeFrom, rangeTo, status] ); - function setStatusFilter(value: Status) { + function setStatusFilter(value: AlertStatus) { const nextSearchParams = new URLSearchParams(history.location.search); nextSearchParams.set('status', value); history.push({ diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.test.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.test.tsx index 20a47fd4757998..72e07ebb8cadfd 100644 --- a/x-pack/plugins/observability/public/pages/alerts/status_filter.test.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.test.tsx @@ -7,19 +7,20 @@ import { render } from '@testing-library/react'; import React from 'react'; -import { StatusFilter, Status } from './status_filter'; +import type { AlertStatus } from '../../../common/typings'; +import { StatusFilter } from './status_filter'; describe('StatusFilter', () => { describe('render', () => { it('renders', () => { const onChange = jest.fn(); - const status: Status = 'all'; + const status: AlertStatus = 'all'; const props = { onChange, status }; expect(() => render()).not.toThrowError(); }); - (['all', 'open', 'closed'] as Status[]).map((status) => { + (['all', 'open', 'closed'] as AlertStatus[]).map((status) => { describe(`when clicking the ${status} button`, () => { it('calls the onChange callback with "${status}"', () => { const onChange = jest.fn(); diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx index 3840407b4ac8cb..506269fed1b1b7 100644 --- a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx @@ -8,12 +8,11 @@ import { EuiFilterButton, EuiFilterGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; - -export type Status = 'all' | 'open' | 'closed'; +import type { AlertStatus } from '../../../common/typings'; export interface StatusFilterProps { - status: Status; - onChange: (value: Status) => void; + status: AlertStatus; + onChange: (value: AlertStatus) => void; } export function StatusFilter({ status = 'open', onChange }: StatusFilterProps) { diff --git a/x-pack/plugins/observability/server/routes/rules.ts b/x-pack/plugins/observability/server/routes/rules.ts index a8f232085a296f..d514c72aeb9022 100644 --- a/x-pack/plugins/observability/server/routes/rules.ts +++ b/x-pack/plugins/observability/server/routes/rules.ts @@ -4,11 +4,12 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import * as t from 'io-ts'; import { isoToEpochRt, toNumberRt } from '@kbn/io-ts-utils'; +import * as t from 'io-ts'; +import { alertStatusRt } from '../../common/typings'; +import { getTopAlerts } from '../lib/rules/get_top_alerts'; import { createObservabilityServerRoute } from './create_observability_server_route'; import { createObservabilityServerRouteRepository } from './create_observability_server_route_repository'; -import { getTopAlerts } from '../lib/rules/get_top_alerts'; const alertsListRoute = createObservabilityServerRoute({ endpoint: 'GET /api/observability/rules/alerts/top', @@ -20,10 +21,10 @@ const alertsListRoute = createObservabilityServerRoute({ t.type({ start: isoToEpochRt, end: isoToEpochRt, + status: alertStatusRt, }), t.partial({ kuery: t.string, - status: t.string, size: toNumberRt, }), ]), From e3a70bc9f1dc11674407aae8581669bc72445463 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 24 May 2021 12:02:33 -0500 Subject: [PATCH 05/14] Add filtering --- .../server/lib/rules/get_top_alerts.ts | 7 +++- .../observability/server/routes/rules.ts | 3 +- .../server/utils/queries.test.ts | 38 +++++++++++++++++++ .../observability/server/utils/queries.ts | 9 +++++ .../basic/tests/index.ts | 1 + .../basic/tests/rules.ts | 37 ++++++++++++++++++ 6 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 x-pack/plugins/observability/server/utils/queries.test.ts create mode 100644 x-pack/test/observability_api_integration/basic/tests/rules.ts diff --git a/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts b/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts index ddfc112ab14521..9560de6ec00ff0 100644 --- a/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts +++ b/x-pack/plugins/observability/server/lib/rules/get_top_alerts.ts @@ -6,7 +6,8 @@ */ import { ALERT_UUID, TIMESTAMP } from '@kbn/rule-data-utils/target/technical_field_names'; import { RuleDataClient } from '../../../../rule_registry/server'; -import { kqlQuery, rangeQuery } from '../../utils/queries'; +import type { AlertStatus } from '../../../common/typings'; +import { kqlQuery, rangeQuery, alertStatusQuery } from '../../utils/queries'; export async function getTopAlerts({ ruleDataClient, @@ -14,18 +15,20 @@ export async function getTopAlerts({ end, kuery, size, + status, }: { ruleDataClient: RuleDataClient; start: number; end: number; kuery?: string; size: number; + status: AlertStatus; }) { const response = await ruleDataClient.getReader().search({ body: { query: { bool: { - filter: [...rangeQuery(start, end), ...kqlQuery(kuery)], + filter: [...rangeQuery(start, end), ...kqlQuery(kuery), ...alertStatusQuery(status)], }, }, fields: ['*'], diff --git a/x-pack/plugins/observability/server/routes/rules.ts b/x-pack/plugins/observability/server/routes/rules.ts index d514c72aeb9022..8b33f5ff8d8632 100644 --- a/x-pack/plugins/observability/server/routes/rules.ts +++ b/x-pack/plugins/observability/server/routes/rules.ts @@ -31,7 +31,7 @@ const alertsListRoute = createObservabilityServerRoute({ }), handler: async ({ ruleDataClient, context, params }) => { const { - query: { start, end, kuery, size = 100 }, + query: { start, end, kuery, size = 100, status }, } = params; return getTopAlerts({ @@ -40,6 +40,7 @@ const alertsListRoute = createObservabilityServerRoute({ end, kuery, size, + status, }); }, }); diff --git a/x-pack/plugins/observability/server/utils/queries.test.ts b/x-pack/plugins/observability/server/utils/queries.test.ts new file mode 100644 index 00000000000000..12fca01aed963f --- /dev/null +++ b/x-pack/plugins/observability/server/utils/queries.test.ts @@ -0,0 +1,38 @@ +/* + * 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 * as queries from './queries'; + +describe('queries', () => { + describe('alertStatusQuery', () => { + describe('given "all"', () => { + it('returns an empty array', () => { + expect(queries.alertStatusQuery('all')).toEqual([]); + }); + }); + + describe('given "open"', () => { + it('returns a query for open', () => { + expect(queries.alertStatusQuery('open')).toEqual([ + { + term: { 'kibana.rac.alert.status': 'open' }, + }, + ]); + }); + }); + + describe('given "closed"', () => { + it('returns a query for closed', () => { + expect(queries.alertStatusQuery('closed')).toEqual([ + { + term: { 'kibana.rac.alert.status': 'closed' }, + }, + ]); + }); + }); + }); +}); diff --git a/x-pack/plugins/observability/server/utils/queries.ts b/x-pack/plugins/observability/server/utils/queries.ts index 9e1c110e775873..f40d6ec9280599 100644 --- a/x-pack/plugins/observability/server/utils/queries.ts +++ b/x-pack/plugins/observability/server/utils/queries.ts @@ -7,6 +7,15 @@ import { QueryContainer } from '@elastic/elasticsearch/api/types'; import { esKuery } from '../../../../../src/plugins/data/server'; +import { AlertStatus } from '../../common/typings'; + +export function alertStatusQuery(status: AlertStatus) { + if (status === 'all') { + return []; + } + + return [{ term: { 'kibana.rac.alert.status': status } }]; +} export function rangeQuery(start?: number, end?: number, field = '@timestamp'): QueryContainer[] { return [ diff --git a/x-pack/test/observability_api_integration/basic/tests/index.ts b/x-pack/test/observability_api_integration/basic/tests/index.ts index c62cf4be0d7c7a..12eee020502d4c 100644 --- a/x-pack/test/observability_api_integration/basic/tests/index.ts +++ b/x-pack/test/observability_api_integration/basic/tests/index.ts @@ -12,5 +12,6 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr describe('Observability specs (basic)', function () { this.tags('ciGroup1'); loadTestFile(require.resolve('./annotations')); + loadTestFile(require.resolve('./rules')); }); } diff --git a/x-pack/test/observability_api_integration/basic/tests/rules.ts b/x-pack/test/observability_api_integration/basic/tests/rules.ts new file mode 100644 index 00000000000000..de07ba3c00f784 --- /dev/null +++ b/x-pack/test/observability_api_integration/basic/tests/rules.ts @@ -0,0 +1,37 @@ +/* + * 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 expect from '@kbn/expect'; +import { JsonObject } from 'src/plugins/kibana_utils/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; + +// eslint-disable-next-line import/no-default-export +export default function rulesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + function request({ method, url, data }: { method: string; url: string; data?: JsonObject }) { + switch (method.toLowerCase()) { + case 'post': + return supertest.post(url).send(data).set('kbn-xsrf', 'foo'); + + default: + throw new Error(`Unsupported methoed ${method}`); + } + } + + describe('Observability rules with a basic license', () => { + describe('when listing top alerts', () => { + it('completes with a 200', async () => { + const response = await supertest + .get('/api/observability/rules/alerts/top') + .query({ status: 'all' }); + + expect(response.status).to.be(200); + }); + }); + }); +} From cf38a58eafadaead1dce465093484ce776d08e79 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 24 May 2021 12:39:03 -0500 Subject: [PATCH 06/14] roll back api test changes --- .../basic/tests/index.ts | 1 - .../basic/tests/rules.ts | 37 ------------------- 2 files changed, 38 deletions(-) delete mode 100644 x-pack/test/observability_api_integration/basic/tests/rules.ts diff --git a/x-pack/test/observability_api_integration/basic/tests/index.ts b/x-pack/test/observability_api_integration/basic/tests/index.ts index 12eee020502d4c..c62cf4be0d7c7a 100644 --- a/x-pack/test/observability_api_integration/basic/tests/index.ts +++ b/x-pack/test/observability_api_integration/basic/tests/index.ts @@ -12,6 +12,5 @@ export default function observabilityApiIntegrationTests({ loadTestFile }: FtrPr describe('Observability specs (basic)', function () { this.tags('ciGroup1'); loadTestFile(require.resolve('./annotations')); - loadTestFile(require.resolve('./rules')); }); } diff --git a/x-pack/test/observability_api_integration/basic/tests/rules.ts b/x-pack/test/observability_api_integration/basic/tests/rules.ts deleted file mode 100644 index de07ba3c00f784..00000000000000 --- a/x-pack/test/observability_api_integration/basic/tests/rules.ts +++ /dev/null @@ -1,37 +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 expect from '@kbn/expect'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function rulesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - function request({ method, url, data }: { method: string; url: string; data?: JsonObject }) { - switch (method.toLowerCase()) { - case 'post': - return supertest.post(url).send(data).set('kbn-xsrf', 'foo'); - - default: - throw new Error(`Unsupported methoed ${method}`); - } - } - - describe('Observability rules with a basic license', () => { - describe('when listing top alerts', () => { - it('completes with a 200', async () => { - const response = await supertest - .get('/api/observability/rules/alerts/top') - .query({ status: 'all' }); - - expect(response.status).to.be(200); - }); - }); - }); -} From cec255b3070d3f88a35c4aba00891bd3b00c84e3 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 24 May 2021 12:42:57 -0500 Subject: [PATCH 07/14] Use rt for alert status --- x-pack/plugins/observability/public/routes/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/routes/index.tsx b/x-pack/plugins/observability/public/routes/index.tsx index 3090635aa03f8a..6e180347106d6e 100644 --- a/x-pack/plugins/observability/public/routes/index.tsx +++ b/x-pack/plugins/observability/public/routes/index.tsx @@ -15,6 +15,7 @@ import { jsonRt } from './json_rt'; import { AlertsPage } from '../pages/alerts'; import { CasesPage } from '../pages/cases'; import { ExploratoryViewPage } from '../components/shared/exploratory_view'; +import { alertStatusRt } from '../../common/typings'; export type RouteParams = DecodeParams; @@ -105,7 +106,7 @@ export const routes = { rangeFrom: t.string, rangeTo: t.string, kuery: t.string, - status: t.union([t.literal('all'), t.literal('open'), t.literal('closed')]), + status: alertStatusRt, refreshPaused: jsonRt.pipe(t.boolean), refreshInterval: jsonRt.pipe(t.number), }), From f53bbfa191c1afc44938b7c971287734559af8aa Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 24 May 2021 13:07:28 -0500 Subject: [PATCH 08/14] fix storybook type import --- .../public/pages/alerts/status_filter.stories.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.stories.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.stories.tsx index f522c0e3d112db..851e0cb6c3ddde 100644 --- a/x-pack/plugins/observability/public/pages/alerts/status_filter.stories.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.stories.tsx @@ -6,7 +6,8 @@ */ import React, { ComponentProps, useState } from 'react'; -import { StatusFilter, Status } from './status_filter'; +import type { AlertStatus } from '../../../common/typings'; +import { StatusFilter } from './status_filter'; type Args = ComponentProps; @@ -19,7 +20,7 @@ export default { }; export function Example({ onChange }: Args) { - const [status, setStatus] = useState('open'); + const [status, setStatus] = useState('open'); return ( Date: Mon, 24 May 2021 15:28:08 -0500 Subject: [PATCH 09/14] Add status to APM API tests --- .../docker_generator/resources/base/bin/kibana-docker | 2 +- x-pack/plugins/observability/README.md | 2 +- x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 3b2feeecabb7c0..2f54bd1d818b56 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -276,7 +276,7 @@ kibana_vars=( xpack.reporting.roles.allow xpack.reporting.roles.enabled xpack.rollup.enabled - xpack.ruleRegistry.unsafe.write.enabled + xpack.ruleRegistry.write.enabled xpack.searchprofiler.enabled xpack.security.audit.enabled xpack.security.audit.appender.type diff --git a/x-pack/plugins/observability/README.md b/x-pack/plugins/observability/README.md index 70f61ffc288d38..943a7482a25eec 100644 --- a/x-pack/plugins/observability/README.md +++ b/x-pack/plugins/observability/README.md @@ -19,7 +19,7 @@ This will only enable the UI for these pages. In order to have alert data indexe you'll need to enable writing in the [Rule Registry plugin](../rule_registry/README.md): ```yaml -xpack.ruleRegistry.unsafe.write.enabled: true +xpack.ruleRegistry.write.enabled: true ``` When both of the these are set to `true`, your alerts should show on the alerts page. diff --git a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts index e0a3e4d3a3f8b5..e9392a611b5b7a 100644 --- a/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts +++ b/x-pack/test/apm_api_integration/tests/alerts/rule_registry.ts @@ -400,6 +400,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { query: { start: new Date(now - 30 * 60 * 1000).toISOString(), end: new Date(now).toISOString(), + status: 'all', }, }) ) @@ -572,6 +573,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { query: { start: new Date(now - 30 * 60 * 1000).toISOString(), end: new Date().toISOString(), + status: 'all', }, }) ) From 3b17d0d21352570bdbda3ad3ed1d6ad6d2ead16f Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 24 May 2021 20:12:09 -0500 Subject: [PATCH 10/14] Move buttons and make active the default --- .../public/pages/alerts/index.tsx | 2 +- .../public/pages/alerts/status_filter.tsx | 20 +++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx index e8f0f6080f3908..6662cff07e4484 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -60,7 +60,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { const { prepend } = core.http.basePath; const history = useHistory(); const { - query: { rangeFrom = 'now-15m', rangeTo = 'now', kuery = '', status = 'all' }, + query: { rangeFrom = 'now-15m', rangeTo = 'now', kuery = '', status = 'active' }, } = routeParams; // In a future milestone we'll have a page dedicated to rule management in diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx index 506269fed1b1b7..0d5a76f1ad0cdb 100644 --- a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx @@ -18,16 +18,6 @@ export interface StatusFilterProps { export function StatusFilter({ status = 'open', onChange }: StatusFilterProps) { return ( - onChange('all')} - withNext={true} - > - {i18n.translate('xpack.observability.alerts.statusFilter.allButtonLabel', { - defaultMessage: 'All', - })} - onChange('closed')} + withNext={true} > {i18n.translate('xpack.observability.alerts.statusFilter.recoveredButtonLabel', { defaultMessage: 'Recovered', })} + onChange('all')} + > + {i18n.translate('xpack.observability.alerts.statusFilter.allButtonLabel', { + defaultMessage: 'All', + })} + ); } From 26a971a5348158a30a82470ea63fa8b02fb42c90 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Mon, 24 May 2021 21:29:15 -0500 Subject: [PATCH 11/14] use open instead of active --- x-pack/plugins/observability/public/pages/alerts/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx index 6662cff07e4484..5d45edda52d957 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -60,7 +60,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { const { prepend } = core.http.basePath; const history = useHistory(); const { - query: { rangeFrom = 'now-15m', rangeTo = 'now', kuery = '', status = 'active' }, + query: { rangeFrom = 'now-15m', rangeTo = 'now', kuery = '', status = 'open' }, } = routeParams; // In a future milestone we'll have a page dedicated to rule management in From 43986303304da674cda59851dde0b5973272d6d2 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Tue, 25 May 2021 07:24:30 -0500 Subject: [PATCH 12/14] Add aria label to group --- .../observability/public/pages/alerts/status_filter.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx index 0d5a76f1ad0cdb..afc565e2d1d573 100644 --- a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx @@ -17,7 +17,11 @@ export interface StatusFilterProps { export function StatusFilter({ status = 'open', onChange }: StatusFilterProps) { return ( - + Date: Tue, 25 May 2021 08:41:25 -0500 Subject: [PATCH 13/14] Use open/closed and constants --- .../observability/public/pages/alerts/alerts_table.tsx | 8 ++++---- .../observability/public/pages/alerts/index.tsx | 4 ++-- .../public/pages/alerts/status_filter.tsx | 10 +++++----- .../plugins/observability/server/utils/queries.test.ts | 5 +++-- x-pack/plugins/observability/server/utils/queries.ts | 3 ++- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts_table.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts_table.tsx index f377186623a03f..31e59679854b1a 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts_table.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts_table.tsx @@ -66,16 +66,16 @@ export function AlertsTable(props: AlertsTableProps) { return active ? ( ) : ( diff --git a/x-pack/plugins/observability/public/pages/alerts/index.tsx b/x-pack/plugins/observability/public/pages/alerts/index.tsx index 5d45edda52d957..7b8055c82f0717 100644 --- a/x-pack/plugins/observability/public/pages/alerts/index.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/index.tsx @@ -17,7 +17,7 @@ import { import { i18n } from '@kbn/i18n'; import { ALERT_START, - EVENT_ACTION, + ALERT_STATUS, RULE_ID, RULE_NAME, } from '@kbn/rule-data-utils/target/technical_field_names'; @@ -112,7 +112,7 @@ export function AlertsPage({ routeParams }: AlertsPageProps) { }, }) : undefined, - active: parsedFields[EVENT_ACTION] !== 'close', + active: parsedFields[ALERT_STATUS] !== 'closed', start: new Date(parsedFields[ALERT_START]!).getTime(), }; }); diff --git a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx index afc565e2d1d573..26169717d29673 100644 --- a/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/status_filter.tsx @@ -19,7 +19,7 @@ export function StatusFilter({ status = 'open', onChange }: StatusFilterProps) { return ( onChange('open')} withNext={true} > - {i18n.translate('xpack.observability.alerts.statusFilter.activeButtonLabel', { - defaultMessage: 'Active', + {i18n.translate('xpack.observability.alerts.statusFilter.openButtonLabel', { + defaultMessage: 'Open', })} onChange('closed')} withNext={true} > - {i18n.translate('xpack.observability.alerts.statusFilter.recoveredButtonLabel', { - defaultMessage: 'Recovered', + {i18n.translate('xpack.observability.alerts.statusFilter.closedButtonLabel', { + defaultMessage: 'Closed', })} { @@ -19,7 +20,7 @@ describe('queries', () => { it('returns a query for open', () => { expect(queries.alertStatusQuery('open')).toEqual([ { - term: { 'kibana.rac.alert.status': 'open' }, + term: { [ALERT_STATUS]: 'open' }, }, ]); }); @@ -29,7 +30,7 @@ describe('queries', () => { it('returns a query for closed', () => { expect(queries.alertStatusQuery('closed')).toEqual([ { - term: { 'kibana.rac.alert.status': 'closed' }, + term: { [ALERT_STATUS]: 'closed' }, }, ]); }); diff --git a/x-pack/plugins/observability/server/utils/queries.ts b/x-pack/plugins/observability/server/utils/queries.ts index f40d6ec9280599..b7412120365c6f 100644 --- a/x-pack/plugins/observability/server/utils/queries.ts +++ b/x-pack/plugins/observability/server/utils/queries.ts @@ -6,6 +6,7 @@ */ import { QueryContainer } from '@elastic/elasticsearch/api/types'; +import { ALERT_STATUS } from '@kbn/rule-data-utils/target/technical_field_names'; import { esKuery } from '../../../../../src/plugins/data/server'; import { AlertStatus } from '../../common/typings'; @@ -14,7 +15,7 @@ export function alertStatusQuery(status: AlertStatus) { return []; } - return [{ term: { 'kibana.rac.alert.status': status } }]; + return [{ term: { [ALERT_STATUS]: status } }]; } export function rangeQuery(start?: number, end?: number, field = '@timestamp'): QueryContainer[] { From 1e1379d19bc51cc487a190516b44296c0d88f2b0 Mon Sep 17 00:00:00 2001 From: Nathan L Smith Date: Tue, 25 May 2021 08:57:14 -0500 Subject: [PATCH 14/14] i18n fix --- x-pack/plugins/translations/translations/ja-JP.json | 2 -- x-pack/plugins/translations/translations/zh-CN.json | 2 -- 2 files changed, 4 deletions(-) diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 134f58236cfee3..f85a260bea02a4 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -17748,9 +17748,7 @@ "xpack.observability.alertsTable.durationColumnDescription": "期間", "xpack.observability.alertsTable.reasonColumnDescription": "理由", "xpack.observability.alertsTable.severityColumnDescription": "深刻度", - "xpack.observability.alertsTable.statusActiveDescription": "アクティブ", "xpack.observability.alertsTable.statusColumnDescription": "ステータス", - "xpack.observability.alertsTable.statusRecoveredDescription": "回復済み", "xpack.observability.alertsTable.triggeredColumnDescription": "実行済み", "xpack.observability.alertsTable.viewInAppButtonLabel": "アプリで表示", "xpack.observability.alertsTitle": "アラート", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 67677f86ddbf78..a6377f67c1c073 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -17989,9 +17989,7 @@ "xpack.observability.alertsTable.durationColumnDescription": "持续时间", "xpack.observability.alertsTable.reasonColumnDescription": "原因", "xpack.observability.alertsTable.severityColumnDescription": "严重性", - "xpack.observability.alertsTable.statusActiveDescription": "活动", "xpack.observability.alertsTable.statusColumnDescription": "状态", - "xpack.observability.alertsTable.statusRecoveredDescription": "已恢复", "xpack.observability.alertsTable.triggeredColumnDescription": "已触发", "xpack.observability.alertsTable.viewInAppButtonLabel": "在应用中查看", "xpack.observability.alertsTitle": "告警",