mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[ResponseOps][Alerts] Migrate alerts fields fetching to TanStack Query (#188320)
## Summary Migrates the `useFetchBrowserFieldCapabilities` hook (renamed to `useFetchAlertsFieldsQuery`) to TanStack Query, following [this organizational logic](https://github.com/elastic/kibana/issues/186448#issuecomment-2228853337). This PR focuses mainly on the fetching logic itself, leaving the surrounding API surface mostly unchanged since it will be likely addressed in subsequent PRs. ## To verify 1. Create rules that fire alerts in different solutions 2. Check that the alerts table usages work correctly ({O11y, Security, Stack} alerts and rule details pages, ...) 1. Check that the alerts displayed in the table are coherent with the solution, KQL query, time filter, pagination 2. Check that pagination changes are reflected in the table 3. Check that changing the query when in pages > 0 resets the pagination to the first page 4. Check that the fields browser shows and works correctly (`Fields` button in the alerts table header) Closes point 2 of https://github.com/elastic/kibana/issues/186448 ### Checklist - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d62334fd58
commit
f24e0cd43c
24 changed files with 655 additions and 525 deletions
29
packages/kbn-alerting-types/alert_fields_type.ts
Normal file
29
packages/kbn-alerting-types/alert_fields_type.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
import type { IFieldSubType } from '@kbn/es-query';
|
||||
import type { RuntimeField } from '@kbn/data-views-plugin/common';
|
||||
|
||||
export interface BrowserField {
|
||||
aggregatable: boolean;
|
||||
category: string;
|
||||
description?: string | null;
|
||||
example?: string | number | null;
|
||||
fields: Readonly<Record<string, Partial<BrowserField>>>;
|
||||
format?: SerializedFieldFormat;
|
||||
indexes: string[];
|
||||
name: string;
|
||||
searchable: boolean;
|
||||
type: string;
|
||||
subType?: IFieldSubType;
|
||||
readFromDocValues: boolean;
|
||||
runtimeField?: RuntimeField;
|
||||
}
|
||||
|
||||
export type BrowserFields = Record<string, Partial<BrowserField>>;
|
|
@ -8,12 +8,13 @@
|
|||
|
||||
export * from './action_group_types';
|
||||
export * from './action_variable';
|
||||
export * from './alert_fields_type';
|
||||
export * from './alert_type';
|
||||
export * from './alerting_framework_health_types';
|
||||
export * from './builtin_action_groups_types';
|
||||
export * from './circuit_breaker_message_header';
|
||||
export * from './r_rule_types';
|
||||
export * from './rule_notify_when_type';
|
||||
export * from './search_strategy_types';
|
||||
export * from './rule_type_types';
|
||||
export * from './rule_types';
|
||||
export * from './search_strategy_types';
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
"@kbn/rrule",
|
||||
"@kbn/core",
|
||||
"@kbn/es-query",
|
||||
"@kbn/field-formats-plugin",
|
||||
"@kbn/data-views-plugin",
|
||||
"@kbn/search-types"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { httpServiceMock } from '@kbn/core/public/mocks';
|
||||
import { fetchAlertsFields } from '.';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
describe('fetchAlertsFields', () => {
|
||||
const http = httpServiceMock.createStartContract();
|
||||
test('should call the browser_fields API with the correct parameters', async () => {
|
||||
const featureIds = [AlertConsumers.STACK_ALERTS];
|
||||
http.get.mockResolvedValueOnce({
|
||||
browserFields: { fakeCategory: {} },
|
||||
fields: [
|
||||
{
|
||||
name: 'fakeCategory',
|
||||
},
|
||||
],
|
||||
});
|
||||
const result = await fetchAlertsFields({ http, featureIds });
|
||||
expect(result).toEqual({
|
||||
browserFields: { fakeCategory: {} },
|
||||
fields: [
|
||||
{
|
||||
name: 'fakeCategory',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(http.get).toHaveBeenLastCalledWith('/internal/rac/alerts/browser_fields', {
|
||||
query: { featureIds },
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { FieldDescriptor } from '@kbn/data-views-plugin/server';
|
||||
import type { BrowserFields } from '@kbn/alerting-types';
|
||||
import type { FetchAlertsFieldsParams } from './types';
|
||||
import { BASE_RAC_ALERTS_API_PATH } from '../../constants';
|
||||
|
||||
export const fetchAlertsFields = ({ http, featureIds }: FetchAlertsFieldsParams) =>
|
||||
http.get<{ browserFields: BrowserFields; fields: FieldDescriptor[] }>(
|
||||
`${BASE_RAC_ALERTS_API_PATH}/browser_fields`,
|
||||
{
|
||||
query: { featureIds },
|
||||
}
|
||||
);
|
|
@ -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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './fetch_alerts_fields';
|
||||
export * from './types';
|
|
@ -6,23 +6,16 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import { FieldSpec } from '@kbn/data-views-plugin/common';
|
||||
import { BASE_RAC_ALERTS_API_PATH } from '../constants';
|
||||
|
||||
export async function fetchAlertFields({
|
||||
http,
|
||||
featureIds,
|
||||
}: {
|
||||
export interface FetchAlertsFieldsParams {
|
||||
// Dependencies
|
||||
http: HttpSetup;
|
||||
|
||||
// Params
|
||||
/**
|
||||
* Array of feature ids used for authorization and area-based filtering
|
||||
*/
|
||||
featureIds: ValidFeatureId[];
|
||||
}): Promise<FieldSpec[]> {
|
||||
const { fields: alertFields = [] } = await http.get<{ fields: FieldSpec[] }>(
|
||||
`${BASE_RAC_ALERTS_API_PATH}/browser_fields`,
|
||||
{
|
||||
query: { featureIds },
|
||||
}
|
||||
);
|
||||
return alertFields;
|
||||
}
|
|
@ -6,12 +6,13 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export * from './fetch_ui_health_status';
|
||||
export * from './fetch_alerting_framework_health';
|
||||
export * from './fetch_ui_config';
|
||||
export * from './create_rule';
|
||||
export * from './update_rule';
|
||||
export * from './resolve_rule';
|
||||
export * from './fetch_connectors';
|
||||
export * from './fetch_alerting_framework_health';
|
||||
export * from './fetch_alerts_fields';
|
||||
export * from './fetch_connector_types';
|
||||
export * from './fetch_connectors';
|
||||
export * from './fetch_rule_type_aad_template_fields';
|
||||
export * from './fetch_ui_config';
|
||||
export * from './fetch_ui_health_status';
|
||||
export * from './resolve_rule';
|
||||
export * from './update_rule';
|
||||
|
|
|
@ -13,8 +13,8 @@ import { AlertConsumers, ValidFeatureId } from '@kbn/rule-data-utils';
|
|||
import type { ToastsStart, HttpStart } from '@kbn/core/public';
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useFetchAlertsFieldsQuery } from './use_fetch_alerts_fields_query';
|
||||
import { fetchAlertIndexNames } from '../apis/fetch_alert_index_names';
|
||||
import { fetchAlertFields } from '../apis/fetch_alert_fields';
|
||||
|
||||
export interface UseAlertDataViewResult {
|
||||
dataViews?: DataView[];
|
||||
|
@ -45,10 +45,6 @@ export function useAlertDataView(props: UseAlertDataViewProps): UseAlertDataView
|
|||
return fetchAlertIndexNames({ http, features });
|
||||
};
|
||||
|
||||
const queryAlertFieldsFn = () => {
|
||||
return fetchAlertFields({ http, featureIds });
|
||||
};
|
||||
|
||||
const onErrorFn = () => {
|
||||
toasts.addDanger(
|
||||
i18n.translate('alertsUIShared.hooks.useAlertDataView.useAlertDataMessage', {
|
||||
|
@ -72,18 +68,19 @@ export function useAlertDataView(props: UseAlertDataViewProps): UseAlertDataView
|
|||
});
|
||||
|
||||
const {
|
||||
data: alertFields,
|
||||
data: { fields: alertFields },
|
||||
isSuccess: isAlertFieldsSuccess,
|
||||
isInitialLoading: isAlertFieldsInitialLoading,
|
||||
isLoading: isAlertFieldsLoading,
|
||||
} = useQuery({
|
||||
queryKey: ['loadAlertFields', features],
|
||||
queryFn: queryAlertFieldsFn,
|
||||
onError: onErrorFn,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: 60 * 1000,
|
||||
enabled: hasNoSecuritySolution,
|
||||
});
|
||||
} = useFetchAlertsFieldsQuery(
|
||||
{ http, featureIds },
|
||||
{
|
||||
onError: onErrorFn,
|
||||
refetchOnWindowFocus: false,
|
||||
staleTime: 60 * 1000,
|
||||
enabled: hasNoSecuritySolution,
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import type { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { testQueryClientConfig } from '../test_utils/test_query_client_config';
|
||||
import { useFetchAlertsFieldsQuery } from './use_fetch_alerts_fields_query';
|
||||
|
||||
const queryClient = new QueryClient(testQueryClientConfig);
|
||||
|
||||
const wrapper: FunctionComponent = ({ children }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
);
|
||||
|
||||
const mockHttpClient = {
|
||||
get: jest.fn(),
|
||||
} as unknown as HttpSetup;
|
||||
|
||||
const emptyData = { browserFields: {}, fields: [] };
|
||||
|
||||
describe('useFetchAlertsFieldsQuery', () => {
|
||||
const mockHttpGet = jest.mocked(mockHttpClient.get);
|
||||
|
||||
beforeEach(() => {
|
||||
mockHttpGet.mockResolvedValue({
|
||||
browserFields: { fakeCategory: {} },
|
||||
fields: [
|
||||
{
|
||||
name: 'fakeCategory',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
mockHttpGet.mockClear();
|
||||
queryClient.clear();
|
||||
});
|
||||
|
||||
it('should not fetch for siem', () => {
|
||||
const { result } = renderHook(
|
||||
() => useFetchAlertsFieldsQuery({ http: mockHttpClient, featureIds: ['siem'] }),
|
||||
{
|
||||
wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
expect(mockHttpGet).toHaveBeenCalledTimes(0);
|
||||
expect(result.current.data).toEqual(emptyData);
|
||||
});
|
||||
|
||||
it('should call the api only once', async () => {
|
||||
const { result, rerender, waitForValueToChange } = renderHook(
|
||||
() => useFetchAlertsFieldsQuery({ http: mockHttpClient, featureIds: ['apm'] }),
|
||||
{
|
||||
wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
await waitForValueToChange(() => result.current.data);
|
||||
|
||||
expect(mockHttpGet).toHaveBeenCalledTimes(1);
|
||||
expect(result.current.data).toEqual({
|
||||
browserFields: { fakeCategory: {} },
|
||||
fields: [
|
||||
{
|
||||
name: 'fakeCategory',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
rerender();
|
||||
|
||||
expect(mockHttpGet).toHaveBeenCalledTimes(1);
|
||||
expect(result.current.data).toEqual({
|
||||
browserFields: { fakeCategory: {} },
|
||||
fields: [
|
||||
{
|
||||
name: 'fakeCategory',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fetch if the only featureId is not valid', async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useFetchAlertsFieldsQuery({
|
||||
http: mockHttpClient,
|
||||
featureIds: ['alerts'] as unknown as AlertConsumers[],
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
expect(mockHttpGet).toHaveBeenCalledTimes(0);
|
||||
expect(result.current.data).toEqual(emptyData);
|
||||
});
|
||||
|
||||
it('should not fetch if all featureId are not valid', async () => {
|
||||
const { result } = renderHook(
|
||||
() =>
|
||||
useFetchAlertsFieldsQuery({
|
||||
http: mockHttpClient,
|
||||
featureIds: ['alerts', 'tomato'] as unknown as AlertConsumers[],
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
expect(mockHttpGet).toHaveBeenCalledTimes(0);
|
||||
expect(result.current.data).toEqual(emptyData);
|
||||
});
|
||||
|
||||
it('should filter out the non valid feature id', async () => {
|
||||
renderHook(
|
||||
() =>
|
||||
useFetchAlertsFieldsQuery({
|
||||
http: mockHttpClient,
|
||||
featureIds: ['alerts', 'apm', 'logs'] as AlertConsumers[],
|
||||
}),
|
||||
{
|
||||
wrapper,
|
||||
}
|
||||
);
|
||||
|
||||
expect(mockHttpGet).toHaveBeenCalledTimes(1);
|
||||
expect(mockHttpGet).toHaveBeenCalledWith('/internal/rac/alerts/browser_fields', {
|
||||
query: { featureIds: ['apm', 'logs'] },
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { AlertConsumers, isValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import type { QueryOptionsOverrides } from '../types/tanstack_query_utility_types';
|
||||
import { fetchAlertsFields, FetchAlertsFieldsParams } from '../apis/fetch_alerts_fields';
|
||||
|
||||
export type UseFetchAlertsFieldsQueryParams = FetchAlertsFieldsParams;
|
||||
|
||||
const UNSUPPORTED_FEATURE_ID = AlertConsumers.SIEM;
|
||||
|
||||
export const queryKeyPrefix = ['alerts', fetchAlertsFields.name];
|
||||
|
||||
/**
|
||||
* Fetch alerts indexes browser fields for the given feature ids
|
||||
*
|
||||
* When testing components that depend on this hook, prefer mocking the {@link fetchAlertsFields} function instead of the hook itself.
|
||||
* @external https://tanstack.com/query/v4/docs/framework/react/guides/testing
|
||||
*/
|
||||
export const useFetchAlertsFieldsQuery = (
|
||||
{ http, ...params }: UseFetchAlertsFieldsQueryParams,
|
||||
options?: Pick<
|
||||
QueryOptionsOverrides<typeof fetchAlertsFields>,
|
||||
'context' | 'onError' | 'refetchOnWindowFocus' | 'staleTime' | 'enabled'
|
||||
>
|
||||
) => {
|
||||
const { featureIds } = params;
|
||||
|
||||
const validFeatureIds = featureIds.filter(
|
||||
(fid) => isValidFeatureId(fid) && fid !== UNSUPPORTED_FEATURE_ID
|
||||
);
|
||||
|
||||
return useQuery({
|
||||
queryKey: queryKeyPrefix.concat(JSON.stringify(featureIds)),
|
||||
queryFn: () => fetchAlertsFields({ http, featureIds: validFeatureIds }),
|
||||
enabled: validFeatureIds.length > 0,
|
||||
initialData: { browserFields: {}, fields: [] },
|
||||
...options,
|
||||
});
|
||||
};
|
|
@ -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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
export const testQueryClientConfig = {
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
logger: {
|
||||
log: console.log,
|
||||
warn: console.warn,
|
||||
error: () => {},
|
||||
},
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { UseQueryOptions } from '@tanstack/react-query';
|
||||
|
||||
/**
|
||||
* Extracts the data type from a fetching function
|
||||
*/
|
||||
export type FetchFnData<TFetchFn> = TFetchFn extends (...args: any) => Promise<infer TData>
|
||||
? TData
|
||||
: TFetchFn extends (...args: any) => infer TData
|
||||
? TData
|
||||
: never;
|
||||
|
||||
/**
|
||||
* A utility type used to correctly type options overrides for `useQuery`
|
||||
*/
|
||||
export type QueryOptionsOverrides<
|
||||
/**
|
||||
* The fetching function type. TData is calculated based on this type.
|
||||
*/
|
||||
TFetchFn,
|
||||
TQueryFnData = FetchFnData<TFetchFn>,
|
||||
TError = unknown,
|
||||
TData = TQueryFnData
|
||||
> = Omit<
|
||||
UseQueryOptions<TQueryFnData, TError, TData, string[]>,
|
||||
'queryKey' | 'queryFn' | 'initialData'
|
||||
>;
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
export type { BrowserFields, BrowserField } from '@kbn/alerting-types';
|
||||
export { parseTechnicalFields, type ParsedTechnicalFields } from './parse_technical_fields';
|
||||
export type {
|
||||
RuleRegistrySearchRequest,
|
||||
|
@ -12,4 +13,3 @@ export type {
|
|||
Alert as EcsFieldsResponse,
|
||||
} from '@kbn/alerting-types';
|
||||
export { BASE_RAC_ALERTS_API_PATH } from './constants';
|
||||
export type { BrowserFields, BrowserField } from './types';
|
||||
|
|
|
@ -6,13 +6,8 @@
|
|||
*/
|
||||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import * as t from 'io-ts';
|
||||
|
||||
import type { IFieldSubType } from '@kbn/es-query';
|
||||
import type { RuntimeField } from '@kbn/data-views-plugin/common';
|
||||
import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { SerializedFieldFormat } from '@kbn/field-formats-plugin/common';
|
||||
|
||||
// note: these schemas are not exhaustive. See the `Sort` type of `@elastic/elasticsearch` if you need to enhance it.
|
||||
const fieldSchema = t.string;
|
||||
|
@ -417,21 +412,3 @@ export interface ClusterPutComponentTemplateBody {
|
|||
mappings: estypes.MappingTypeMapping;
|
||||
};
|
||||
}
|
||||
|
||||
export interface BrowserField {
|
||||
aggregatable: boolean;
|
||||
category: string;
|
||||
description?: string | null;
|
||||
example?: string | number | null;
|
||||
fields: Readonly<Record<string, Partial<BrowserField>>>;
|
||||
format?: SerializedFieldFormat;
|
||||
indexes: string[];
|
||||
name: string;
|
||||
searchable: boolean;
|
||||
type: string;
|
||||
subType?: IFieldSubType;
|
||||
readFromDocValues: boolean;
|
||||
runtimeField?: RuntimeField;
|
||||
}
|
||||
|
||||
export type BrowserFields = Record<string, Partial<BrowserField>>;
|
||||
|
|
|
@ -35,8 +35,7 @@
|
|||
"@kbn/core-http-router-server-mocks",
|
||||
"@kbn/core-http-server",
|
||||
"@kbn/alerting-state-types",
|
||||
"@kbn/alerting-types",
|
||||
"@kbn/field-formats-plugin"
|
||||
"@kbn/alerting-types"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -30,7 +30,7 @@ import {
|
|||
} from '../../../types';
|
||||
import { EuiButton, EuiButtonIcon, EuiDataGridColumnCellAction, EuiFlexItem } from '@elastic/eui';
|
||||
import { bulkActionsReducer } from './bulk_actions/reducer';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { BrowserFields } from '@kbn/alerting-types';
|
||||
import { getCasesMockMap } from './cases/index.mock';
|
||||
import { getMaintenanceWindowMockMap } from './maintenance_windows/index.mock';
|
||||
import { createAppMockRenderer, getJsDomPerformanceFix } from '../test_utils';
|
||||
|
|
|
@ -29,33 +29,32 @@ import {
|
|||
import { PLUGIN_ID } from '../../../common/constants';
|
||||
import AlertsTableState, { AlertsTableStateProps } from './alerts_table_state';
|
||||
import { AlertsTable } from './alerts_table';
|
||||
import { useFetchBrowserFieldCapabilities } from './hooks/use_fetch_browser_fields_capabilities';
|
||||
import { useBulkGetCases } from './hooks/use_bulk_get_cases';
|
||||
import { DefaultSort } from './hooks';
|
||||
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { BrowserFields } from '@kbn/alerting-types';
|
||||
import { getCasesMockMap } from './cases/index.mock';
|
||||
import { createCasesServiceMock } from './index.mock';
|
||||
import { useBulkGetMaintenanceWindows } from './hooks/use_bulk_get_maintenance_windows';
|
||||
import { getMaintenanceWindowMockMap } from './maintenance_windows/index.mock';
|
||||
import { AlertTableConfigRegistry } from '../../alert_table_config_registry';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { fetchAlertsFields } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerts_fields';
|
||||
import { useSearchAlertsQuery } from '@kbn/alerts-ui-shared/src/common/hooks';
|
||||
|
||||
jest.mock('@kbn/kibana-utils-plugin/public');
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/hooks/use_search_alerts_query');
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerts_fields');
|
||||
|
||||
jest.mock('./hooks/use_bulk_get_cases');
|
||||
jest.mock('./hooks/use_bulk_get_maintenance_windows');
|
||||
jest.mock('./alerts_table', () => {
|
||||
return {
|
||||
AlertsTable: jest.fn(),
|
||||
};
|
||||
});
|
||||
const MockAlertsTable = AlertsTable as jest.Mock;
|
||||
|
||||
jest.mock('./hooks/use_fetch_browser_fields_capabilities');
|
||||
jest.mock('./hooks/use_bulk_get_cases');
|
||||
jest.mock('./hooks/use_bulk_get_maintenance_windows');
|
||||
|
||||
jest.mock('@kbn/kibana-utils-plugin/public');
|
||||
|
||||
const MockAlertsTable = jest.mocked(AlertsTable);
|
||||
const mockCurrentAppId$ = new BehaviorSubject<string>('testAppId');
|
||||
const mockCaseService = createCasesServiceMock();
|
||||
|
||||
|
@ -299,9 +298,9 @@ const alertsTableConfigurationRegistryMock = {
|
|||
update: updateMock,
|
||||
} as unknown as AlertTableConfigRegistry;
|
||||
|
||||
const storageMock = Storage as jest.Mock;
|
||||
const mockStorage = Storage as jest.Mock;
|
||||
|
||||
storageMock.mockImplementation(() => {
|
||||
mockStorage.mockImplementation(() => {
|
||||
return { get: jest.fn(), set: jest.fn() };
|
||||
});
|
||||
|
||||
|
@ -320,19 +319,47 @@ const searchAlertsResponse = {
|
|||
|
||||
mockUseSearchAlertsQuery.mockReturnValue(searchAlertsResponse);
|
||||
|
||||
const hookUseFetchBrowserFieldCapabilities = useFetchBrowserFieldCapabilities as jest.Mock;
|
||||
hookUseFetchBrowserFieldCapabilities.mockImplementation(() => [false, {}]);
|
||||
|
||||
const casesMap = getCasesMockMap();
|
||||
const useBulkGetCasesMock = useBulkGetCases as jest.Mock;
|
||||
|
||||
const maintenanceWindowsMap = getMaintenanceWindowMockMap();
|
||||
const useBulkGetMaintenanceWindowsMock = useBulkGetMaintenanceWindows as jest.Mock;
|
||||
|
||||
const AlertsTableWithLocale: React.FunctionComponent<AlertsTableStateProps> = (props) => (
|
||||
<IntlProvider locale="en">
|
||||
<AlertsTableState {...props} />
|
||||
</IntlProvider>
|
||||
const browserFields: BrowserFields = {
|
||||
kibana: {
|
||||
fields: {
|
||||
[AlertsField.uuid]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.uuid,
|
||||
},
|
||||
[AlertsField.name]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.name,
|
||||
},
|
||||
[AlertsField.reason]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.reason,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jest.mocked(fetchAlertsFields).mockResolvedValue({ browserFields, fields: [] });
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const TestComponent: React.FunctionComponent<AlertsTableStateProps> = (props) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<IntlProvider locale="en">
|
||||
<AlertsTableState {...props} />
|
||||
</IntlProvider>
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
describe('AlertsTableState', () => {
|
||||
|
@ -405,12 +432,12 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
it('should show the cases column', async () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(await screen.findByText('Cases')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the cases titles correctly', async () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(await screen.findByText('Test case')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Test case 2')).toBeInTheDocument();
|
||||
});
|
||||
|
@ -418,12 +445,12 @@ describe('AlertsTableState', () => {
|
|||
it('should show the loading skeleton when fetching cases', async () => {
|
||||
useBulkGetCasesMock.mockReturnValue({ data: casesMap, isFetching: true });
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect((await screen.findAllByTestId('cases-cell-loading')).length).toBe(3);
|
||||
});
|
||||
|
||||
it('should pass the correct case ids to useBulkGetCases', async () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useBulkGetCasesMock).toHaveBeenCalledWith(['test-id', 'test-id-2'], true);
|
||||
|
@ -439,7 +466,7 @@ describe('AlertsTableState', () => {
|
|||
},
|
||||
});
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useBulkGetCasesMock).toHaveBeenCalledWith(['test-id', 'test-id-2'], true);
|
||||
|
@ -458,7 +485,7 @@ describe('AlertsTableState', () => {
|
|||
},
|
||||
});
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useBulkGetCasesMock).toHaveBeenCalledWith(['test-id-2'], true);
|
||||
|
@ -470,7 +497,7 @@ describe('AlertsTableState', () => {
|
|||
.fn()
|
||||
.mockReturnValue({ create: false, read: false });
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(useBulkGetCasesMock).toHaveBeenCalledWith(['test-id-2'], false);
|
||||
|
@ -485,7 +512,7 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
render(
|
||||
<AlertsTableWithLocale
|
||||
<TestComponent
|
||||
{...props}
|
||||
columns={[
|
||||
{
|
||||
|
@ -501,7 +528,7 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
it('calls canUseCases with an empty array if the case configuration is not defined', async () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(mockCaseService.helpers.canUseCases).toHaveBeenCalledWith([]);
|
||||
});
|
||||
|
||||
|
@ -510,7 +537,7 @@ describe('AlertsTableState', () => {
|
|||
cases: { featureId: 'test-feature-id', owner: ['test-owner'] },
|
||||
});
|
||||
|
||||
render(<AlertsTableWithLocale {...props} />);
|
||||
render(<TestComponent {...props} />);
|
||||
expect(mockCaseService.helpers.canUseCases).toHaveBeenCalledWith(['test-owner']);
|
||||
});
|
||||
|
||||
|
@ -522,7 +549,7 @@ describe('AlertsTableState', () => {
|
|||
const CasesContextMock = jest.fn().mockReturnValue(null);
|
||||
mockCaseService.ui.getCasesContext = jest.fn().mockReturnValue(CasesContextMock);
|
||||
|
||||
render(<AlertsTableWithLocale {...props} />);
|
||||
render(<TestComponent {...props} />);
|
||||
|
||||
expect(CasesContextMock).toHaveBeenCalledWith(
|
||||
{
|
||||
|
@ -539,7 +566,7 @@ describe('AlertsTableState', () => {
|
|||
const CasesContextMock = jest.fn().mockReturnValue(null);
|
||||
mockCaseService.ui.getCasesContext = jest.fn().mockReturnValue(CasesContextMock);
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(CasesContextMock).toHaveBeenCalledWith(
|
||||
{
|
||||
children: expect.anything(),
|
||||
|
@ -558,7 +585,7 @@ describe('AlertsTableState', () => {
|
|||
.fn()
|
||||
.mockReturnValue({ create: false, read: false });
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(CasesContextMock).toHaveBeenCalledWith(
|
||||
{
|
||||
children: expect.anything(),
|
||||
|
@ -578,7 +605,7 @@ describe('AlertsTableState', () => {
|
|||
const CasesContextMock = jest.fn().mockReturnValue(null);
|
||||
mockCaseService.ui.getCasesContext = jest.fn().mockReturnValue(CasesContextMock);
|
||||
|
||||
render(<AlertsTableWithLocale {...props} />);
|
||||
render(<TestComponent {...props} />);
|
||||
expect(CasesContextMock).toHaveBeenCalledWith(
|
||||
{
|
||||
children: expect.anything(),
|
||||
|
@ -597,18 +624,18 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
it('should show maintenance windows column', async () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(await screen.findByText('Maintenance Windows')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show maintenance windows titles correctly', async () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(await screen.findByText('test-title')).toBeInTheDocument();
|
||||
expect(await screen.findByText('test-title-2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should pass the correct maintenance window ids to useBulkGetMaintenanceWindows', async () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
await waitFor(() => {
|
||||
expect(useBulkGetMaintenanceWindowsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -628,7 +655,7 @@ describe('AlertsTableState', () => {
|
|||
},
|
||||
});
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
await waitFor(() => {
|
||||
expect(useBulkGetMaintenanceWindowsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -651,7 +678,7 @@ describe('AlertsTableState', () => {
|
|||
},
|
||||
});
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
await waitFor(() => {
|
||||
expect(useBulkGetMaintenanceWindowsMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
|
@ -668,7 +695,7 @@ describe('AlertsTableState', () => {
|
|||
isFetching: true,
|
||||
});
|
||||
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect((await screen.findAllByTestId('maintenance-window-cell-loading')).length).toBe(1);
|
||||
});
|
||||
|
||||
|
@ -676,7 +703,7 @@ describe('AlertsTableState', () => {
|
|||
|
||||
it('should not fetch maintenance windows if the column is not visible', async () => {
|
||||
render(
|
||||
<AlertsTableWithLocale
|
||||
<TestComponent
|
||||
{...tableProps}
|
||||
columns={[
|
||||
{
|
||||
|
@ -699,22 +726,22 @@ describe('AlertsTableState', () => {
|
|||
|
||||
describe('Alerts table configuration registry', () => {
|
||||
it('should read the configuration from the registry', async () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(hasMock).toHaveBeenCalledWith(PLUGIN_ID);
|
||||
expect(getMock).toHaveBeenCalledWith(PLUGIN_ID);
|
||||
expect(updateMock).toBeCalledTimes(2);
|
||||
expect(updateMock).toBeCalledTimes(3);
|
||||
});
|
||||
|
||||
it('should render an empty error state when the plugin id owner is not registered', async () => {
|
||||
const props = { ...tableProps, configurationId: 'none' };
|
||||
const result = render(<AlertsTableWithLocale {...props} />);
|
||||
const result = render(<TestComponent {...props} />);
|
||||
expect(result.getByTestId('alertsTableNoConfiguration')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('flyout', () => {
|
||||
it('should show a flyout when selecting an alert', async () => {
|
||||
const wrapper = render(<AlertsTableWithLocale {...tableProps} />);
|
||||
const wrapper = render(<TestComponent {...tableProps} />);
|
||||
userEvent.click(wrapper.queryAllByTestId('expandColumnCellOpenFlyoutButton-0')[0]!);
|
||||
|
||||
const result = await wrapper.findAllByTestId('alertsFlyout');
|
||||
|
@ -735,7 +762,7 @@ describe('AlertsTableState', () => {
|
|||
|
||||
it('should refetch data if flyout pagination exceeds the current page', async () => {
|
||||
const wrapper = render(
|
||||
<AlertsTableWithLocale
|
||||
<TestComponent
|
||||
{...{
|
||||
...tableProps,
|
||||
initialPageSize: 1,
|
||||
|
@ -769,7 +796,7 @@ describe('AlertsTableState', () => {
|
|||
|
||||
it('Should be able to go back from last page to n - 1', async () => {
|
||||
const wrapper = render(
|
||||
<AlertsTableWithLocale
|
||||
<TestComponent
|
||||
{...{
|
||||
...tableProps,
|
||||
initialPageSize: 2,
|
||||
|
@ -803,29 +830,8 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
describe('field browser', () => {
|
||||
const browserFields: BrowserFields = {
|
||||
kibana: {
|
||||
fields: {
|
||||
[AlertsField.uuid]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.uuid,
|
||||
},
|
||||
[AlertsField.name]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.name,
|
||||
},
|
||||
[AlertsField.reason]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.reason,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
hookUseFetchBrowserFieldCapabilities.mockClear();
|
||||
hookUseFetchBrowserFieldCapabilities.mockImplementation(() => [true, browserFields]);
|
||||
useBulkGetCasesMock.mockReturnValue({ data: new Map(), isFetching: false });
|
||||
useBulkGetMaintenanceWindowsMock.mockReturnValue({
|
||||
data: new Map(),
|
||||
|
@ -833,13 +839,13 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should show field browser', () => {
|
||||
const { queryByTestId } = render(<AlertsTableWithLocale {...tableProps} />);
|
||||
expect(queryByTestId('show-field-browser')).not.toBe(null);
|
||||
it('should show field browser', async () => {
|
||||
const { findByTestId } = render(<TestComponent {...tableProps} />);
|
||||
expect(await findByTestId('show-field-browser')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should remove an already existing element when selected', async () => {
|
||||
const { getByTestId, queryByTestId } = render(<AlertsTableWithLocale {...tableProps} />);
|
||||
const { getByTestId, queryByTestId } = render(<TestComponent {...tableProps} />);
|
||||
|
||||
expect(queryByTestId(`dataGridHeaderCell-${AlertsField.name}`)).not.toBe(null);
|
||||
fireEvent.click(getByTestId('show-field-browser'));
|
||||
|
@ -853,8 +859,8 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
it('should restore a default element that has been removed previously', async () => {
|
||||
storageMock.mockClear();
|
||||
storageMock.mockImplementation(() => ({
|
||||
mockStorage.mockClear();
|
||||
mockStorage.mockImplementation(() => ({
|
||||
get: () => {
|
||||
return {
|
||||
columns: [{ displayAsText: 'Reason', id: AlertsField.reason, schema: undefined }],
|
||||
|
@ -871,7 +877,7 @@ describe('AlertsTableState', () => {
|
|||
set: jest.fn(),
|
||||
}));
|
||||
|
||||
const { getByTestId, queryByTestId } = render(<AlertsTableWithLocale {...tableProps} />);
|
||||
const { getByTestId, queryByTestId } = render(<TestComponent {...tableProps} />);
|
||||
|
||||
expect(queryByTestId(`dataGridHeaderCell-${AlertsField.name}`)).toBe(null);
|
||||
fireEvent.click(getByTestId('show-field-browser'));
|
||||
|
@ -890,7 +896,7 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
it('should insert a new field as column when its not a default one', async () => {
|
||||
const { getByTestId, queryByTestId } = render(<AlertsTableWithLocale {...tableProps} />);
|
||||
const { getByTestId, queryByTestId } = render(<TestComponent {...tableProps} />);
|
||||
|
||||
expect(queryByTestId(`dataGridHeaderCell-${AlertsField.uuid}`)).toBe(null);
|
||||
fireEvent.click(getByTestId('show-field-browser'));
|
||||
|
@ -914,14 +920,14 @@ describe('AlertsTableState', () => {
|
|||
const props = mockCustomProps({
|
||||
usePersistentControls: () => ({ right: <span>This is a persistent control</span> }),
|
||||
});
|
||||
const result = render(<AlertsTableWithLocale {...props} />);
|
||||
const result = render(<TestComponent {...props} />);
|
||||
expect(result.getByText('This is a persistent control')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('inspect button', () => {
|
||||
it('should hide the inspect button by default', () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(screen.queryByTestId('inspect-icon-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
|
@ -929,7 +935,7 @@ describe('AlertsTableState', () => {
|
|||
const props = mockCustomProps({
|
||||
showInspectButton: true,
|
||||
});
|
||||
render(<AlertsTableWithLocale {...props} />);
|
||||
render(<TestComponent {...props} />);
|
||||
expect(await screen.findByTestId('inspect-icon-button')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -948,19 +954,19 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
it('should render an empty screen if there are no alerts', async () => {
|
||||
const result = render(<AlertsTableWithLocale {...tableProps} />);
|
||||
const result = render(<TestComponent {...tableProps} />);
|
||||
expect(result.getByTestId('alertsStateTableEmptyState')).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('inspect button', () => {
|
||||
it('should hide the inspect button by default', () => {
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
expect(screen.queryByTestId('inspect-icon-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show the inspect button if the right prop is set', async () => {
|
||||
const props = mockCustomProps({ showInspectButton: true });
|
||||
render(<AlertsTableWithLocale {...props} />);
|
||||
render(<TestComponent {...props} />);
|
||||
expect(await screen.findByTestId('inspect-icon-button')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -971,7 +977,7 @@ describe('AlertsTableState', () => {
|
|||
});
|
||||
|
||||
it('should show persistent controls if set', () => {
|
||||
const result = render(<AlertsTableWithLocale {...props} />);
|
||||
const result = render(<TestComponent {...props} />);
|
||||
expect(result.getByText('This is a persistent control')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
@ -984,7 +990,7 @@ describe('AlertsTableState', () => {
|
|||
toolbarVisibility: { showColumnSelector: false },
|
||||
};
|
||||
|
||||
render(<AlertsTableWithLocale {...customTableProps} />);
|
||||
render(<TestComponent {...customTableProps} />);
|
||||
|
||||
expect(screen.queryByTestId('dataGridColumnSelectorButton')).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -994,7 +1000,7 @@ describe('AlertsTableState', () => {
|
|||
toolbarVisibility: { showSortSelector: false },
|
||||
};
|
||||
|
||||
render(<AlertsTableWithLocale {...customTableProps} />);
|
||||
render(<TestComponent {...customTableProps} />);
|
||||
|
||||
expect(screen.queryByTestId('dataGridColumnSortingButton')).not.toBeInTheDocument();
|
||||
});
|
||||
|
@ -1006,12 +1012,12 @@ describe('AlertsTableState', () => {
|
|||
...searchAlertsResponse,
|
||||
alerts: Array.from({ length: 100 }).map((_, i) => ({ [AlertsField.uuid]: `alert-${i}` })),
|
||||
});
|
||||
const { rerender } = render(<AlertsTableWithLocale {...tableProps} />);
|
||||
const { rerender } = render(<TestComponent {...tableProps} />);
|
||||
act(() => {
|
||||
onPageChange({ pageIndex: 1, pageSize: 50 });
|
||||
});
|
||||
rerender(
|
||||
<AlertsTableWithLocale
|
||||
<TestComponent
|
||||
{...tableProps}
|
||||
query={{ bool: { filter: [{ term: { 'kibana.alert.rule.name': 'test' } }] } }}
|
||||
/>
|
||||
|
@ -1026,7 +1032,7 @@ describe('AlertsTableState', () => {
|
|||
...searchAlertsResponse,
|
||||
alerts: Array.from({ length: 100 }).map((_, i) => ({ [AlertsField.uuid]: `alert-${i}` })),
|
||||
});
|
||||
render(<AlertsTableWithLocale {...tableProps} />);
|
||||
render(<TestComponent {...tableProps} />);
|
||||
act(() => {
|
||||
onPageChange({ pageIndex: 1, pageSize: 50 });
|
||||
});
|
||||
|
|
|
@ -22,10 +22,8 @@ import {
|
|||
import type { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { ALERT_CASE_IDS, ALERT_MAINTENANCE_WINDOW_IDS } from '@kbn/rule-data-utils';
|
||||
import type { ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import type {
|
||||
BrowserFields,
|
||||
RuleRegistrySearchRequestPagination,
|
||||
} from '@kbn/rule-registry-plugin/common';
|
||||
import type { RuleRegistrySearchRequestPagination } from '@kbn/rule-registry-plugin/common';
|
||||
import type { BrowserFields } from '@kbn/alerting-types';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type {
|
||||
QueryDslQueryContainer,
|
||||
|
@ -298,7 +296,7 @@ const AlertsTableStateWithQueryProvider = memo(
|
|||
storage,
|
||||
id,
|
||||
defaultColumns: columnConfigByClient,
|
||||
initialBrowserFields: propBrowserFields,
|
||||
browserFields: propBrowserFields,
|
||||
});
|
||||
|
||||
const [queryParams, setQueryParams] = useState({
|
||||
|
|
|
@ -5,16 +5,21 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import { EuiDataGridColumn } from '@elastic/eui';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { BrowserFields } from '@kbn/alerting-types';
|
||||
import { testQueryClientConfig } from '@kbn/alerts-ui-shared/src/common/test_utils/test_query_client_config';
|
||||
import { fetchAlertsFields } from '@kbn/alerts-ui-shared/src/common/apis/fetch_alerts_fields';
|
||||
import { useColumns, UseColumnsArgs, UseColumnsResp } from './use_columns';
|
||||
import { useFetchBrowserFieldCapabilities } from '../use_fetch_browser_fields_capabilities';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { AlertsTableStorage } from '../../alerts_table_state';
|
||||
import { createStartServicesMock } from '../../../../../common/lib/kibana/kibana_react.mock';
|
||||
import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context';
|
||||
|
||||
jest.mock('@kbn/alerts-ui-shared/src/common/apis/fetch_alerts_fields');
|
||||
|
||||
const mockUseKibanaReturnValue = createStartServicesMock();
|
||||
jest.mock('../../../../../common/lib/kibana', () => ({
|
||||
|
@ -23,7 +28,6 @@ jest.mock('../../../../../common/lib/kibana', () => ({
|
|||
services: mockUseKibanaReturnValue,
|
||||
})),
|
||||
}));
|
||||
jest.mock('../use_fetch_browser_fields_capabilities');
|
||||
|
||||
const setItemStorageMock = jest.fn();
|
||||
const mockStorage = {
|
||||
|
@ -33,7 +37,44 @@ const mockStorage = {
|
|||
clear: jest.fn(),
|
||||
};
|
||||
|
||||
describe('useColumn', () => {
|
||||
const queryClient = new QueryClient(testQueryClientConfig);
|
||||
|
||||
const wrapper: FunctionComponent = ({ children }) => (
|
||||
<QueryClientProvider client={queryClient} context={AlertsQueryContext}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
|
||||
const mockFetchAlertsFields = jest.mocked(fetchAlertsFields);
|
||||
const browserFields: BrowserFields = {
|
||||
kibana: {
|
||||
fields: {
|
||||
['event.action']: {
|
||||
category: 'event',
|
||||
name: 'event.action',
|
||||
type: 'string',
|
||||
},
|
||||
['@timestamp']: {
|
||||
category: 'base',
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
},
|
||||
['kibana.alert.duration.us']: {
|
||||
category: 'kibana',
|
||||
name: 'kibana.alert.duration.us',
|
||||
type: 'number',
|
||||
},
|
||||
['kibana.alert.reason']: {
|
||||
category: 'kibana',
|
||||
name: 'kibana.alert.reason',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
mockFetchAlertsFields.mockResolvedValue({ browserFields, fields: [] });
|
||||
|
||||
describe('useColumns', () => {
|
||||
const id = 'useColumnTest';
|
||||
const featureIds: AlertConsumers[] = [AlertConsumers.LOGS, AlertConsumers.APM];
|
||||
let storage = { current: new Storage(mockStorage) };
|
||||
|
@ -73,38 +114,8 @@ describe('useColumn', () => {
|
|||
schema: 'string',
|
||||
},
|
||||
];
|
||||
const hookUseFetchBrowserFieldCapabilities = useFetchBrowserFieldCapabilities as jest.Mock;
|
||||
const browserFields: BrowserFields = {
|
||||
kibana: {
|
||||
fields: {
|
||||
['event.action']: {
|
||||
category: 'event',
|
||||
name: 'event.action',
|
||||
type: 'string',
|
||||
},
|
||||
['@timestamp']: {
|
||||
category: 'base',
|
||||
name: '@timestamp',
|
||||
type: 'date',
|
||||
},
|
||||
['kibana.alert.duration.us']: {
|
||||
category: 'kibana',
|
||||
name: 'kibana.alert.duration.us',
|
||||
type: 'number',
|
||||
},
|
||||
['kibana.alert.reason']: {
|
||||
category: 'kibana',
|
||||
name: 'kibana.alert.reason',
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
hookUseFetchBrowserFieldCapabilities.mockClear();
|
||||
hookUseFetchBrowserFieldCapabilities.mockImplementation(() => [true, browserFields]);
|
||||
|
||||
setItemStorageMock.mockClear();
|
||||
storage = { current: new Storage(mockStorage) };
|
||||
});
|
||||
|
@ -113,14 +124,16 @@ describe('useColumn', () => {
|
|||
// storageTable will always be in sync with defualtColumns.
|
||||
// it is an invariant. If that is the case, that can be considered an issue
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
act(() => {
|
||||
|
@ -142,14 +155,16 @@ describe('useColumn', () => {
|
|||
describe('visibleColumns', () => {
|
||||
test('hide all columns with onChangeVisibleColumns', async () => {
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
act(() => {
|
||||
|
@ -162,14 +177,16 @@ describe('useColumn', () => {
|
|||
|
||||
test('show all columns with onChangeVisibleColumns', async () => {
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
act(() => {
|
||||
|
@ -189,14 +206,16 @@ describe('useColumn', () => {
|
|||
|
||||
test('should populate visiblecolumns correctly', async () => {
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
expect(result.current.visibleColumns).toMatchObject(defaultColumns.map((col) => col.id));
|
||||
|
@ -205,14 +224,16 @@ describe('useColumn', () => {
|
|||
test('should change visiblecolumns if provided defaultColumns change', async () => {
|
||||
let localDefaultColumns = [...defaultColumns];
|
||||
let localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(localDefaultColumns);
|
||||
const { result, rerender } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns: localDefaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result, rerender } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns: localDefaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
expect(result.current.visibleColumns).toMatchObject(defaultColumns.map((col) => col.id));
|
||||
|
@ -240,14 +261,16 @@ describe('useColumn', () => {
|
|||
describe('columns', () => {
|
||||
test('should changes the column list when defaultColumns has been updated', async () => {
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result, waitFor } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result, waitFor } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitFor(() => expect(result.current.columns).toMatchObject(defaultColumns));
|
||||
|
@ -257,14 +280,16 @@ describe('useColumn', () => {
|
|||
describe('onToggleColumns', () => {
|
||||
test('should update the list of columns when on Toggle Columns is called', () => {
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
act(() => {
|
||||
|
@ -276,14 +301,16 @@ describe('useColumn', () => {
|
|||
|
||||
test('should update the list of visible columns when onToggleColumn is called', async () => {
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
// remove particular column
|
||||
|
@ -303,14 +330,16 @@ describe('useColumn', () => {
|
|||
|
||||
test('should update the column details in the storage when onToggleColumn is called', () => {
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
// remove particular column
|
||||
|
@ -334,14 +363,16 @@ describe('useColumn', () => {
|
|||
describe('onResetColumns', () => {
|
||||
test('should restore visible columns defaults', () => {
|
||||
const localStorageAlertsTable = getStorageAlertsTableByDefaultColumns(defaultColumns);
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
})
|
||||
const { result } = renderHook<UseColumnsArgs, UseColumnsResp>(
|
||||
() =>
|
||||
useColumns({
|
||||
defaultColumns,
|
||||
featureIds,
|
||||
id,
|
||||
storageAlertsTable: localStorageAlertsTable,
|
||||
storage,
|
||||
}),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
expect(result.current.visibleColumns).toEqual([
|
|
@ -7,13 +7,15 @@
|
|||
|
||||
import { EuiDataGridColumn, EuiDataGridOnColumnResizeData } from '@elastic/eui';
|
||||
import { IStorageWrapper } from '@kbn/kibana-utils-plugin/public';
|
||||
import { BrowserField, BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { BrowserField, BrowserFields } from '@kbn/alerting-types';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useFetchAlertsFieldsQuery } from '@kbn/alerts-ui-shared/src/common/hooks/use_fetch_alerts_fields_query';
|
||||
import { AlertsQueryContext } from '@kbn/alerts-ui-shared/src/common/contexts/alerts_query_context';
|
||||
import { AlertsTableStorage } from '../../alerts_table_state';
|
||||
import { toggleColumn } from './toggle_column';
|
||||
import { useFetchBrowserFieldCapabilities } from '../use_fetch_browser_fields_capabilities';
|
||||
import { useKibana } from '../../../../../common';
|
||||
|
||||
export interface UseColumnsArgs {
|
||||
featureIds: AlertConsumers[];
|
||||
|
@ -21,7 +23,7 @@ export interface UseColumnsArgs {
|
|||
storage: React.MutableRefObject<IStorageWrapper>;
|
||||
id: string;
|
||||
defaultColumns: EuiDataGridColumn[];
|
||||
initialBrowserFields?: BrowserFields;
|
||||
browserFields?: BrowserFields;
|
||||
}
|
||||
|
||||
export interface UseColumnsResp {
|
||||
|
@ -154,18 +156,25 @@ export const useColumns = ({
|
|||
storage,
|
||||
id,
|
||||
defaultColumns,
|
||||
initialBrowserFields,
|
||||
browserFields,
|
||||
}: UseColumnsArgs): UseColumnsResp => {
|
||||
const [isBrowserFieldDataLoading, browserFields] = useFetchBrowserFieldCapabilities({
|
||||
featureIds,
|
||||
initialBrowserFields,
|
||||
});
|
||||
const { http } = useKibana().services;
|
||||
|
||||
const { isLoading: isBrowserFieldDataLoading, data } = useFetchAlertsFieldsQuery(
|
||||
{
|
||||
http,
|
||||
featureIds,
|
||||
},
|
||||
{
|
||||
context: AlertsQueryContext,
|
||||
}
|
||||
);
|
||||
|
||||
const [columns, setColumns] = useState<EuiDataGridColumn[]>(() => {
|
||||
let cols = storageAlertsTable.current.columns;
|
||||
// before restoring from storage, enrich the column data
|
||||
if (initialBrowserFields && defaultColumns) {
|
||||
cols = populateColumns(cols, initialBrowserFields, defaultColumns);
|
||||
if (browserFields && defaultColumns) {
|
||||
cols = populateColumns(cols, browserFields, defaultColumns);
|
||||
} else if (cols && cols.length === 0) {
|
||||
cols = defaultColumns;
|
||||
}
|
||||
|
@ -211,13 +220,13 @@ export const useColumns = ({
|
|||
}, [didDefaultColumnChange, storageAlertsTable, defaultColumns, visibleColumns]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEmpty(browserFields) || isColumnsPopulated) return;
|
||||
if (isEmpty(data.browserFields) || isColumnsPopulated) return;
|
||||
|
||||
const populatedColumns = populateColumns(columns, browserFields, defaultColumns);
|
||||
const populatedColumns = populateColumns(columns, data.browserFields, defaultColumns);
|
||||
|
||||
setColumnsPopulated(true);
|
||||
setColumns(populatedColumns);
|
||||
}, [browserFields, defaultColumns, isBrowserFieldDataLoading, isColumnsPopulated, columns]);
|
||||
}, [data.browserFields, defaultColumns, isBrowserFieldDataLoading, isColumnsPopulated, columns]);
|
||||
|
||||
const setColumnsAndSave = useCallback(
|
||||
(newColumns: EuiDataGridColumn[], newVisibleColumns: string[]) => {
|
||||
|
@ -235,7 +244,7 @@ export const useColumns = ({
|
|||
|
||||
const onToggleColumn = useCallback(
|
||||
(columnId: string): void => {
|
||||
const column = euiColumnFactory(columnId, browserFields, defaultColumns);
|
||||
const column = euiColumnFactory(columnId, data.browserFields, defaultColumns);
|
||||
|
||||
const newColumns = toggleColumn({
|
||||
column,
|
||||
|
@ -251,15 +260,19 @@ export const useColumns = ({
|
|||
setVisibleColumns(newVisibleColumns);
|
||||
setColumnsAndSave(newColumns, newVisibleColumns);
|
||||
},
|
||||
[browserFields, columns, defaultColumns, setColumnsAndSave, visibleColumns]
|
||||
[data.browserFields, columns, defaultColumns, setColumnsAndSave, visibleColumns]
|
||||
);
|
||||
|
||||
const onResetColumns = useCallback(() => {
|
||||
const populatedDefaultColumns = populateColumns(defaultColumns, browserFields, defaultColumns);
|
||||
const populatedDefaultColumns = populateColumns(
|
||||
defaultColumns,
|
||||
data.browserFields,
|
||||
defaultColumns
|
||||
);
|
||||
const newVisibleColumns = populatedDefaultColumns.map((pdc) => pdc.id);
|
||||
setVisibleColumns(newVisibleColumns);
|
||||
setColumnsAndSave(populatedDefaultColumns, newVisibleColumns);
|
||||
}, [browserFields, defaultColumns, setColumnsAndSave]);
|
||||
}, [data.browserFields, defaultColumns, setColumnsAndSave]);
|
||||
|
||||
const onColumnResize = useCallback(
|
||||
({ columnId, width }: EuiDataGridOnColumnResizeData) => {
|
||||
|
@ -282,7 +295,7 @@ export const useColumns = ({
|
|||
columns,
|
||||
visibleColumns,
|
||||
isBrowserFieldDataLoading,
|
||||
browserFields,
|
||||
browserFields: browserFields ?? data.browserFields,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
onChangeVisibleColumns: setColumnsByColumnIds,
|
||||
|
@ -290,15 +303,16 @@ export const useColumns = ({
|
|||
fields: fieldsToFetch,
|
||||
}),
|
||||
[
|
||||
browserFields,
|
||||
columns,
|
||||
fieldsToFetch,
|
||||
isBrowserFieldDataLoading,
|
||||
onColumnResize,
|
||||
onResetColumns,
|
||||
onToggleColumn,
|
||||
setColumnsByColumnIds,
|
||||
visibleColumns,
|
||||
isBrowserFieldDataLoading,
|
||||
browserFields,
|
||||
data.browserFields,
|
||||
onToggleColumn,
|
||||
onResetColumns,
|
||||
setColumnsByColumnIds,
|
||||
onColumnResize,
|
||||
fieldsToFetch,
|
||||
]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useFetchBrowserFieldCapabilities } from './use_fetch_browser_fields_capabilities';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { AlertsField } from '../../../../types';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
|
||||
const browserFields: BrowserFields = {
|
||||
kibana: {
|
||||
fields: {
|
||||
[AlertsField.uuid]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.uuid,
|
||||
},
|
||||
[AlertsField.name]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.name,
|
||||
},
|
||||
[AlertsField.reason]: {
|
||||
category: 'kibana',
|
||||
name: AlertsField.reason,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('useFetchBrowserFieldCapabilities', () => {
|
||||
let httpMock: jest.Mock;
|
||||
|
||||
beforeEach(() => {
|
||||
httpMock = useKibana().services.http.get as jest.Mock;
|
||||
httpMock.mockReturnValue({
|
||||
browserFields: { fakeCategory: {} },
|
||||
fields: [
|
||||
{
|
||||
name: 'fakeCategory',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
httpMock.mockClear();
|
||||
});
|
||||
|
||||
it('should not fetch for siem', () => {
|
||||
const { result } = renderHook(() => useFetchBrowserFieldCapabilities({ featureIds: ['siem'] }));
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(0);
|
||||
expect(result.current).toEqual([undefined, {}, []]);
|
||||
});
|
||||
|
||||
it('should call the api only once', async () => {
|
||||
const { result, waitForNextUpdate, rerender } = renderHook(() =>
|
||||
useFetchBrowserFieldCapabilities({ featureIds: ['apm'] })
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(1);
|
||||
expect(result.current).toEqual([
|
||||
false,
|
||||
{ fakeCategory: {} },
|
||||
[
|
||||
{
|
||||
name: 'fakeCategory',
|
||||
},
|
||||
],
|
||||
]);
|
||||
|
||||
rerender();
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(1);
|
||||
expect(result.current).toEqual([
|
||||
false,
|
||||
{ fakeCategory: {} },
|
||||
[
|
||||
{
|
||||
name: 'fakeCategory',
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not fetch if browserFields have been provided', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFetchBrowserFieldCapabilities({ featureIds: ['apm'], initialBrowserFields: browserFields })
|
||||
);
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(0);
|
||||
expect(result.current).toEqual([undefined, browserFields, []]);
|
||||
});
|
||||
|
||||
it('should not fetch if the only featureId is not valid', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFetchBrowserFieldCapabilities({
|
||||
featureIds: ['alerts'] as unknown as AlertConsumers[],
|
||||
})
|
||||
);
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(0);
|
||||
expect(result.current).toEqual([undefined, {}, []]);
|
||||
});
|
||||
|
||||
it('should not fetch if all featureId are not valid', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useFetchBrowserFieldCapabilities({
|
||||
featureIds: ['alerts', 'tomato'] as unknown as AlertConsumers[],
|
||||
})
|
||||
);
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(0);
|
||||
expect(result.current).toEqual([undefined, {}, []]);
|
||||
});
|
||||
|
||||
it('should filter out the non valid feature id', async () => {
|
||||
const { waitForNextUpdate } = renderHook(() =>
|
||||
useFetchBrowserFieldCapabilities({
|
||||
featureIds: ['alerts', 'apm', 'logs'] as unknown as AlertConsumers[],
|
||||
})
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(httpMock).toHaveBeenCalledTimes(1);
|
||||
expect(httpMock).toHaveBeenCalledWith('/internal/rac/alerts/browser_fields', {
|
||||
query: { featureIds: ['apm', 'logs'] },
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { isValidFeatureId, ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import { BASE_RAC_ALERTS_API_PATH, BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { FieldDescriptor } from '@kbn/data-views-plugin/server';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { ERROR_FETCH_BROWSER_FIELDS } from './translations';
|
||||
|
||||
export interface FetchAlertsArgs {
|
||||
featureIds: ValidFeatureId[];
|
||||
initialBrowserFields?: BrowserFields;
|
||||
}
|
||||
|
||||
const INVALID_FEATURE_ID = 'siem';
|
||||
|
||||
export const useFetchBrowserFieldCapabilities = ({
|
||||
featureIds,
|
||||
initialBrowserFields,
|
||||
}: FetchAlertsArgs): [boolean | undefined, BrowserFields, unknown[]] => {
|
||||
const {
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean | undefined>(undefined);
|
||||
const [browserFields, setBrowserFields] = useState<BrowserFields>(
|
||||
() => initialBrowserFields ?? {}
|
||||
);
|
||||
const [fields, setFields] = useState<FieldDescriptor[]>([]);
|
||||
|
||||
const getBrowserFieldInfo = useCallback(
|
||||
async (
|
||||
validFeatureId: ValidFeatureId[]
|
||||
): Promise<{
|
||||
browserFields: BrowserFields;
|
||||
fields: FieldDescriptor[];
|
||||
}> => {
|
||||
if (!http) return Promise.resolve({ browserFields: {}, fields: [] });
|
||||
|
||||
try {
|
||||
return await http.get<{ browserFields: BrowserFields; fields: FieldDescriptor[] }>(
|
||||
`${BASE_RAC_ALERTS_API_PATH}/browser_fields`,
|
||||
{
|
||||
query: { featureIds: validFeatureId },
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
toasts.addDanger(ERROR_FETCH_BROWSER_FIELDS);
|
||||
return Promise.resolve({ browserFields: {}, fields: [] });
|
||||
}
|
||||
},
|
||||
[http, toasts]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialBrowserFields) {
|
||||
// Event if initial browser fields is empty, assign it
|
||||
// because client may be doing it to hide Fields Browser
|
||||
setBrowserFields(initialBrowserFields);
|
||||
return;
|
||||
}
|
||||
const validFeatureIdTmp = featureIds.filter((fid) => isValidFeatureId(fid));
|
||||
if (
|
||||
isLoading !== undefined ||
|
||||
featureIds.includes(INVALID_FEATURE_ID) ||
|
||||
validFeatureIdTmp.length === 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
|
||||
const callApi = async (validFeatureId: ValidFeatureId[]) => {
|
||||
const { browserFields: browserFieldsInfo, fields: newFields } = await getBrowserFieldInfo(
|
||||
validFeatureId
|
||||
);
|
||||
setFields(newFields);
|
||||
setBrowserFields(browserFieldsInfo);
|
||||
setIsLoading(false);
|
||||
};
|
||||
callApi(validFeatureIdTmp);
|
||||
}, [getBrowserFieldInfo, isLoading, featureIds, initialBrowserFields]);
|
||||
|
||||
return [isLoading, browserFields, fields];
|
||||
};
|
|
@ -10,7 +10,7 @@ import {
|
|||
EuiDataGridToolBarVisibilityOptions,
|
||||
} from '@elastic/eui';
|
||||
import React, { lazy, Suspense, memo, useMemo, useContext } from 'react';
|
||||
import { BrowserFields } from '@kbn/rule-registry-plugin/common';
|
||||
import { BrowserFields } from '@kbn/alerting-types';
|
||||
import { EsQuerySnapshot } from '@kbn/alerts-ui-shared';
|
||||
import { AlertsCount } from './components/alerts_count/alerts_count';
|
||||
import { AlertsTableContext } from '../contexts/alerts_table_context';
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue