diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d5d1e2d94c673..410436d7e4a89 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -140,6 +140,9 @@ packages/kbn-validate-next-docs-cli @elastic/kibana-operations packages/kbn-web-worker-stub @elastic/kibana-operations packages/kbn-whereis-pkg-cli @elastic/kibana-operations packages/kbn-yarn-lock-validator @elastic/kibana-operations +packages/response-ops/alerts_apis @elastic/response-ops +packages/response-ops/alerts_fields_browser @elastic/response-ops +packages/response-ops/alerts_table @elastic/response-ops packages/serverless/storybook/config @elastic/appex-sharedux src/core @elastic/kibana-core src/core/packages/analytics/browser @elastic/kibana-core diff --git a/package.json b/package.json index 37e4c04905cba..919902413ac0c 100644 --- a/package.json +++ b/package.json @@ -760,6 +760,9 @@ "@kbn/resizable-layout": "link:src/platform/packages/shared/kbn-resizable-layout", "@kbn/resizable-layout-examples-plugin": "link:examples/resizable_layout_examples", "@kbn/resolver-test-plugin": "link:x-pack/test/plugin_functional/plugins/resolver_test", + "@kbn/response-ops-alerts-apis": "link:packages/response-ops/alerts_apis", + "@kbn/response-ops-alerts-fields-browser": "link:packages/response-ops/alerts_fields_browser", + "@kbn/response-ops-alerts-table": "link:packages/response-ops/alerts_table", "@kbn/response-ops-rule-form": "link:src/platform/packages/shared/response-ops/rule_form", "@kbn/response-ops-rule-params": "link:src/platform/packages/shared/response-ops/rule_params", "@kbn/response-stream-plugin": "link:examples/response_stream", diff --git a/packages/response-ops/alerts_apis/README.md b/packages/response-ops/alerts_apis/README.md new file mode 100644 index 0000000000000..74d102a668533 --- /dev/null +++ b/packages/response-ops/alerts_apis/README.md @@ -0,0 +1,3 @@ +# @kbn/response-ops-alerts-apis + +Client-side Alerts HTTP API fetchers and React Query wrappers. diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/get_rules_muted_alerts.test.ts b/packages/response-ops/alerts_apis/apis/get_muted_alerts_instances_by_rule.test.ts similarity index 78% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/get_rules_muted_alerts.test.ts rename to packages/response-ops/alerts_apis/apis/get_muted_alerts_instances_by_rule.test.ts index f6b3d23c2e289..46f3c061b2d1e 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/get_rules_muted_alerts.test.ts +++ b/packages/response-ops/alerts_apis/apis/get_muted_alerts_instances_by_rule.test.ts @@ -1,15 +1,18 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import { httpServiceMock } from '@kbn/core/public/mocks'; -import { getMutedAlerts } from './get_rules_muted_alerts'; +import { getMutedAlertsInstancesByRule } from './get_muted_alerts_instances_by_rule'; const http = httpServiceMock.createStartContract(); -describe('getMutedAlerts', () => { +describe('getMutedAlertsInstancesByRule', () => { const apiRes = { page: 1, per_page: 10, @@ -24,7 +27,7 @@ describe('getMutedAlerts', () => { }); test('should call find API with correct params', async () => { - const result = await getMutedAlerts(http, { ruleIds: ['foo'] }); + const result = await getMutedAlertsInstancesByRule({ http, ruleIds: ['foo'] }); expect(result).toEqual({ page: 1, @@ -45,7 +48,7 @@ describe('getMutedAlerts', () => { }); test('should call find API with multiple ruleIds', async () => { - const result = await getMutedAlerts(http, { ruleIds: ['foo', 'bar'] }); + const result = await getMutedAlertsInstancesByRule({ http, ruleIds: ['foo', 'bar'] }); expect(result).toEqual({ page: 1, diff --git a/packages/response-ops/alerts_apis/apis/get_muted_alerts_instances_by_rule.ts b/packages/response-ops/alerts_apis/apis/get_muted_alerts_instances_by_rule.ts new file mode 100644 index 0000000000000..ed05514d12401 --- /dev/null +++ b/packages/response-ops/alerts_apis/apis/get_muted_alerts_instances_by_rule.ts @@ -0,0 +1,45 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { HttpStart } from '@kbn/core-http-browser'; +import { nodeBuilder } from '@kbn/es-query'; + +const INTERNAL_FIND_RULES_URL = '/internal/alerting/rules/_find'; + +export interface Rule { + id: string; + muted_alert_ids: string[]; +} + +export interface FindRulesResponse { + data: Rule[]; +} + +export interface GetMutedAlertsInstancesByRuleParams { + ruleIds: string[]; + http: HttpStart; + signal?: AbortSignal; +} + +export const getMutedAlertsInstancesByRule = async ({ + http, + ruleIds, + signal, +}: GetMutedAlertsInstancesByRuleParams) => { + const filterNode = nodeBuilder.or(ruleIds.map((id) => nodeBuilder.is('alert.id', `alert:${id}`))); + return http.post(INTERNAL_FIND_RULES_URL, { + body: JSON.stringify({ + filter: JSON.stringify(filterNode), + fields: ['id', 'mutedInstanceIds'], + page: 1, + per_page: ruleIds.length, + }), + signal, + }); +}; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/lib/rule_api/mute_alert.test.ts b/packages/response-ops/alerts_apis/apis/mute_alert_instance.test.ts similarity index 57% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/lib/rule_api/mute_alert.test.ts rename to packages/response-ops/alerts_apis/apis/mute_alert_instance.test.ts index dc046903b25b0..f8a63cf11c8bd 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/lib/rule_api/mute_alert.test.ts +++ b/packages/response-ops/alerts_apis/apis/mute_alert_instance.test.ts @@ -1,12 +1,14 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { muteAlertInstance } from './mute_alert'; +import { muteAlertInstance } from './mute_alert_instance'; const http = httpServiceMock.createStartContract(); diff --git a/packages/response-ops/alerts_apis/apis/mute_alert_instance.ts b/packages/response-ops/alerts_apis/apis/mute_alert_instance.ts new file mode 100644 index 0000000000000..76b26fc59a3d3 --- /dev/null +++ b/packages/response-ops/alerts_apis/apis/mute_alert_instance.ts @@ -0,0 +1,25 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { HttpSetup } from '@kbn/core/public'; +import { BASE_ALERTING_API_PATH } from '../constants'; + +export interface MuteAlertInstanceParams { + id: string; + instanceId: string; + http: HttpSetup; +} + +export const muteAlertInstance = ({ id, instanceId, http }: MuteAlertInstanceParams) => { + return http.post( + `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/alert/${encodeURIComponent( + instanceId + )}/_mute` + ); +}; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/lib/rule_api/unmute_alert.test.ts b/packages/response-ops/alerts_apis/apis/unmute_alert_instance.test.ts similarity index 57% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/lib/rule_api/unmute_alert.test.ts rename to packages/response-ops/alerts_apis/apis/unmute_alert_instance.test.ts index c7c1b4ab5dab0..dc462f84fd38f 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/lib/rule_api/unmute_alert.test.ts +++ b/packages/response-ops/alerts_apis/apis/unmute_alert_instance.test.ts @@ -1,12 +1,14 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { httpServiceMock } from '@kbn/core/public/mocks'; -import { unmuteAlertInstance } from './unmute_alert'; +import { unmuteAlertInstance } from './unmute_alert_instance'; const http = httpServiceMock.createStartContract(); diff --git a/packages/response-ops/alerts_apis/apis/unmute_alert_instance.ts b/packages/response-ops/alerts_apis/apis/unmute_alert_instance.ts new file mode 100644 index 0000000000000..f1260712e2cf6 --- /dev/null +++ b/packages/response-ops/alerts_apis/apis/unmute_alert_instance.ts @@ -0,0 +1,25 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { HttpSetup } from '@kbn/core/public'; +import { BASE_ALERTING_API_PATH } from '../constants'; + +export interface UnmuteAlertInstanceParams { + id: string; + instanceId: string; + http: HttpSetup; +} + +export const unmuteAlertInstance = ({ id, instanceId, http }: UnmuteAlertInstanceParams) => { + return http.post( + `${BASE_ALERTING_API_PATH}/rule/${encodeURIComponent(id)}/alert/${encodeURIComponent( + instanceId + )}/_unmute` + ); +}; diff --git a/packages/response-ops/alerts_apis/constants.ts b/packages/response-ops/alerts_apis/constants.ts new file mode 100644 index 0000000000000..5ba66d66887e3 --- /dev/null +++ b/packages/response-ops/alerts_apis/constants.ts @@ -0,0 +1,22 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export const BASE_ALERTING_API_PATH = '/api/alerting'; + +export const queryKeys = { + root: 'alerts', + mutedAlerts: (ruleIds: string[]) => + [queryKeys.root, 'mutedInstanceIdsForRuleIds', ruleIds] as const, +}; + +export const mutationKeys = { + root: 'alerts', + muteAlertInstance: () => [mutationKeys.root, 'muteAlertInstance'] as const, + unmuteAlertInstance: () => [mutationKeys.root, 'unmuteAlertInstance'] as const, +}; diff --git a/packages/response-ops/alerts_apis/hooks/use_get_muted_alerts_query.test.tsx b/packages/response-ops/alerts_apis/hooks/use_get_muted_alerts_query.test.tsx new file mode 100644 index 0000000000000..ac1dc15801994 --- /dev/null +++ b/packages/response-ops/alerts_apis/hooks/use_get_muted_alerts_query.test.tsx @@ -0,0 +1,64 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { renderHook, waitFor } from '@testing-library/react'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { Wrapper } from '@kbn/alerts-ui-shared/src/common/test_utils/wrapper'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import * as api from '../apis/get_muted_alerts_instances_by_rule'; +import { useGetMutedAlertsQuery } from './use_get_muted_alerts_query'; + +jest.mock('../apis/get_muted_alerts_instances_by_rule'); + +const ruleIds = ['a', 'b']; + +describe('useGetMutedAlertsQuery', () => { + const http = httpServiceMock.createStartContract(); + const notifications = notificationServiceMock.createStartContract(); + const addErrorMock = notifications.toasts.addError; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('calls the api when invoked with the correct parameters', async () => { + const muteAlertInstanceSpy = jest.spyOn(api, 'getMutedAlertsInstancesByRule'); + + renderHook(() => useGetMutedAlertsQuery({ http, notifications, ruleIds }), { + wrapper: Wrapper, + }); + + await waitFor(() => + expect(muteAlertInstanceSpy).toHaveBeenCalledWith(expect.objectContaining({ ruleIds })) + ); + }); + + it('does not call the api if the enabled option is false', async () => { + const spy = jest.spyOn(api, 'getMutedAlertsInstancesByRule'); + + renderHook(() => useGetMutedAlertsQuery({ http, notifications, ruleIds }, { enabled: false }), { + wrapper: Wrapper, + }); + + await waitFor(() => expect(spy).not.toHaveBeenCalled()); + }); + + it('shows a toast error when the api returns an error', async () => { + const spy = jest + .spyOn(api, 'getMutedAlertsInstancesByRule') + .mockRejectedValue(new Error('An error')); + + renderHook(() => useGetMutedAlertsQuery({ http, notifications, ruleIds }), { + wrapper: Wrapper, + }); + + await waitFor(() => expect(spy).toHaveBeenCalled()); + await waitFor(() => expect(addErrorMock).toHaveBeenCalled()); + }); +}); diff --git a/packages/response-ops/alerts_apis/hooks/use_get_muted_alerts_query.tsx b/packages/response-ops/alerts_apis/hooks/use_get_muted_alerts_query.tsx new file mode 100644 index 0000000000000..52623236c68bd --- /dev/null +++ b/packages/response-ops/alerts_apis/hooks/use_get_muted_alerts_query.tsx @@ -0,0 +1,58 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { i18n } from '@kbn/i18n'; +import { useQuery } from '@tanstack/react-query'; +import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; +import { QueryOptionsOverrides } from '@kbn/alerts-ui-shared/src/common/types/tanstack_query_utility_types'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { NotificationsStart } from '@kbn/core-notifications-browser'; +import { queryKeys } from '../constants'; +import { MutedAlerts, ServerError } from '../types'; +import { + getMutedAlertsInstancesByRule, + GetMutedAlertsInstancesByRuleParams, +} from '../apis/get_muted_alerts_instances_by_rule'; + +const ERROR_TITLE = i18n.translate('xpack.responseOpsAlertsApis.mutedAlerts.api.get', { + defaultMessage: 'Error fetching muted alerts data', +}); + +const getMutedAlerts = ({ http, signal, ruleIds }: GetMutedAlertsInstancesByRuleParams) => + getMutedAlertsInstancesByRule({ http, ruleIds, signal }).then(({ data: rules }) => + rules?.reduce((mutedAlerts, rule) => { + mutedAlerts[rule.id] = rule.muted_alert_ids; + return mutedAlerts; + }, {} as MutedAlerts) + ); + +export interface UseGetMutedAlertsQueryParams { + ruleIds: string[]; + http: HttpStart; + notifications: NotificationsStart; +} + +export const useGetMutedAlertsQuery = ( + { ruleIds, http, notifications: { toasts } }: UseGetMutedAlertsQueryParams, + { enabled }: QueryOptionsOverrides = {} +) => { + return useQuery({ + context: AlertsQueryContext, + queryKey: queryKeys.mutedAlerts(ruleIds), + queryFn: ({ signal }) => getMutedAlerts({ http, signal, ruleIds }), + onError: (error: ServerError) => { + if (error.name !== 'AbortError') { + toasts.addError(error.body?.message ? new Error(error.body.message) : error, { + title: ERROR_TITLE, + }); + } + }, + enabled: ruleIds.length > 0 && enabled !== false, + }); +}; diff --git a/packages/response-ops/alerts_apis/hooks/use_mute_alert_instance.test.tsx b/packages/response-ops/alerts_apis/hooks/use_mute_alert_instance.test.tsx new file mode 100644 index 0000000000000..65854d714868d --- /dev/null +++ b/packages/response-ops/alerts_apis/hooks/use_mute_alert_instance.test.tsx @@ -0,0 +1,62 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { renderHook, waitFor } from '@testing-library/react'; +import { Wrapper } from '@kbn/alerts-ui-shared/src/common/test_utils/wrapper'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import * as api from '../apis/mute_alert_instance'; +import { useMuteAlertInstance } from './use_mute_alert_instance'; + +jest.mock('../apis/mute_alert_instance'); + +const params = { ruleId: '', alertInstanceId: '' }; + +describe('useMuteAlertInstance', () => { + const http = httpServiceMock.createStartContract(); + const notifications = notificationServiceMock.createStartContract(); + const addErrorMock = notifications.toasts.addError; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('calls the api when invoked with the correct parameters', async () => { + const muteAlertInstanceSpy = jest.spyOn(api, 'muteAlertInstance'); + + const { result } = renderHook(() => useMuteAlertInstance({ http, notifications }), { + wrapper: Wrapper, + }); + + result.current.mutate(params); + + await waitFor(() => { + expect(muteAlertInstanceSpy).toHaveBeenCalledWith({ + id: params.ruleId, + instanceId: params.alertInstanceId, + http: expect.anything(), + }); + }); + }); + + it('shows a toast error when the api returns an error', async () => { + const spy = jest.spyOn(api, 'muteAlertInstance').mockRejectedValue(new Error('An error')); + + const { result } = renderHook(() => useMuteAlertInstance({ http, notifications }), { + wrapper: Wrapper, + }); + + result.current.mutate(params); + + await waitFor(() => { + expect(spy).toHaveBeenCalled(); + expect(addErrorMock).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/response-ops/alerts_apis/hooks/use_mute_alert_instance.ts b/packages/response-ops/alerts_apis/hooks/use_mute_alert_instance.ts new file mode 100644 index 0000000000000..0421b793bc011 --- /dev/null +++ b/packages/response-ops/alerts_apis/hooks/use_mute_alert_instance.ts @@ -0,0 +1,57 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { useMutation } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; +import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { NotificationsStart } from '@kbn/core-notifications-browser'; +import { mutationKeys } from '../constants'; +import type { ServerError, ToggleAlertParams } from '../types'; +import { muteAlertInstance } from '../apis/mute_alert_instance'; + +const ERROR_TITLE = i18n.translate('alertsApis.muteAlert.error', { + defaultMessage: 'Error muting alert', +}); + +export interface UseMuteAlertInstanceParams { + http: HttpStart; + notifications: NotificationsStart; +} + +export const useMuteAlertInstance = ({ + http, + notifications: { toasts }, +}: UseMuteAlertInstanceParams) => { + return useMutation( + ({ ruleId, alertInstanceId }: ToggleAlertParams) => + muteAlertInstance({ http, id: ruleId, instanceId: alertInstanceId }), + { + mutationKey: mutationKeys.muteAlertInstance(), + context: AlertsQueryContext, + onSuccess() { + toasts.addSuccess( + i18n.translate('xpack.responseOpsAlertsApis.alertsTable.alertMuted', { + defaultMessage: 'Alert muted', + }) + ); + }, + onError: (error: ServerError) => { + if (error.name !== 'AbortError') { + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: ERROR_TITLE, + } + ); + } + }, + } + ); +}; diff --git a/packages/response-ops/alerts_apis/hooks/use_unmute_alert_instance.test.tsx b/packages/response-ops/alerts_apis/hooks/use_unmute_alert_instance.test.tsx new file mode 100644 index 0000000000000..3c368b632e604 --- /dev/null +++ b/packages/response-ops/alerts_apis/hooks/use_unmute_alert_instance.test.tsx @@ -0,0 +1,62 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { renderHook, waitFor } from '@testing-library/react'; +import { httpServiceMock } from '@kbn/core-http-browser-mocks'; +import { notificationServiceMock } from '@kbn/core-notifications-browser-mocks'; +import { Wrapper } from '@kbn/alerts-ui-shared/src/common/test_utils/wrapper'; +import { useUnmuteAlertInstance } from './use_unmute_alert_instance'; +import * as api from '../apis/unmute_alert_instance'; + +jest.mock('../apis/unmute_alert_instance'); + +const params = { ruleId: '', alertInstanceId: '' }; + +describe('useUnmuteAlertInstance', () => { + const http = httpServiceMock.createStartContract(); + const notifications = notificationServiceMock.createStartContract(); + const addErrorMock = notifications.toasts.addError; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('calls the api when invoked with the correct parameters', async () => { + const muteAlertInstanceSpy = jest.spyOn(api, 'unmuteAlertInstance'); + + const { result } = renderHook(() => useUnmuteAlertInstance({ http, notifications }), { + wrapper: Wrapper, + }); + + result.current.mutate(params); + + await waitFor(() => { + expect(muteAlertInstanceSpy).toHaveBeenCalledWith({ + id: params.ruleId, + instanceId: params.alertInstanceId, + http: expect.anything(), + }); + }); + }); + + it('shows a toast error when the api returns an error', async () => { + const spy = jest.spyOn(api, 'unmuteAlertInstance').mockRejectedValue(new Error('An error')); + + const { result } = renderHook(() => useUnmuteAlertInstance({ http, notifications }), { + wrapper: Wrapper, + }); + + result.current.mutate(params); + + await waitFor(() => { + expect(spy).toHaveBeenCalled(); + expect(addErrorMock).toHaveBeenCalled(); + }); + }); +}); diff --git a/packages/response-ops/alerts_apis/hooks/use_unmute_alert_instance.ts b/packages/response-ops/alerts_apis/hooks/use_unmute_alert_instance.ts new file mode 100644 index 0000000000000..8525991100a2f --- /dev/null +++ b/packages/response-ops/alerts_apis/hooks/use_unmute_alert_instance.ts @@ -0,0 +1,57 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { useMutation } from '@tanstack/react-query'; +import { i18n } from '@kbn/i18n'; +import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { NotificationsStart } from '@kbn/core-notifications-browser'; +import { mutationKeys } from '../constants'; +import type { ServerError, ToggleAlertParams } from '../types'; +import { unmuteAlertInstance } from '../apis/unmute_alert_instance'; + +const ERROR_TITLE = i18n.translate('alertsApis.unmuteAlert.error', { + defaultMessage: 'Error unmuting alert', +}); + +export interface UseUnmuteAlertInstanceParams { + http: HttpStart; + notifications: NotificationsStart; +} + +export const useUnmuteAlertInstance = ({ + http, + notifications: { toasts }, +}: UseUnmuteAlertInstanceParams) => { + return useMutation( + ({ ruleId, alertInstanceId }: ToggleAlertParams) => + unmuteAlertInstance({ http, id: ruleId, instanceId: alertInstanceId }), + { + mutationKey: mutationKeys.unmuteAlertInstance(), + context: AlertsQueryContext, + onSuccess() { + toasts.addSuccess( + i18n.translate('xpack.responseOpsAlertsApis.alertsTable.alertUnmuted', { + defaultMessage: 'Alert unmuted', + }) + ); + }, + onError: (error: ServerError) => { + if (error.name !== 'AbortError') { + toasts.addError( + error.body && error.body.message ? new Error(error.body.message) : error, + { + title: ERROR_TITLE, + } + ); + } + }, + } + ); +}; diff --git a/packages/response-ops/alerts_apis/jest.config.js b/packages/response-ops/alerts_apis/jest.config.js new file mode 100644 index 0000000000000..4e584d40c5bc7 --- /dev/null +++ b/packages/response-ops/alerts_apis/jest.config.js @@ -0,0 +1,15 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/packages/response-ops/alerts_apis'], + setupFilesAfterEnv: ['/packages/response-ops/alerts_apis/setup_tests.ts'], +}; diff --git a/packages/response-ops/alerts_apis/kibana.jsonc b/packages/response-ops/alerts_apis/kibana.jsonc new file mode 100644 index 0000000000000..63e90cae3fbc7 --- /dev/null +++ b/packages/response-ops/alerts_apis/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-browser", + "id": "@kbn/response-ops-alerts-apis", + "owner": "@elastic/response-ops", + "group": "platform", + "visibility": "shared" +} diff --git a/packages/response-ops/alerts_apis/package.json b/packages/response-ops/alerts_apis/package.json new file mode 100644 index 0000000000000..7b0c1015879b7 --- /dev/null +++ b/packages/response-ops/alerts_apis/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/response-ops-alerts-apis", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0" +} \ No newline at end of file diff --git a/packages/response-ops/alerts_apis/setup_tests.ts b/packages/response-ops/alerts_apis/setup_tests.ts new file mode 100644 index 0000000000000..e8d6b49e9d94e --- /dev/null +++ b/packages/response-ops/alerts_apis/setup_tests.ts @@ -0,0 +1,11 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ +/* eslint-disable import/no-extraneous-dependencies */ +import '@testing-library/jest-dom'; +import 'jest-styled-components'; diff --git a/packages/response-ops/alerts_apis/tsconfig.json b/packages/response-ops/alerts_apis/tsconfig.json new file mode 100644 index 0000000000000..0ea4e7a36c6f2 --- /dev/null +++ b/packages/response-ops/alerts_apis/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core", + "@kbn/core-http-browser", + "@kbn/i18n", + "@kbn/alerts-ui-shared", + "@kbn/core-notifications-browser", + "@kbn/core-http-browser-mocks", + "@kbn/core-notifications-browser-mocks", + "@kbn/es-query" + ] +} diff --git a/packages/response-ops/alerts_apis/types.ts b/packages/response-ops/alerts_apis/types.ts new file mode 100644 index 0000000000000..b7e19750f6868 --- /dev/null +++ b/packages/response-ops/alerts_apis/types.ts @@ -0,0 +1,22 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { IHttpFetchError, ResponseErrorBody } from '@kbn/core-http-browser'; + +export type ServerError = IHttpFetchError; + +export interface ToggleAlertParams { + ruleId: string; + alertInstanceId: string; +} + +/** + * Map from rule ids to muted alert instance ids + */ +export type MutedAlerts = Record; diff --git a/packages/response-ops/alerts_fields_browser/README.md b/packages/response-ops/alerts_fields_browser/README.md new file mode 100644 index 0000000000000..db52eb2e30100 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/README.md @@ -0,0 +1,3 @@ +# @kbn/response-ops-alerts-fields-browser + +A picker component for alert document fields. diff --git a/src/platform/packages/shared/kbn-alerting-types/alert_type.ts b/packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.styles.ts similarity index 64% rename from src/platform/packages/shared/kbn-alerting-types/alert_type.ts rename to packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.styles.ts index 1663e8af556bf..a6fad874c2a6c 100644 --- a/src/platform/packages/shared/kbn-alerting-types/alert_type.ts +++ b/packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.styles.ts @@ -7,15 +7,12 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { TechnicalRuleDataFieldName } from '@kbn/rule-data-utils'; +import { css } from '@emotion/react'; +import { UseEuiTheme } from '@elastic/eui'; -export interface BasicFields { - _id: string; - _index: string; -} - -export type Alert = BasicFields & { - [Property in TechnicalRuleDataFieldName]?: string[]; -} & { - [x: string]: unknown[]; +export const styles = { + badgesGroup: ({ euiTheme }: { euiTheme: UseEuiTheme['euiTheme'] }) => css` + margin-top: ${euiTheme.size.xs}; + min-height: 24px; + `, }; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_badges/categories_badges.test.tsx b/packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.test.tsx similarity index 80% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_badges/categories_badges.test.tsx rename to packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.test.tsx index 2af93334298de..f52246f1aaa1c 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_badges/categories_badges.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.test.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { render } from '@testing-library/react'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_badges/categories_badges.tsx b/packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.tsx similarity index 80% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_badges/categories_badges.tsx rename to packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.tsx index 652d5ccbfafbf..e8bf89a55101c 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_badges/categories_badges.tsx +++ b/packages/response-ops/alerts_fields_browser/components/categories_badges/categories_badges.tsx @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import React, { useCallback } from 'react'; import { EuiBadge, EuiFlexGroup, EuiFlexItem, useEuiTheme } from '@elastic/eui'; import { styles } from './categories_badges.styles'; diff --git a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/types/alerts_types.ts b/packages/response-ops/alerts_fields_browser/components/categories_badges/index.ts similarity index 75% rename from src/platform/packages/shared/kbn-alerts-ui-shared/src/common/types/alerts_types.ts rename to packages/response-ops/alerts_fields_browser/components/categories_badges/index.ts index f06bf794f6ee4..e3a795a174f2a 100644 --- a/src/platform/packages/shared/kbn-alerts-ui-shared/src/common/types/alerts_types.ts +++ b/packages/response-ops/alerts_fields_browser/components/categories_badges/index.ts @@ -7,11 +7,5 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -export interface LegacyField { - field: string; - value: string[]; -} -export interface EsQuerySnapshot { - request: string[]; - response: string[]; -} +export { CategoriesBadges } from './categories_badges'; +export type { CategoriesBadgesProps } from './categories_badges'; diff --git a/packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.styles.ts b/packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.styles.ts new file mode 100644 index 0000000000000..693a40943a1ae --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.styles.ts @@ -0,0 +1,22 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { css } from '@emotion/react'; + +export const styles = { + countBadge: css` + margin-left: 5px; + `, + categoryName: ({ bold }: { bold: boolean }) => css` + font-weight: ${bold ? 'bold' : 'normal'}; + `, + selectableContainer: css` + width: 300px; + `, +}; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_selector/categories_selector.test.tsx b/packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.test.tsx similarity index 88% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_selector/categories_selector.test.tsx rename to packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.test.tsx index fa64f4953ab09..6ef4139ca787d 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_selector/categories_selector.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.test.tsx @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import React from 'react'; import { render } from '@testing-library/react'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_selector/categories_selector.tsx b/packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.tsx similarity index 91% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_selector/categories_selector.tsx rename to packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.tsx index c87bec9684678..f3f9b90a4e5d0 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/categories_selector/categories_selector.tsx +++ b/packages/response-ops/alerts_fields_browser/components/categories_selector/categories_selector.tsx @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import React, { useCallback, useMemo, useState } from 'react'; import { omit } from 'lodash'; import { @@ -17,7 +20,7 @@ import { EuiSelectable, FilterChecked, } from '@elastic/eui'; -import { BrowserFields } from '@kbn/rule-registry-plugin/common'; +import type { BrowserFields } from '@kbn/rule-registry-plugin/common'; import * as i18n from '../../translations'; import { getFieldCount, isEscape } from '../../helpers'; import { styles } from './categories_selector.styles'; diff --git a/packages/response-ops/alerts_fields_browser/components/categories_selector/index.ts b/packages/response-ops/alerts_fields_browser/components/categories_selector/index.ts new file mode 100644 index 0000000000000..62fa588b1dc1d --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/components/categories_selector/index.ts @@ -0,0 +1,10 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { CategoriesSelector } from './categories_selector'; diff --git a/packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.styles.ts b/packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.styles.ts new file mode 100644 index 0000000000000..1cb5da4d0dac5 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.styles.ts @@ -0,0 +1,17 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { css } from '@emotion/react'; + +export const styles = { + buttonContainer: css` + display: inline-block; + position: relative; + `, +}; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser.test.tsx b/packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.test.tsx similarity index 90% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser.test.tsx rename to packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.test.tsx index 32a6b1b85019b..88fdd2b503640 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.test.tsx @@ -1,17 +1,19 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import React from 'react'; import { act, fireEvent, render, waitFor } from '@testing-library/react'; import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; -import { mockBrowserFields } from './mock'; -import { FIELD_BROWSER_WIDTH } from './helpers'; +import { mockBrowserFields } from '../../mock'; +import { FIELD_BROWSER_WIDTH } from '../../helpers'; import { FieldBrowserComponent } from './field_browser'; -import type { FieldBrowserProps } from './types'; +import type { FieldBrowserProps } from '../../types'; const defaultProps: FieldBrowserProps = { browserFields: mockBrowserFields, diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser.tsx b/packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.tsx similarity index 89% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser.tsx rename to packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.tsx index 32fe7a6f74df8..738f4b2837210 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_browser/field_browser.tsx @@ -1,18 +1,21 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import { EuiButtonEmpty, EuiToolTip } from '@elastic/eui'; import { debounce } from 'lodash'; import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react'; -import { BrowserFields } from '@kbn/rule-registry-plugin/common'; -import type { FieldBrowserProps } from './types'; -import { FieldBrowserModal } from './field_browser_modal'; -import { filterBrowserFieldsByFieldName, filterSelectedBrowserFields } from './helpers'; -import * as i18n from './translations'; +import type { BrowserFields } from '@kbn/rule-registry-plugin/common'; +import type { FieldBrowserProps } from '../../types'; +import { FieldBrowserModal } from '../field_browser_modal/field_browser_modal'; +import { filterBrowserFieldsByFieldName, filterSelectedBrowserFields } from '../../helpers'; +import * as i18n from '../../translations'; import { styles } from './field_browser.styles'; const FIELDS_BUTTON_CLASS_NAME = 'fields-button'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser_modal.test.tsx b/packages/response-ops/alerts_fields_browser/components/field_browser_modal/field_browser_modal.test.tsx similarity index 89% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser_modal.test.tsx rename to packages/response-ops/alerts_fields_browser/components/field_browser_modal/field_browser_modal.test.tsx index 49b787041ace2..28f73aabfaca9 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser_modal.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_browser_modal/field_browser_modal.test.tsx @@ -1,14 +1,16 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { mount } from 'enzyme'; import React from 'react'; -import { mockBrowserFields } from './mock'; +import { mockBrowserFields } from '../../mock'; import { FieldBrowserModal, FieldBrowserModalProps } from './field_browser_modal'; const mockOnHide = jest.fn(); diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser_modal.tsx b/packages/response-ops/alerts_fields_browser/components/field_browser_modal/field_browser_modal.tsx similarity index 87% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser_modal.tsx rename to packages/response-ops/alerts_fields_browser/components/field_browser_modal/field_browser_modal.tsx index cff489c3c15be..f826ae7cb60c1 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/field_browser_modal.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_browser_modal/field_browser_modal.tsx @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import { EuiFlexGroup, EuiFlexItem, @@ -18,16 +21,20 @@ import { } from '@elastic/eui'; import React, { useCallback } from 'react'; -import { BrowserFields } from '@kbn/rule-registry-plugin/common'; -import type { FieldBrowserProps } from './types'; -import { Search } from './components/search'; - -import { CLOSE_BUTTON_CLASS_NAME, FIELD_BROWSER_WIDTH, RESET_FIELDS_CLASS_NAME } from './helpers'; +import type { BrowserFields } from '@kbn/rule-registry-plugin/common'; +import type { FieldBrowserProps } from '../../types'; +import { Search } from '../search'; -import * as i18n from './translations'; -import { CategoriesSelector } from './components/categories_selector'; -import { CategoriesBadges } from './components/categories_badges'; -import { FieldTable } from './components/field_table'; +import { + CLOSE_BUTTON_CLASS_NAME, + FIELD_BROWSER_WIDTH, + RESET_FIELDS_CLASS_NAME, +} from '../../helpers'; + +import * as i18n from '../../translations'; +import { CategoriesSelector } from '../categories_selector'; +import { CategoriesBadges } from '../categories_badges'; +import { FieldTable } from '../field_table'; export type FieldBrowserModalProps = Pick< FieldBrowserProps, diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.style.ts b/packages/response-ops/alerts_fields_browser/components/field_items/field_items.style.ts similarity index 56% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.style.ts rename to packages/response-ops/alerts_fields_browser/components/field_items/field_items.style.ts index d879b239dc599..2a835c43d70bf 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.style.ts +++ b/packages/response-ops/alerts_fields_browser/components/field_items/field_items.style.ts @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { css } from '@emotion/react'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.test.tsx b/packages/response-ops/alerts_fields_browser/components/field_items/field_items.test.tsx similarity index 96% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.test.tsx rename to packages/response-ops/alerts_fields_browser/components/field_items/field_items.test.tsx index f780c4f8f2f10..46bbf336de3cd 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_items/field_items.test.tsx @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import React from 'react'; import { omit } from 'lodash/fp'; import { render } from '@testing-library/react'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.tsx b/packages/response-ops/alerts_fields_browser/components/field_items/field_items.tsx similarity index 92% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.tsx rename to packages/response-ops/alerts_fields_browser/components/field_items/field_items.tsx index ae0f5d7670f1a..30e9e551da520 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_items/field_items.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_items/field_items.tsx @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import React from 'react'; import { EuiCheckbox, @@ -15,7 +18,7 @@ import { EuiScreenReaderOnly, } from '@elastic/eui'; import { uniqBy } from 'lodash/fp'; -import { BrowserFields } from '@kbn/rule-registry-plugin/common'; +import type { BrowserFields } from '@kbn/rule-registry-plugin/common'; import { EcsFlat } from '@elastic/ecs'; import { EcsMetadata } from '@kbn/alerts-as-data-utils/src/field_maps/types'; diff --git a/packages/response-ops/alerts_fields_browser/components/field_items/index.ts b/packages/response-ops/alerts_fields_browser/components/field_items/index.ts new file mode 100644 index 0000000000000..1373943bd99b5 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/components/field_items/index.ts @@ -0,0 +1,10 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { getFieldItemsData, getFieldColumns } from './field_items'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx b/packages/response-ops/alerts_fields_browser/components/field_name/field_name.test.tsx similarity index 70% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx rename to packages/response-ops/alerts_fields_browser/components/field_name/field_name.test.tsx index 3a589ac97e711..bf14f0228c4b0 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_name/field_name.test.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { mount } from 'enzyme'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.tsx b/packages/response-ops/alerts_fields_browser/components/field_name/field_name.tsx similarity index 59% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.tsx rename to packages/response-ops/alerts_fields_browser/components/field_name/field_name.tsx index 0ef0ce64c637b..8f64e690697d4 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_name/field_name.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_name/field_name.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import React from 'react'; diff --git a/packages/response-ops/alerts_fields_browser/components/field_name/index.ts b/packages/response-ops/alerts_fields_browser/components/field_name/index.ts new file mode 100644 index 0000000000000..7add3b194f33a --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/components/field_name/index.ts @@ -0,0 +1,10 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { FieldName } from './field_name'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.styles.ts b/packages/response-ops/alerts_fields_browser/components/field_table/field_table.styles.ts similarity index 54% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.styles.ts rename to packages/response-ops/alerts_fields_browser/components/field_table/field_table.styles.ts index 665848e47ab2b..6eaab01b01d9f 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.styles.ts +++ b/packages/response-ops/alerts_fields_browser/components/field_table/field_table.styles.ts @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import { UseEuiTheme } from '@elastic/eui'; import { css } from '@emotion/react'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.test.tsx b/packages/response-ops/alerts_fields_browser/components/field_table/field_table.test.tsx similarity index 93% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.test.tsx rename to packages/response-ops/alerts_fields_browser/components/field_table/field_table.test.tsx index 355cee63f2f32..5d85abbd6295a 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_table/field_table.test.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import React from 'react'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.tsx b/packages/response-ops/alerts_fields_browser/components/field_table/field_table.tsx similarity index 90% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.tsx rename to packages/response-ops/alerts_fields_browser/components/field_table/field_table.tsx index 7647e8c625761..d6ba96a4fca7b 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_table/field_table.tsx @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { EuiInMemoryTable, @@ -12,7 +15,7 @@ import { useEuiTheme, CriteriaWithPagination, } from '@elastic/eui'; -import { BrowserFields } from '@kbn/rule-registry-plugin/common'; +import type { BrowserFields } from '@kbn/rule-registry-plugin/common'; import { getFieldColumns, getFieldItemsData } from '../field_items'; import { CATEGORY_TABLE_CLASS_NAME, TABLE_HEIGHT } from '../../helpers'; import type { BrowserFieldItem, FieldBrowserProps, GetFieldTableColumns } from '../../types'; diff --git a/packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.styles.ts b/packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.styles.ts new file mode 100644 index 0000000000000..c04635d5efb49 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.styles.ts @@ -0,0 +1,16 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { css } from '@emotion/react'; + +export const styles = { + count: css` + font-weight: bold; + `, +}; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table_header.test.tsx b/packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.test.tsx similarity index 90% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table_header.test.tsx rename to packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.test.tsx index e46b025e86826..2bde369dcdff1 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table_header.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.test.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import React from 'react'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table_header.tsx b/packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.tsx similarity index 89% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table_header.tsx rename to packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.tsx index b1fa7d9b82ef9..5d65c6aa04137 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/field_table/field_table_header.tsx +++ b/packages/response-ops/alerts_fields_browser/components/field_table/field_table_header.tsx @@ -1,9 +1,12 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ + import React, { useCallback, useState } from 'react'; import { EuiText, diff --git a/packages/response-ops/alerts_fields_browser/components/field_table/index.ts b/packages/response-ops/alerts_fields_browser/components/field_table/index.ts new file mode 100644 index 0000000000000..52faf941ddf78 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/components/field_table/index.ts @@ -0,0 +1,11 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { FieldTable } from './field_table'; +export type { FieldTableProps } from './field_table'; diff --git a/packages/response-ops/alerts_fields_browser/components/search/index.ts b/packages/response-ops/alerts_fields_browser/components/search/index.ts new file mode 100644 index 0000000000000..aac6c19da1575 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/components/search/index.ts @@ -0,0 +1,10 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +export { Search } from './search'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/search/search.test.tsx b/packages/response-ops/alerts_fields_browser/components/search/search.test.tsx similarity index 81% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/search/search.test.tsx rename to packages/response-ops/alerts_fields_browser/components/search/search.test.tsx index 25390da9506cf..042d8fba36d41 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/search/search.test.tsx +++ b/packages/response-ops/alerts_fields_browser/components/search/search.test.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { mount } from 'enzyme'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/search/search.tsx b/packages/response-ops/alerts_fields_browser/components/search/search.tsx similarity index 67% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/search/search.tsx rename to packages/response-ops/alerts_fields_browser/components/search/search.tsx index f2b833700aa43..650d2ce69314e 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/components/search/search.tsx +++ b/packages/response-ops/alerts_fields_browser/components/search/search.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import React from 'react'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/helpers.test.ts b/packages/response-ops/alerts_fields_browser/helpers.test.ts similarity index 96% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/helpers.test.ts rename to packages/response-ops/alerts_fields_browser/helpers.test.ts index 3deddab635778..1951a0f11b37d 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/helpers.test.ts +++ b/packages/response-ops/alerts_fields_browser/helpers.test.ts @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { mockBrowserFields } from './mock'; @@ -15,7 +17,7 @@ import { filterBrowserFieldsByFieldName, filterSelectedBrowserFields, } from './helpers'; -import { BrowserFields } from '@kbn/rule-registry-plugin/common'; +import type { BrowserFields } from '@kbn/rule-registry-plugin/common'; import { EcsFlat } from '@elastic/ecs'; describe('helpers', () => { diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/helpers.ts b/packages/response-ops/alerts_fields_browser/helpers.ts similarity index 92% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/helpers.ts rename to packages/response-ops/alerts_fields_browser/helpers.ts index 6df7328aa2762..9df810d7f4c75 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/helpers.ts +++ b/packages/response-ops/alerts_fields_browser/helpers.ts @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { EcsMetadata } from '@kbn/alerts-as-data-utils/src/field_maps/types'; @@ -11,9 +13,9 @@ import { ALERT_MAINTENANCE_WINDOW_IDS, DefaultAlertFieldName, } from '@kbn/rule-data-utils'; -import { BrowserField, BrowserFields } from '@kbn/rule-registry-plugin/common'; +import type { BrowserField, BrowserFields } from '@kbn/rule-registry-plugin/common'; import { isEmpty } from 'lodash/fp'; -import { CASES, MAINTENANCE_WINDOWS } from '../translations'; +import { CASES, MAINTENANCE_WINDOWS } from './translations'; export const FIELD_BROWSER_WIDTH = 925; export const TABLE_HEIGHT = 260; diff --git a/packages/response-ops/alerts_fields_browser/index.ts b/packages/response-ops/alerts_fields_browser/index.ts new file mode 100644 index 0000000000000..d292ac79283fb --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/index.ts @@ -0,0 +1,13 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { FieldBrowser } from './components/field_browser/field_browser'; +export type { FieldBrowserProps, FieldBrowserOptions } from './types'; + +export { FieldBrowser }; diff --git a/packages/response-ops/alerts_fields_browser/jest.config.js b/packages/response-ops/alerts_fields_browser/jest.config.js new file mode 100644 index 0000000000000..b3517ca061fca --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/jest.config.js @@ -0,0 +1,15 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/packages/response-ops/alerts_fields_browser'], + setupFilesAfterEnv: ['/packages/response-ops/alerts_fields_browser/setup_tests.ts'], +}; diff --git a/packages/response-ops/alerts_fields_browser/kibana.jsonc b/packages/response-ops/alerts_fields_browser/kibana.jsonc new file mode 100644 index 0000000000000..ea5d46a04ae34 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/kibana.jsonc @@ -0,0 +1,7 @@ +{ + "type": "shared-browser", + "id": "@kbn/response-ops-alerts-fields-browser", + "owner": "@elastic/response-ops", + "group": "platform", + "visibility": "shared" +} diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/mock.ts b/packages/response-ops/alerts_fields_browser/mock.ts similarity index 97% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/mock.ts rename to packages/response-ops/alerts_fields_browser/mock.ts index 59d9c33838250..189078d135ef6 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/mock.ts +++ b/packages/response-ops/alerts_fields_browser/mock.ts @@ -1,12 +1,14 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ -import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { BrowserFields } from '@kbn/rule-registry-plugin/common'; +import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types'; +import type { BrowserFields } from '@kbn/rule-registry-plugin/common'; const DEFAULT_INDEX_PATTERN = [ 'apm-*-transaction*', diff --git a/packages/response-ops/alerts_fields_browser/package.json b/packages/response-ops/alerts_fields_browser/package.json new file mode 100644 index 0000000000000..93efb418dbbdf --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/response-ops-alerts-fields-browser", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0 OR AGPL-3.0-only OR SSPL-1.0" +} \ No newline at end of file diff --git a/packages/response-ops/alerts_fields_browser/setup_tests.ts b/packages/response-ops/alerts_fields_browser/setup_tests.ts new file mode 100644 index 0000000000000..5ebc6d3dac1ca --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/setup_tests.ts @@ -0,0 +1,11 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +// eslint-disable-next-line import/no-extraneous-dependencies +import '@testing-library/jest-dom'; diff --git a/packages/response-ops/alerts_fields_browser/translations.ts b/packages/response-ops/alerts_fields_browser/translations.ts new file mode 100644 index 0000000000000..121a9df0ee3f5 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/translations.ts @@ -0,0 +1,118 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { i18n } from '@kbn/i18n'; + +export const CASES = i18n.translate('responseOpsAlertsFieldsBrowser.cases.label', { + defaultMessage: 'Cases', +}); + +export const MAINTENANCE_WINDOWS = i18n.translate( + 'responseOpsAlertsFieldsBrowser.maintenanceWindows.label', + { + defaultMessage: 'Maintenance Windows', + } +); + +export const CATEGORY = i18n.translate('responseOpsAlertsFieldsBrowser.categoryLabel', { + defaultMessage: 'Category', +}); + +export const CATEGORIES = i18n.translate('responseOpsAlertsFieldsBrowser.categoriesTitle', { + defaultMessage: 'Categories', +}); + +export const CATEGORIES_COUNT = (totalCount: number) => + i18n.translate('responseOpsAlertsFieldsBrowser.categoriesCountTitle', { + values: { totalCount }, + defaultMessage: '{totalCount} {totalCount, plural, =1 {category} other {categories}}', + }); + +export const CLOSE = i18n.translate('responseOpsAlertsFieldsBrowser.closeButton', { + defaultMessage: 'Close', +}); + +export const FIELDS_BROWSER = i18n.translate('responseOpsAlertsFieldsBrowser.fieldBrowserTitle', { + defaultMessage: 'Fields', +}); + +export const DESCRIPTION = i18n.translate('responseOpsAlertsFieldsBrowser.descriptionLabel', { + defaultMessage: 'Description', +}); + +export const DESCRIPTION_FOR_FIELD = (field: string) => + i18n.translate('responseOpsAlertsFieldsBrowser.descriptionForScreenReaderOnly', { + values: { + field, + }, + defaultMessage: 'Description for field {field}:', + }); + +export const NAME = i18n.translate('responseOpsAlertsFieldsBrowser.fieldName', { + defaultMessage: 'Name', +}); + +export const FIELD = i18n.translate('responseOpsAlertsFieldsBrowser.fieldLabel', { + defaultMessage: 'Field', +}); + +export const FIELDS = i18n.translate('responseOpsAlertsFieldsBrowser.fieldsTitle', { + defaultMessage: 'Fields', +}); + +export const FIELDS_SHOWING = i18n.translate('responseOpsAlertsFieldsBrowser.fieldsCountShowing', { + defaultMessage: 'Showing', +}); + +export const FIELDS_COUNT = (totalCount: number) => + i18n.translate('responseOpsAlertsFieldsBrowser.fieldsCountTitle', { + values: { totalCount }, + defaultMessage: '{totalCount, plural, =1 {field} other {fields}}', + }); + +export const FILTER_PLACEHOLDER = i18n.translate( + 'responseOpsAlertsFieldsBrowser.filterPlaceholder', + { + defaultMessage: 'Field name', + } +); + +export const NO_FIELDS_MATCH = i18n.translate('responseOpsAlertsFieldsBrowser.noFieldsMatchLabel', { + defaultMessage: 'No fields match', +}); + +export const NO_FIELDS_MATCH_INPUT = (searchInput: string) => + i18n.translate('responseOpsAlertsFieldsBrowser.noFieldsMatchInputLabel', { + defaultMessage: 'No fields match {searchInput}', + values: { + searchInput, + }, + }); + +export const RESET_FIELDS = i18n.translate('responseOpsAlertsFieldsBrowser.resetFieldsLink', { + defaultMessage: 'Reset Fields', +}); + +export const VIEW_COLUMN = (field: string) => + i18n.translate('responseOpsAlertsFieldsBrowser.viewColumnCheckboxAriaLabel', { + values: { field }, + defaultMessage: 'View {field} column', + }); + +export const VIEW_LABEL = i18n.translate('responseOpsAlertsFieldsBrowser.viewLabel', { + defaultMessage: 'View', +}); + +export const VIEW_VALUE_SELECTED = i18n.translate('responseOpsAlertsFieldsBrowser.viewSelected', { + defaultMessage: 'selected', +}); + +export const VIEW_VALUE_ALL = i18n.translate('responseOpsAlertsFieldsBrowser.viewAll', { + defaultMessage: 'all', +}); diff --git a/packages/response-ops/alerts_fields_browser/tsconfig.json b/packages/response-ops/alerts_fields_browser/tsconfig.json new file mode 100644 index 0000000000000..8036e7dede0a5 --- /dev/null +++ b/packages/response-ops/alerts_fields_browser/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "@emotion/react/types/css-prop" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx" + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/i18n", + "@kbn/rule-registry-plugin", + "@kbn/alerts-as-data-utils", + "@kbn/rule-data-utils", + ] +} diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/types.ts b/packages/response-ops/alerts_fields_browser/types.ts similarity index 78% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/types.ts rename to packages/response-ops/alerts_fields_browser/types.ts index 898fd67140837..deb845dbc9caf 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/field_browser/types.ts +++ b/packages/response-ops/alerts_fields_browser/types.ts @@ -1,12 +1,14 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import type { EuiBasicTableColumn } from '@elastic/eui'; -import { BrowserFields } from '@kbn/rule-registry-plugin/common'; +import type { BrowserFields } from '@kbn/rule-registry-plugin/common'; /** * An item rendered in the table diff --git a/packages/response-ops/alerts_table/README.md b/packages/response-ops/alerts_table/README.md new file mode 100644 index 0000000000000..e507383166d37 --- /dev/null +++ b/packages/response-ops/alerts_table/README.md @@ -0,0 +1,175 @@ +# @kbn/response-ops-alerts-table + +An abstraction on top of `EuiDataGrid` dedicated to rendering alert documents. + +## Usage + +In addition to `EuiDataGrid`'s functionality, the table manages the paginated and cached fetching of alerts, based on +the provided `ruleTypeIds` and `consumers` (the final query can be refined through the `query` and `initialSort` props). +The `id` prop is used to persist the table state in `localStorage`. + +```tsx + +``` + +## Columns + +Just like in `EuiDataGrid`, the columns are customizable through the `columns` prop. In addition to those, the table +renders an "Actions" column with default alert call-to-actions and provides row selection and bulk actions +functionality. + +```tsx +// The @kbn/rule-data-utils package exports constants +// for many common alert field keys +import { ALERT_RULE_NAME } from '@kbn/rule-data-utils'; + + +``` + +## Cells, popovers and flyouts + +All the sub-components of the table are customizable through the `render*` +props (`renderCellValue`, `renderCellPopover`, `renderActionsCell`, etc.). Values passed to these props are treated as +components, allowing hooks, context, and other React concepts to be used. + +```tsx +const CustomCellValue: GetAlertsTableProp<'renderCellValue'> = ({ alert }) => { + // ... +}; + + +``` + +## Render context + +All the sub-component renderers receive as part of their props a context object ( +see [EuiDataGrid's `cellContext`](https://eui.elastic.co/#/tabular-content/data-grid-cells-popovers%23cell-context)) +with common utilities and services (i.e. the fetched alerts, loading states etc.). +You can add properties to this context by means of the `additionalContext` prop: + +```tsx + { + // ... + }} +/> +``` + +The context type is inferred based on the `additionalContext` prop and all render functions props are typed accordingly. +To avoid prop drilling, you can use the `useAlertsTableContext` hook to access the same context in any sub-component. + +```tsx +const CustomCellValue = ({ alert }) => { + const { alertsCount, myCustomProperty } = useAlertsTableContext(); + + // ... +}; +``` + +In order to define your custom sub-components separately from the table but still benefit from the context type +inference, you may want to extract props from the `AlertsTableProps` type. The `GetAlertsTableProp` utility type is +provided for this: it extracts the type of a specific prop from the `AlertsTableProps` type, excluding `undefined` in +case of optional props. + +```tsx +import type { GetAlertsTableProp } from '@kbn/response-ops-alerts-table/types'; + +export const CustomCellValue: GetAlertsTableProp<'renderCellValue'> = ({ alert }) => { + // ... +}; +``` + +If you also have an additional context, you can define a type for it and wrap the `AlertsTableProps`, providing it as a +generic: + +```tsx +import type { AlertsTableProps } from '@kbn/response-ops-alerts-table/types'; + +interface MyAdditionalContext { + myCustomProperty: string; +} + +export type MyAlertsTableProps = AlertsTableProps; +export type GetMyAlertsTableProp = Exclude; + +export const CustomCellValue: GetMyAlertsTableProp<'renderCellValue'> = ({ myCustomProperty }) => { + // ... +}; + + + additionalContext={{ + myCustomProperty: 'my custom value', + }} + renderCellValue={CustomCellValue} +/> +``` + +## Dependencies + +The table relies on the following Kibana services, expected in the `services` prop: + +- `data` +- `http` +- `notifications` +- `fieldFormats` +- `application` +- `licensing` +- `settings` +- `cases` (optional) + +## Integrations + +The table has built-in integration with Maintenance Windows and Cases. If alerts have maintenance windows or cases +associated to them, they will be loaded and made available through the `maintenanceWindows` and `cases` properties of +the render context. +A special cell renderer is used by default for the `kibana.alert.maintenance_window_ids` and `kibana.alert.case_ids` +columns. + +## Lazy loading + +Contrary to the previous implementation exported by `triggersActionsUI`, this package doesn't prescribe how to lazy load +the table component; a default export is just provided for convenience. However, do consider that +the `React.lazy` function loses the original generic types of the component. To make the type inference work correctly, +you can assert the type of the lazy loaded component using a type import: + +```tsx +import type { AlertsTable as AlertsTableType } from '@kbn/response-ops-alerts-table'; + +const AlertsTable = React.lazy(() => import('@kbn/response-ops-alerts-table')) as AlertsTableType; +``` + +## Mocking + +When mocking the table, keep in mind that the component is manually typed as a normal function component (to keep its +generic types), but it's actually a memoized, forwardRef'ed component. To mock it properly, mock the entire module: + +```tsx +jest.mock('@kbn/response-ops-alerts-table', () => ({ + AlertsTable: jest.fn().mockImplementation(() =>
), +})); +``` + +## What's new compared to `triggersActionsUI`? + +- The alerts table registry was removed. The table is now a standalone component and the configuration is based entirely + on props. +- All the custom renderers (cell, cell popover, actions cell, etc.) are now exposed as strongly typed props (`render*`). +- More `EuiDataGrid` props are exposed for customization. diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_cases.test.ts b/packages/response-ops/alerts_table/apis/bulk_get_cases.test.ts similarity index 68% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_cases.test.ts rename to packages/response-ops/alerts_table/apis/bulk_get_cases.test.ts index 6368bafb9c054..575ac3ce7abae 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_cases.test.ts +++ b/packages/response-ops/alerts_table/apis/bulk_get_cases.test.ts @@ -1,14 +1,16 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { bulkGetCases } from './bulk_get_cases'; import { coreMock } from '@kbn/core/public/mocks'; -describe('Bulk Get Cases API', () => { +describe('bulkGetCases', () => { const abortCtrl = new AbortController(); const mockCoreSetup = coreMock.createSetup(); const http = mockCoreSetup.http; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_cases.ts b/packages/response-ops/alerts_table/apis/bulk_get_cases.ts similarity index 60% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_cases.ts rename to packages/response-ops/alerts_table/apis/bulk_get_cases.ts index 02b5fc1a509f9..08b6df7ba5078 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_cases.ts +++ b/packages/response-ops/alerts_table/apis/bulk_get_cases.ts @@ -1,12 +1,15 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { CaseStatuses } from '@kbn/cases-components'; import { HttpStart } from '@kbn/core-http-browser'; + const INTERNAL_BULK_GET_CASES_URL = '/internal/cases/_bulk_get'; export interface Case { @@ -37,15 +40,9 @@ export interface CasesBulkGetResponse { }>; } -export const bulkGetCases = async ( - http: HttpStart, - params: { ids: string[] }, - signal?: AbortSignal -): Promise => { - const res = await http.post(INTERNAL_BULK_GET_CASES_URL, { +export const bulkGetCases = (http: HttpStart, params: { ids: string[] }, signal?: AbortSignal) => { + return http.post(INTERNAL_BULK_GET_CASES_URL, { body: JSON.stringify({ ...params }), signal, }); - - return res; }; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_maintenance_windows.test.ts b/packages/response-ops/alerts_table/apis/bulk_get_maintenance_windows.test.ts similarity index 70% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_maintenance_windows.test.ts rename to packages/response-ops/alerts_table/apis/bulk_get_maintenance_windows.test.ts index d59d4a8339cdb..c6d0638be3f04 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_maintenance_windows.test.ts +++ b/packages/response-ops/alerts_table/apis/bulk_get_maintenance_windows.test.ts @@ -1,14 +1,16 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { coreMock } from '@kbn/core/public/mocks'; import { bulkGetMaintenanceWindows } from './bulk_get_maintenance_windows'; -describe('Bulk Get Maintenance Windows API', () => { +describe('bulkGetMaintenanceWindows', () => { const mockCoreSetup = coreMock.createSetup(); const http = mockCoreSetup.http; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_maintenance_windows.ts b/packages/response-ops/alerts_table/apis/bulk_get_maintenance_windows.ts similarity index 75% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_maintenance_windows.ts rename to packages/response-ops/alerts_table/apis/bulk_get_maintenance_windows.ts index c16dfa91d6a86..6e830f10fecb4 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/hooks/apis/bulk_get_maintenance_windows.ts +++ b/packages/response-ops/alerts_table/apis/bulk_get_maintenance_windows.ts @@ -1,16 +1,16 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ -import { HttpStart } from '@kbn/core-http-browser'; -import { - MaintenanceWindow, - INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH, -} from '@kbn/alerting-plugin/common'; -import { AsApiContract } from '@kbn/actions-plugin/common'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { MaintenanceWindow } from '@kbn/alerting-plugin/common'; +import type { AsApiContract } from '@kbn/actions-plugin/common'; +import { INTERNAL_ALERTING_API_MAINTENANCE_WINDOW_PATH } from '../constants'; export interface BulkGetMaintenanceWindowsParams { http: HttpStart; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/assets/illustration_product_no_results_magnifying_glass.svg b/packages/response-ops/alerts_table/assets/illustration_product_no_results_magnifying_glass.svg similarity index 100% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/assets/illustration_product_no_results_magnifying_glass.svg rename to packages/response-ops/alerts_table/assets/illustration_product_no_results_magnifying_glass.svg diff --git a/packages/response-ops/alerts_table/components/actions_cell_host.test.tsx b/packages/response-ops/alerts_table/components/actions_cell_host.test.tsx new file mode 100644 index 0000000000000..6ab52ed9c19c2 --- /dev/null +++ b/packages/response-ops/alerts_table/components/actions_cell_host.test.tsx @@ -0,0 +1,49 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { ComponentProps, useEffect } from 'react'; +import { screen, render } from '@testing-library/react'; +import { ActionsCellHost } from './actions_cell_host'; +import { createPartialObjectMock } from '../utils/test'; +import { mockRenderContext } from '../mocks/context.mock'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; + +const props = createPartialObjectMock>({ + ...mockRenderContext, + rowIndex: 0, +}); + +describe('ActionsCellHost', () => { + it('should render the provided custom actions cell', () => { + render( + ( +
+ ))} + /> + ); + expect(screen.getByTestId('renderActionsCell')).toBeInTheDocument(); + }); + + it('should catch errors from the custom actions cell', async () => { + const CustomActionsCell = () => { + useEffect(() => { + throw new Error('test error'); + }, []); + return null; + }; + render( + + + + ); + expect(await screen.findByTestId('errorCell')).toBeInTheDocument(); + }); +}); diff --git a/packages/response-ops/alerts_table/components/actions_cell_host.tsx b/packages/response-ops/alerts_table/components/actions_cell_host.tsx new file mode 100644 index 0000000000000..131a4edc1be24 --- /dev/null +++ b/packages/response-ops/alerts_table/components/actions_cell_host.tsx @@ -0,0 +1,75 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import { SetRequired } from 'type-fest'; +import React, { ComponentProps, useCallback } from 'react'; +import { EuiFlexGroup } from '@elastic/eui'; +import { typedMemo } from '../utils/react'; +import { AdditionalContext, AlertsTableProps, BulkActionsVerbs } from '../types'; +import { ErrorBoundary } from './error_boundary'; +import { ErrorCell } from './error_cell'; + +/** + * Entry point for rendering actions cells (in control columns) + */ +export const ActionsCellHost = typedMemo( + ( + // The actions control column is active only when the user provided the `renderActionsCell` prop + props: SetRequired< + ComponentProps['renderActionsCell']>>, + 'renderActionsCell' + > + ) => { + const { + rowIndex, + pageSize, + pageIndex, + alerts, + oldAlertsData, + ecsAlertsData, + bulkActionsStore, + renderActionsCell: ActionsCell, + visibleRowIndex, + } = props; + const idx = rowIndex - pageSize * pageIndex; + const alert = alerts[idx]; + const legacyAlert = oldAlertsData[idx]; + const ecsAlert = ecsAlertsData[idx]; + const [, updateBulkActionsState] = bulkActionsStore; + + const setIsActionLoading = useCallback( + (_isLoading: boolean = true) => { + updateBulkActionsState({ + action: BulkActionsVerbs.updateRowLoadingState, + rowIndex: visibleRowIndex, + isLoading: _isLoading, + }); + }, + [visibleRowIndex, updateBulkActionsState] + ); + + if (!alert) { + return null; + } + + return ( + + + ['renderActionsCell']>>)} + alert={alert} + legacyAlert={legacyAlert} + ecsAlert={ecsAlert} + setIsActionLoading={setIsActionLoading} + /> + + + ); + } +); diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.test.tsx b/packages/response-ops/alerts_table/components/alert_lifecycle_status_cell.test.tsx similarity index 51% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.test.tsx rename to packages/response-ops/alerts_table/components/alert_lifecycle_status_cell.test.tsx index b4800e25428f9..d2e76b06f1bee 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.test.tsx +++ b/packages/response-ops/alerts_table/components/alert_lifecycle_status_cell.test.tsx @@ -1,72 +1,64 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import React from 'react'; -import { screen } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; +import { Alert } from '@kbn/alerting-types'; import { AlertLifecycleStatusCell } from './alert_lifecycle_status_cell'; import { CellComponentProps } from '../types'; -import { Alert } from '../../../../types'; -import { AppMockRenderer, createAppMockRenderer } from '../../test_utils'; -import { getCasesMockMap } from '../cases/index.mock'; -import { getMaintenanceWindowMockMap } from '../maintenance_windows/index.mock'; - -jest.mock('../../../../common/lib/kibana'); +import { getCasesMapMock } from '../mocks/cases.mock'; +import { getMaintenanceWindowsMapMock } from '../mocks/maintenance_windows.mock'; describe('AlertLifecycleStatusCell', () => { - const casesMap = getCasesMockMap(); - const maintenanceWindowsMap = getMaintenanceWindowMockMap(); - const alert = { + const casesMap = getCasesMapMock(); + const maintenanceWindowsMap = getMaintenanceWindowsMapMock(); + const alert: Alert = { _id: 'alert-id', _index: 'alert-index', 'kibana.alert.status': ['active'], - } as Alert; + }; - const props: CellComponentProps = { + const props = { isLoading: false, alert, cases: casesMap, maintenanceWindows: maintenanceWindowsMap, columnId: 'kibana.alert.status', showAlertStatusWithFlapping: true, - }; - - let appMockRender: AppMockRenderer; - - beforeEach(() => { - appMockRender = createAppMockRenderer(); - }); + // Assertion used to avoid defining all the (numerous) context properties + } as CellComponentProps; it('shows the status', async () => { - appMockRender.render(); + render(); expect(screen.getByText('Active')).toBeInTheDocument(); }); it('does not shows the status if showAlertStatusWithFlapping=false', async () => { - appMockRender.render( - - ); + render(); expect(screen.queryByText('Active')).not.toBeInTheDocument(); }); it('shows the status with flapping', async () => { - appMockRender.render( + render( ); expect(screen.getByText('Flapping')).toBeInTheDocument(); }); it('shows the status with multiple values', async () => { - appMockRender.render( + render( ); @@ -74,12 +66,7 @@ describe('AlertLifecycleStatusCell', () => { }); it('shows the default cell if the status is empty', async () => { - appMockRender.render( - - ); + render(); expect(screen.getByText('--')).toBeInTheDocument(); }); diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.tsx b/packages/response-ops/alerts_table/components/alert_lifecycle_status_cell.tsx similarity index 61% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.tsx rename to packages/response-ops/alerts_table/components/alert_lifecycle_status_cell.tsx index 8c04b48ee7670..a4ea204f2a8d6 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/cells/alert_lifecycle_status_cell.tsx +++ b/packages/response-ops/alerts_table/components/alert_lifecycle_status_cell.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import { AlertStatus, ALERT_FLAPPING, ALERT_STATUS } from '@kbn/rule-data-utils'; @@ -10,12 +12,12 @@ import React, { memo } from 'react'; import { EuiBadge, EuiFlexGroup, EuiToolTip, useEuiTheme } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { css } from '@emotion/react'; -import { AlertLifecycleStatusBadge } from '../../../components/alert_lifecycle_status_badge'; -import { CellComponentProps } from '../types'; +import { AlertLifecycleStatusBadge } from '@kbn/alerts-ui-shared'; import { DefaultCell } from './default_cell'; -import { useAlertMutedState } from '../hooks/alert_mute/use_alert_muted_state'; +import { useAlertMutedState } from '../hooks/use_alert_muted_state'; +import type { CellComponent } from '../types'; -const AlertLifecycleStatusCellComponent: React.FC = (props) => { +export const AlertLifecycleStatusCell: CellComponent = memo((props) => { const { euiTheme } = useEuiTheme(); const { alert, showAlertStatusWithFlapping } = props; const { isMuted } = useAlertMutedState(alert); @@ -24,16 +26,16 @@ const AlertLifecycleStatusCellComponent: React.FC = (props) return null; } - const alertStatus = (alert && alert[ALERT_STATUS]) ?? []; + const alertStatus = (alert?.[ALERT_STATUS] ?? []) as string[] | undefined; if (Array.isArray(alertStatus) && alertStatus.length) { - const flapping = (alert && alert[ALERT_FLAPPING]) ?? []; + const flapping = alert?.[ALERT_FLAPPING]?.[0] as boolean | undefined; return ( {isMuted && ( = (props) } return ; -}; - -AlertLifecycleStatusCellComponent.displayName = 'AlertLifecycleStatusCell'; - -export const AlertLifecycleStatusCell = memo(AlertLifecycleStatusCellComponent); +}); diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/alerts_count/alerts_count.tsx b/packages/response-ops/alerts_table/components/alerts_count.tsx similarity index 73% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/alerts_count/alerts_count.tsx rename to packages/response-ops/alerts_table/components/alerts_count.tsx index 45ca382686752..f72b543bcfd26 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/toolbar/components/alerts_count/alerts_count.tsx +++ b/packages/response-ops/alerts_table/components/alerts_count.tsx @@ -1,8 +1,10 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ import React, { useMemo } from 'react'; diff --git a/packages/response-ops/alerts_table/components/alerts_data_grid.test.tsx b/packages/response-ops/alerts_table/components/alerts_data_grid.test.tsx new file mode 100644 index 0000000000000..0d5c39feb01cb --- /dev/null +++ b/packages/response-ops/alerts_table/components/alerts_data_grid.test.tsx @@ -0,0 +1,496 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { FunctionComponent, useMemo, useReducer } from 'react'; +import { fireEvent, render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { waitForEuiPopoverOpen } from '@elastic/eui/lib/test/rtl'; +import { AlertsDataGrid } from './alerts_data_grid'; +import { AlertsDataGridProps, BulkActionsState } from '../types'; +import { AdditionalContext, RenderContext } from '../types'; +import { EuiButton, EuiButtonIcon, EuiDataGridColumnCellAction, EuiFlexItem } from '@elastic/eui'; +import { bulkActionsReducer } from '../reducers/bulk_actions_reducer'; +import { getJsDomPerformanceFix } from '../utils/test'; +import { useCaseViewNavigation } from '../hooks/use_case_view_navigation'; +import { act } from 'react-dom/test-utils'; +import { AlertsTableContextProvider } from '../contexts/alerts_table_context'; +import { + mockRenderContext, + mockColumns, + mockAlerts, + createMockBulkActionsState, +} from '../mocks/context.mock'; +import { + CELL_ACTIONS_EXPAND_TEST_ID, + CELL_ACTIONS_POPOVER_TEST_ID, + FIELD_BROWSER_BTN_TEST_ID, + FIELD_BROWSER_CUSTOM_CREATE_BTN_TEST_ID, + FIELD_BROWSER_TEST_ID, +} from '../constants'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { testQueryClientConfig } from '@kbn/alerts-ui-shared/src/common/test_utils/test_query_client_config'; +import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; +import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context'; + +jest.mock('../hooks/use_case_view_navigation'); + +const cellActionOnClickMockedFn = jest.fn(); + +const { fix, cleanup } = getJsDomPerformanceFix(); + +beforeAll(() => { + fix(); +}); + +afterAll(() => { + cleanup(); +}); + +export type BaseAlertsDataGridProps = AlertsDataGridProps; +export type TestAlertsDataGridProps = Partial> & { + renderContext?: Partial>; +}; + +const queryClient = new QueryClient(testQueryClientConfig); + +export const mockDataGridProps: Partial = { + pageSizeOptions: [1, 10, 20, 50, 100], + leadingControlColumns: [], + trailingControlColumns: [], + visibleColumns: mockColumns.map((c) => c.id), + 'data-test-subj': 'testTable', + onToggleColumn: jest.fn(), + onResetColumns: jest.fn(), + onChangeVisibleColumns: jest.fn(), + query: {}, + sort: [], + alertsQuerySnapshot: { request: [], response: [] }, + onSortChange: jest.fn(), + onChangePageIndex: jest.fn(), + onChangePageSize: jest.fn(), + getBulkActions: () => [ + { + id: 0, + items: [ + { + label: 'Fake Bulk Action', + key: 'fakeBulkAction', + 'data-test-subj': 'fake-bulk-action', + disableOnQuery: false, + onClick: () => {}, + }, + ], + }, + ], + fieldsBrowserOptions: { + createFieldButton: () => , + }, + casesConfiguration: { featureId: 'test-feature-id', owner: ['cases'] }, +}; + +describe('AlertsDataGrid', () => { + const useCaseViewNavigationMock = useCaseViewNavigation as jest.Mock; + useCaseViewNavigationMock.mockReturnValue({ navigateToCaseView: jest.fn() }); + + const TestComponent: React.FunctionComponent< + Omit & { + initialBulkActionsState?: BulkActionsState; + renderContext?: Partial>; + } + > = (props) => { + const bulkActionsStore = useReducer( + bulkActionsReducer, + props.initialBulkActionsState || createMockBulkActionsState() + ); + const renderContext = useMemo( + () => ({ + ...mockRenderContext, + bulkActionsStore, + ...props.renderContext, + }), + [bulkActionsStore, props.renderContext] + ); + + return ( + + + + + + + + ); + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('Alerts table UI', () => { + it('should support sorting', async () => { + const { container } = render(); + await userEvent.click(container.querySelector('.euiDataGridHeaderCell__button')!, { + pointerEventsCheck: 0, + }); + + await waitForEuiPopoverOpen(); + + await userEvent.click( + screen.getByTestId(`dataGridHeaderCellActionGroup-${mockColumns[0].id}`), + { + pointerEventsCheck: 0, + } + ); + + await userEvent.click(screen.getByTitle('Sort A-Z'), { + pointerEventsCheck: 0, + }); + + expect(mockDataGridProps.onSortChange).toHaveBeenCalledWith([ + { direction: 'asc', id: 'kibana.alert.rule.name' }, + ]); + }); + + it('should support pagination', async () => { + render(); + await userEvent.click(screen.getByTestId('pagination-button-1'), { + pointerEventsCheck: 0, + }); + + expect(mockDataGridProps.onChangePageIndex).toHaveBeenCalledWith(1); + }); + + it('should show when it was updated', () => { + render(); + expect(screen.getByTestId('toolbar-updated-at')).not.toBe(null); + }); + + it('should show alerts count', () => { + render(); + expect(screen.getByTestId('toolbar-alerts-count')).not.toBe(null); + }); + + it('should show alert status', () => { + render( + + ); + expect(screen.queryAllByTestId('alertLifecycleStatusBadge')[0].textContent).toEqual( + 'Flapping' + ); + expect(screen.queryAllByTestId('alertLifecycleStatusBadge')[1].textContent).toEqual('Active'); + expect(screen.queryAllByTestId('alertLifecycleStatusBadge')[2].textContent).toEqual( + 'Recovered' + ); + expect(screen.queryAllByTestId('alertLifecycleStatusBadge')[3].textContent).toEqual( + 'Recovered' + ); + }); + + describe('leading control columns', () => { + it('should render other leading controls', () => { + const props: TestAlertsDataGridProps = { + ...mockDataGridProps, + leadingControlColumns: [ + { + id: 'selection', + width: 67, + headerCellRender: () => Test header, + rowCellRender: () =>

Test cell

, + }, + ], + }; + render(); + expect(screen.queryByTestId('testHeader')).not.toBe(null); + expect(screen.queryByTestId('testCell')).not.toBe(null); + }); + }); + + describe('actions column', () => { + it('should render custom actions cells', () => { + render( + ( + <> + + {}} + size="s" + data-test-subj="testAction" + aria-label="testActionLabel" + /> + + + {}} + size="s" + data-test-subj="testAction2" + aria-label="testActionLabel2" + /> + + + ), + }} + /> + ); + expect(screen.queryByTestId('testAction')).toBeInTheDocument(); + expect(screen.queryByTestId('testAction2')).toBeInTheDocument(); + expect(screen.queryByTestId('expandColumnCellOpenFlyoutButton-0')).not.toBeInTheDocument(); + }); + + it('should render no action column if neither the action nor the expand action config is set', () => { + render( + + ); + expect(screen.queryByTestId('expandColumnHeaderLabel')).not.toBeInTheDocument(); + expect(screen.queryByTestId('expandColumnCellOpenFlyoutButton')).not.toBeInTheDocument(); + }); + + describe('row loading state on action', () => { + type ExtractFunctionComponent = T extends FunctionComponent ? T : never; + const mockRenderActionsCell = jest.fn( + mockRenderContext.renderActionsCell as ExtractFunctionComponent< + typeof mockRenderContext.renderActionsCell + > + ); + const props: TestAlertsDataGridProps = { + ...mockDataGridProps, + actionsColumnWidth: 124, + renderContext: { + pageSize: 10, + renderActionsCell: mockRenderActionsCell, + }, + }; + + it('should show the row loader when callback triggered', async () => { + render(); + fireEvent.click((await screen.findAllByTestId('testAction'))[0]); + + // the callback given to our clients to run when they want to update the loading state + act(() => { + mockRenderActionsCell.mock.calls[0][0].setIsActionLoading!(true); + }); + + expect(await screen.findAllByTestId('row-loader')).toHaveLength(1); + const selectedOptions = await screen.findAllByTestId('dataGridRowCell'); + + // first row, first column + expect(within(selectedOptions[0]).getByLabelText('Loading')).toBeDefined(); + expect( + within(selectedOptions[0]).queryByTestId('bulk-actions-row-cell') + ).not.toBeInTheDocument(); + + // second row, first column + expect(within(selectedOptions[6]).queryByLabelText('Loading')).not.toBeInTheDocument(); + expect(within(selectedOptions[6]).getByTestId('bulk-actions-row-cell')).toBeDefined(); + }); + + it('should show the row loader when callback triggered with false', async () => { + const initialBulkActionsState = { + ...createMockBulkActionsState(), + rowSelection: new Map([[0, { isLoading: true }]]), + }; + + render(); + fireEvent.click((await screen.findAllByTestId('testAction'))[0]); + + // the callback given to our clients to run when they want to update the loading state + act(() => { + mockRenderActionsCell.mock.calls[0][0].setIsActionLoading!(false); + }); + + expect(screen.queryByTestId('row-loader')).not.toBeInTheDocument(); + }); + }); + }); + + describe('cell Actions', () => { + const mockGetCellActionsForColumn = jest.fn( + (columnId: string): EuiDataGridColumnCellAction[] => [ + ({ rowIndex, Component }) => { + const label = 'Fake Cell First Action'; + return ( + cellActionOnClickMockedFn(columnId, rowIndex)} + data-test-subj={'fake-cell-first-action'} + iconType="refresh" + aria-label={label} + /> + ); + }, + ] + ); + const props: TestAlertsDataGridProps = { + ...mockDataGridProps, + cellActionsOptions: { + getCellActionsForColumn: mockGetCellActionsForColumn, + visibleCellActions: 2, + disabledCellActions: [], + }, + }; + + it('should render cell actions on hover', async () => { + render(); + + const reasonFirstRow = (await screen.findAllByTestId('dataGridRowCell'))[3]; + + fireEvent.mouseOver(reasonFirstRow); + + expect(await screen.findByTestId('fake-cell-first-action')).toBeInTheDocument(); + }); + + it('should render expandable cell actions', async () => { + render(); + const reasonFirstRow = (await screen.findAllByTestId('dataGridRowCell'))[3]; + + fireEvent.mouseOver(reasonFirstRow); + + expect(await screen.findByTestId(CELL_ACTIONS_EXPAND_TEST_ID)).toBeVisible(); + + fireEvent.click(await screen.findByTestId(CELL_ACTIONS_EXPAND_TEST_ID)); + + expect(await screen.findByTestId(CELL_ACTIONS_POPOVER_TEST_ID)).toBeVisible(); + expect(await screen.findAllByLabelText(/fake cell first action/i)).toHaveLength(2); + }); + }); + + describe('Fields browser', () => { + it('fields browser is working correctly', async () => { + render( + + ); + + const fieldBrowserBtn = screen.getByTestId(FIELD_BROWSER_BTN_TEST_ID); + expect(fieldBrowserBtn).toBeVisible(); + + fireEvent.click(fieldBrowserBtn); + + expect(await screen.findByTestId(FIELD_BROWSER_TEST_ID)).toBeVisible(); + + expect(await screen.findByTestId(FIELD_BROWSER_CUSTOM_CREATE_BTN_TEST_ID)).toBeVisible(); + }); + + it('syncs the columns state correctly between the column selector and the field selector', async () => { + const columnToHide = mockColumns[0]; + render( + + ); + + const fieldBrowserBtn = await screen.findByTestId(FIELD_BROWSER_BTN_TEST_ID); + const columnSelectorBtn = await screen.findByTestId('dataGridColumnSelectorButton'); + + // Open the column visibility selector and hide the column + fireEvent.click(columnSelectorBtn); + const columnVisibilityToggle = await screen.findByTestId( + `dataGridColumnSelectorToggleColumnVisibility-${columnToHide.id}` + ); + fireEvent.click(columnVisibilityToggle); + + // Open the field browser + fireEvent.click(fieldBrowserBtn); + expect(await screen.findByTestId(FIELD_BROWSER_TEST_ID)).toBeVisible(); + + // The column should be checked in the field browser, independent of its visibility status + const columnCheckbox: HTMLInputElement = await screen.findByTestId( + `field-${columnToHide.id}-checkbox` + ); + expect(columnCheckbox).toBeChecked(); + }); + }); + + describe('cases column', () => { + const props: TestAlertsDataGridProps = { + ...mockDataGridProps, + renderContext: { + pageSize: mockAlerts.length, + }, + }; + + it('should show the cases column', async () => { + render(); + expect(await screen.findByText('Cases')).toBeInTheDocument(); + }); + + it('should show the cases titles correctly', async () => { + render(); + expect(await screen.findByText('Test case')).toBeInTheDocument(); + expect(await screen.findByText('Test case 2')).toBeInTheDocument(); + }); + + it('show loading skeleton if it loads cases', async () => { + render( + + ); + + expect((await screen.findAllByTestId('cases-cell-loading')).length).toBe(4); + }); + + it('shows the cases tooltip', async () => { + render(); + expect(await screen.findByText('Test case')).toBeInTheDocument(); + + await userEvent.hover(screen.getByText('Test case')); + + expect(await screen.findByTestId('cases-components-tooltip')).toBeInTheDocument(); + }); + }); + + describe('dynamic row height mode', () => { + it('should render a non-virtualized grid body when the dynamicRowHeight option is on', async () => { + const { container } = render(); + + expect(container.querySelector('.euiDataGrid__customRenderBody')).toBeTruthy(); + }); + + it('should render a virtualized grid body when the dynamicRowHeight option is off', async () => { + const { container } = render(); + + expect(container.querySelector('.euiDataGrid__virtualized')).toBeTruthy(); + }); + }); + }); +}); diff --git a/packages/response-ops/alerts_table/components/alerts_data_grid.tsx b/packages/response-ops/alerts_table/components/alerts_data_grid.tsx new file mode 100644 index 0000000000000..0b8a107555214 --- /dev/null +++ b/packages/response-ops/alerts_table/components/alerts_data_grid.tsx @@ -0,0 +1,387 @@ +/* + * 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", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +import React, { FC, lazy, Suspense, useCallback, useMemo } from 'react'; +import { + EuiDataGrid, + EuiDataGridControlColumn, + EuiDataGridProps, + EuiDataGridStyle, + RenderCellValue, + tint, + useEuiTheme, +} from '@elastic/eui'; +import { css } from '@emotion/react'; +import { euiThemeVars } from '@kbn/ui-theme'; +import { ActionsCellHost } from './actions_cell_host'; +import { ControlColumnHeaderCell } from './control_column_header_cell'; +import { CellValueHost } from './cell_value_host'; +import { BulkActionsCell } from './bulk_actions_cell'; +import { BulkActionsHeader } from './bulk_actions_header_cell'; +import { AdditionalContext, AlertsDataGridProps, CellActionsOptions } from '../types'; +import { useGetToolbarVisibility } from '../hooks/use_toolbar_visibility'; +import { InspectButtonContainer } from './alerts_query_inspector'; +import { typedMemo } from '../utils/react'; +import type { AlertsFlyout as AlertsFlyoutType } from './alerts_flyout'; +import { useBulkActions } from '../hooks/use_bulk_actions'; +import { useSorting } from '../hooks/use_sorting'; +import { CellPopoverHost } from './cell_popover_host'; +import { NonVirtualizedGridBody } from './non_virtualized_grid_body'; + +const AlertsFlyout = lazy(() => import('./alerts_flyout')) as typeof AlertsFlyoutType; + +const defaultGridStyle: EuiDataGridStyle = { + border: 'none', + header: 'underline', + fontSize: 's', +}; +const defaultCellActionsOptions: CellActionsOptions = { + getCellActionsForColumn: () => [], + disabledCellActions: [], +}; +const DEFAULT_PAGE_SIZE_OPTIONS = [10, 20, 50, 100]; +const DEFAULT_ACTIONS_COLUMN_WIDTH = 75; +const stableMappedRowClasses: EuiDataGridStyle['rowClasses'] = {}; + +export const AlertsDataGrid = typedMemo( + (props: AlertsDataGridProps) => { + const { + ruleTypeIds, + query, + visibleColumns, + onToggleColumn, + onResetColumns, + onChangeVisibleColumns, + onColumnResize, + showInspectButton = false, + leadingControlColumns: additionalLeadingControlColumns, + trailingControlColumns, + onSortChange, + sort: sortingFields, + rowHeightsOptions, + dynamicRowHeight, + alertsQuerySnapshot, + additionalToolbarControls, + toolbarVisibility: toolbarVisibilityProp, + shouldHighlightRow, + renderContext, + hideBulkActions, + casesConfiguration, + flyoutAlertIndex, + setFlyoutAlertIndex, + onPaginateFlyout, + onChangePageSize, + onChangePageIndex, + actionsColumnWidth = DEFAULT_ACTIONS_COLUMN_WIDTH, + getBulkActions, + fieldsBrowserOptions, + cellActionsOptions, + pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS, + height, + ...euiDataGridProps + } = props; + const { + isLoading, + alerts, + alertsCount, + isLoadingAlerts, + browserFields, + renderActionsCell, + pageIndex, + pageSize, + refresh: refreshQueries, + columns, + dataGridRef, + services: { http, notifications, application, cases: casesService, settings }, + } = renderContext; + + const { colorMode } = useEuiTheme(); + const { sortingColumns, onSort } = useSorting(onSortChange, visibleColumns, sortingFields); + const { + isBulkActionsColumnActive, + bulkActionsState, + bulkActions, + setIsBulkActionsLoading, + clearSelection, + } = useBulkActions({ + ruleTypeIds, + query, + alertsCount: alerts.length, + casesConfig: casesConfiguration, + getBulkActions, + refresh: refreshQueries, + hideBulkActions, + http, + notifications, + application, + casesService, + }); + + const refresh = useCallback(() => { + refreshQueries(); + clearSelection(); + }, [clearSelection, refreshQueries]); + + const columnIds = useMemo(() => columns.map((column) => column.id), [columns]); + + const toolbarVisibility = useGetToolbarVisibility({ + bulkActions, + alertsCount, + rowSelection: bulkActionsState.rowSelection, + alerts, + isLoading, + columnIds, + onToggleColumn, + onResetColumns, + browserFields, + additionalToolbarControls, + setIsBulkActionsLoading, + clearSelection, + refresh, + fieldsBrowserOptions, + alertsQuerySnapshot, + showInspectButton, + toolbarVisibilityProp, + settings, + }); + + const leadingControlColumns: EuiDataGridControlColumn[] | undefined = useMemo(() => { + const controlColumns = [ + ...(additionalLeadingControlColumns ?? []), + ...(isBulkActionsColumnActive + ? [ + { + id: 'bulkActions', + width: 30, + headerCellRender: BulkActionsHeader, + rowCellRender: BulkActionsCell, + }, + ] + : []), + // If the user provided an actions cell renderer, add the actions column + ...(renderActionsCell + ? [ + { + id: 'expandColumn', + width: actionsColumnWidth, + headerCellRender: ControlColumnHeaderCell, + // Though untyped, rowCellRender's CellPropsWithContext contains the correct context + rowCellRender: + ActionsCellHost as unknown as EuiDataGridControlColumn['rowCellRender'], + }, + ] + : []), + ]; + if (controlColumns.length) { + return controlColumns; + } + }, [ + additionalLeadingControlColumns, + isBulkActionsColumnActive, + renderActionsCell, + actionsColumnWidth, + ]); + + const flyoutRowIndex = flyoutAlertIndex + pageIndex * pageSize; + + // Row classes do not deal with visible row indices, so we need to handle page offset + const activeRowClasses = useMemo>( + () => ({ + [flyoutRowIndex]: 'alertsTableActiveRow', + }), + [flyoutRowIndex] + ); + + const handleFlyoutClose = useCallback(() => setFlyoutAlertIndex(-1), [setFlyoutAlertIndex]); + + const dataGridPagination = useMemo( + () => ({ + pageIndex, + pageSize, + pageSizeOptions, + onChangeItemsPerPage: onChangePageSize, + onChangePage: onChangePageIndex, + }), + [onChangePageIndex, onChangePageSize, pageIndex, pageSize, pageSizeOptions] + ); + + const { getCellActionsForColumn, visibleCellActions, disabledCellActions } = + cellActionsOptions ?? defaultCellActionsOptions; + + const columnsWithCellActions = useMemo(() => { + if (getCellActionsForColumn) { + return columns.map((col, idx) => ({ + ...col, + ...(!(disabledCellActions ?? []).includes(col.id) + ? { + cellActions: getCellActionsForColumn(col.id, idx) ?? [], + visibleCellActions, + } + : {}), + })); + } + return columns; + }, [getCellActionsForColumn, columns, disabledCellActions, visibleCellActions]); + + // Update highlighted rows when alerts or pagination changes + const highlightedRowClasses = useMemo(() => { + if (shouldHighlightRow) { + const emptyShouldHighlightRow: EuiDataGridStyle['rowClasses'] = {}; + return alerts.reduce>( + (rowClasses, alert, index) => { + if (shouldHighlightRow(alert)) { + rowClasses[index + pageIndex * pageSize] = 'alertsTableHighlightedRow'; + } + + return rowClasses; + }, + emptyShouldHighlightRow + ); + } else { + return stableMappedRowClasses; + } + }, [shouldHighlightRow, alerts, pageIndex, pageSize]); + + const mergedGridStyle = useMemo(() => { + const propGridStyle: NonNullable = props.gridStyle ?? {}; + // Merges default row classes, custom ones and adds the active row class style + return { + ...defaultGridStyle, + ...propGridStyle, + rowClasses: { + // We're spreading the highlighted row classes first, so that the active + // row classed can override the highlighted row classes. + ...highlightedRowClasses, + ...activeRowClasses, + }, + }; + }, [activeRowClasses, highlightedRowClasses, props.gridStyle]); + + // Merges the default grid style with the grid style that comes in through props. + const actualGridStyle = useMemo(() => { + const propGridStyle: NonNullable = props.gridStyle ?? {}; + // If ANY additional rowClasses have been provided, we need to merge them with our internal ones + if (propGridStyle.rowClasses) { + // Get all row indices with a rowClass. + const mergedKeys = [ + ...Object.keys(mergedGridStyle.rowClasses || {}), + ...Object.keys(propGridStyle.rowClasses || {}), + ]; + // Deduplicate keys to avoid extra iterations + const dedupedKeys = Array.from(new Set(mergedKeys)); + + // For each index, merge row classes + mergedGridStyle.rowClasses = dedupedKeys.reduce< + NonNullable + >((rowClasses, key) => { + const intKey = parseInt(key, 10); + // Use internal row classes over custom row classes. + rowClasses[intKey] = + mergedGridStyle.rowClasses?.[intKey] || propGridStyle.rowClasses?.[intKey] || ''; + return rowClasses; + }, {}); + } + return mergedGridStyle; + }, [props.gridStyle, mergedGridStyle]); + + const renderCustomGridBody = useCallback>( + ({ visibleColumns: _visibleColumns, Cell, headerRow, footerRow }) => ( + <> + {headerRow} + + {footerRow} + + ), + [alerts, actualGridStyle, pageIndex, pageSize, isLoadingAlerts, props.gridStyle?.stripes] + ); + + const sortProps = useMemo(() => { + return { columns: sortingColumns, onSort }; + }, [sortingColumns, onSort]); + + const columnVisibility = useMemo(() => { + return { visibleColumns, setVisibleColumns: onChangeVisibleColumns }; + }, [visibleColumns, onChangeVisibleColumns]); + + const rowStyles = useMemo( + () => css` + .alertsTableHighlightedRow { + background-color: ${euiThemeVars.euiColorHighlight}; + } + + .alertsTableActiveRow { + background-color: ${colorMode === 'LIGHT' + ? tint(euiThemeVars.euiColorLightShade, 0.5) + : euiThemeVars.euiColorLightShade}; + } + `, + [colorMode] + ); + + return ( + +
+ + {flyoutAlertIndex > -1 && ( + + {...renderContext} + alert={alerts[flyoutAlertIndex]} + alertsCount={alertsCount} + onClose={handleFlyoutClose} + flyoutIndex={flyoutAlertIndex + pageIndex * pageSize} + onPaginate={onPaginateFlyout} + /> + )} + + {alertsCount > 0 && ( + + )} +
+
+ ); + } +); + +(AlertsDataGrid as FC).displayName = 'AlertsDataGrid'; diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.test.tsx b/packages/response-ops/alerts_table/components/alerts_flyout.test.tsx similarity index 54% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.test.tsx rename to packages/response-ops/alerts_table/components/alerts_flyout.test.tsx index 7bfb4773cca39..cb14ea876358e 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.test.tsx +++ b/packages/response-ops/alerts_table/components/alerts_flyout.test.tsx @@ -1,54 +1,57 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ -import React from 'react'; + +import React, { ComponentProps } from 'react'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { act } from 'react-dom/test-utils'; import { AlertsFlyout } from './alerts_flyout'; -import { Alert, AlertsField } from '../../../../types'; +import { AlertsField, FlyoutSectionRenderer } from '../types'; +import { createPartialObjectMock } from '../utils/test'; + +type FlyoutProps = ComponentProps; const onClose = jest.fn(); const onPaginate = jest.fn(); -const props = { +const props = createPartialObjectMock({ alert: { - [AlertsField.name]: ['one'], - [AlertsField.reason]: ['two'], _id: '0123456789', _index: '.alerts-default', - } as unknown as Alert, - alertsTableConfiguration: { - id: 'test', - casesFeatureId: 'testCases', - columns: [ - { - id: AlertsField.name, - displayAsText: 'Name', - initialWidth: 150, - }, - { - id: AlertsField.reason, - displayAsText: 'Reason', - initialWidth: 250, - }, - ], - useInternalFlyout: () => ({ - body: () =>

Internal flyout body

, - header: null, - footer: () => null, - }), - getRenderCellValue: jest.fn().mockImplementation((rcvProps) => { - return `${rcvProps.colIndex}:${rcvProps.rowIndex}`; - }), + [AlertsField.name]: ['one'], + [AlertsField.reason]: ['two'], }, + tableId: 'test', + columns: [ + { + id: AlertsField.name, + displayAsText: 'Name', + initialWidth: 150, + }, + { + id: AlertsField.reason, + displayAsText: 'Reason', + initialWidth: 250, + }, + ], + renderCellValue: jest.fn((rcvProps) => { + return ( + <> + `${rcvProps.colIndex}:${rcvProps.rowIndex}` + + ); + }), + renderFlyoutBody: () =>

Internal flyout body

, flyoutIndex: 0, alertsCount: 4, isLoading: false, onClose, onPaginate, -}; +}); describe('AlertsFlyout', () => { afterEach(() => { @@ -61,25 +64,15 @@ describe('AlertsFlyout', () => { await nextTick(); wrapper.update(); }); - expect(wrapper.find('h3').first().text()).toBe('Internal flyout body'); + expect(wrapper.find('[data-test-subj="test-flyout-body"]').first().text()).toBe( + 'Internal flyout body' + ); }); - const base = { - body: () => null, - header: () => null, - footer: () => null, - }; - it(`should use header from useInternalFlyout configuration`, async () => { - const customProps = { + it(`should use header from the alerts table props`, async () => { + const customProps: FlyoutProps = { ...props, - alertsTableConfiguration: { - ...props.alertsTableConfiguration, - useInternalFlyout: () => ({ - ...base, - header: () =>

Header

, - footer: () => null, - }), - }, + renderFlyoutHeader: () =>

Header

, }; const wrapper = mountWithIntl(); await act(async () => { @@ -89,16 +82,10 @@ describe('AlertsFlyout', () => { expect(wrapper.find('h4').first().text()).toBe('Header'); }); - it(`should use body from useInternalFlyout configuration`, async () => { - const customProps = { + it(`should use body the alerts table props`, async () => { + const customProps: FlyoutProps = { ...props, - alertsTableConfiguration: { - ...props.alertsTableConfiguration, - useInternalFlyout: () => ({ - ...base, - body: () =>
Body
, - }), - }, + renderFlyoutBody: () =>
Body
, }; const wrapper = mountWithIntl(); await act(async () => { @@ -108,16 +95,10 @@ describe('AlertsFlyout', () => { expect(wrapper.find('h5').first().text()).toBe('Body'); }); - it(`should use footer from useInternalFlyout configuration`, async () => { - const customProps = { + it(`should use footer from the alerts table props`, async () => { + const customProps: FlyoutProps = { ...props, - alertsTableConfiguration: { - ...props.alertsTableConfiguration, - useInternalFlyout: () => ({ - ...base, - footer: () =>
Footer
, - }), - }, + renderFlyoutFooter: () =>
Footer
, }; const wrapper = mountWithIntl(); await act(async () => { diff --git a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx b/packages/response-ops/alerts_table/components/alerts_flyout.tsx similarity index 51% rename from x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx rename to packages/response-ops/alerts_table/components/alerts_flyout.tsx index a25af7cb5386e..22ad07b249c06 100644 --- a/x-pack/platform/plugins/shared/triggers_actions_ui/public/application/sections/alerts_table/alerts_flyout/alerts_flyout.tsx +++ b/packages/response-ops/alerts_table/components/alerts_flyout.tsx @@ -1,10 +1,13 @@ /* * 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. + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". */ -import React, { Suspense, lazy, useCallback, useMemo, useRef, useEffect } from 'react'; + +import React, { Suspense, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiFlyout, @@ -15,9 +18,16 @@ import { EuiPagination, EuiProgress, } from '@elastic/eui'; -import type { Alert, AlertsTableConfigurationRegistry } from '../../../../types'; +import usePrevious from 'react-use/lib/usePrevious'; +import type { Alert } from '@kbn/alerting-types'; +import { DefaultAlertsFlyoutBody, DefaultAlertsFlyoutHeader } from './default_alerts_flyout'; +import { + AdditionalContext, + FlyoutSectionProps, + FlyoutSectionRenderer, + RenderContext, +} from '../types'; -const AlertsFlyoutHeader = lazy(() => import('./alerts_flyout_header')); const PAGINATION_LABEL = i18n.translate( 'xpack.triggersActionsUI.sections.alertsTable.alertsFlyout.paginationLabel', { @@ -25,93 +35,74 @@ const PAGINATION_LABEL = i18n.translate( } ); -function usePrevious(alert: Alert) { - const ref = useRef(null); - useEffect(() => { - if (alert) { - ref.current = alert; - } - }); - return ref.current; -} - -interface AlertsFlyoutProps { +export const AlertsFlyout = ({ + alert, + ...renderContext +}: RenderContext & { alert: Alert; - alertsTableConfiguration: AlertsTableConfigurationRegistry; flyoutIndex: number; - alertsCount: number; isLoading: boolean; onClose: () => void; onPaginate: (pageIndex: number) => void; - id?: string; -} -export const AlertsFlyout: React.FunctionComponent = ({ - alert, - alertsTableConfiguration, - flyoutIndex, - alertsCount, - isLoading, - onClose, - onPaginate, - id, -}: AlertsFlyoutProps) => { +}) => { const { - header: Header, - body: Body, - footer: Footer, - } = alertsTableConfiguration?.useInternalFlyout?.() ?? { - header: AlertsFlyoutHeader, - body: null, - footer: null, - }; + flyoutIndex, + alertsCount, + onClose, + onPaginate, + isLoading, + renderFlyoutHeader: Header = DefaultAlertsFlyoutHeader, + renderFlyoutBody: Body = DefaultAlertsFlyoutBody, + renderFlyoutFooter, + } = renderContext; + const Footer: FlyoutSectionRenderer | undefined = renderFlyoutFooter; const prevAlert = usePrevious(alert); - const passedProps = useMemo( - () => ({ - alert: alert === undefined && prevAlert != null ? prevAlert : alert, - id, - isLoading, - }), + const props = useMemo( + () => + ({ + ...renderContext, + // Show the previous alert while loading the next one + alert: alert === undefined && prevAlert != null ? prevAlert : alert, + } as FlyoutSectionProps), // eslint-disable-next-line react-hooks/exhaustive-deps - [alert, id, isLoading] + [alert, renderContext] ); - const FlyoutBody = useCallback( + const FlyoutHeader = useCallback( () => - Body ? ( + Header ? ( - + {...props} /> ) : null, - [Body, passedProps] + [Header, props] ); - const FlyoutFooter = useCallback( + const FlyoutBody = useCallback( () => - Footer ? ( + Body ? ( -