mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Actionable Observability] Refactor data view creation in alerts search bar (#142474)
* Refactor creating dataView for alert table * Add test for use_alert_data_view * Use [] as default value for indexPatterns
This commit is contained in:
parent
7203e303da
commit
8f50c3b34b
8 changed files with 170 additions and 78 deletions
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import type { ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
|
||||
export const observabilityAlertFeatureIds: ValidFeatureId[] = [
|
||||
AlertConsumers.APM,
|
||||
AlertConsumers.INFRASTRUCTURE,
|
||||
AlertConsumers.LOGS,
|
||||
AlertConsumers.UPTIME,
|
||||
];
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
export { paths } from './paths';
|
||||
export { translations } from './translations';
|
||||
export { observabilityAlertFeatureIds } from './alert_feature_ids';
|
||||
|
||||
export enum AlertingPages {
|
||||
alerts = 'alerts',
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 { DataView } from '@kbn/data-views-plugin/common';
|
||||
import type { ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import { act, renderHook } from '@testing-library/react-hooks';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
import { kibanaStartMock } from '../utils/kibana_react.mock';
|
||||
import { observabilityAlertFeatureIds } from '../config';
|
||||
import { useAlertDataView } from './use_alert_data_view';
|
||||
|
||||
const mockUseKibanaReturnValue = kibanaStartMock.startContract();
|
||||
|
||||
jest.mock('@kbn/kibana-react-plugin/public', () => ({
|
||||
__esModule: true,
|
||||
useKibana: jest.fn(() => mockUseKibanaReturnValue),
|
||||
}));
|
||||
|
||||
describe('useAlertDataView', () => {
|
||||
const mockedDataView = 'dataView';
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseKibanaReturnValue.services.http.get.mockImplementation(async () => ({
|
||||
index_name: [
|
||||
'.alerts-observability.uptime.alerts-*',
|
||||
'.alerts-observability.metrics.alerts-*',
|
||||
'.alerts-observability.logs.alerts-*',
|
||||
'.alerts-observability.apm.alerts-*',
|
||||
],
|
||||
}));
|
||||
mockUseKibanaReturnValue.services.data.dataViews.create.mockImplementation(
|
||||
async () => mockedDataView
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('initially is loading and does not have data', async () => {
|
||||
await act(async () => {
|
||||
const mockedAsyncDataView = {
|
||||
loading: true,
|
||||
};
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook<ValidFeatureId[], AsyncState<DataView>>(() =>
|
||||
useAlertDataView(observabilityAlertFeatureIds)
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current).toEqual(mockedAsyncDataView);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns dataView for the provided featureIds', async () => {
|
||||
await act(async () => {
|
||||
const mockedAsyncDataView = {
|
||||
loading: false,
|
||||
value: mockedDataView,
|
||||
};
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook<ValidFeatureId[], AsyncState<DataView>>(() =>
|
||||
useAlertDataView(observabilityAlertFeatureIds)
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current).toEqual(mockedAsyncDataView);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error with no data when error happens', async () => {
|
||||
const error = new Error('http error');
|
||||
mockUseKibanaReturnValue.services.http.get.mockImplementation(async () => {
|
||||
throw error;
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
const mockedAsyncDataView = {
|
||||
loading: false,
|
||||
error,
|
||||
};
|
||||
|
||||
const { result, waitForNextUpdate } = renderHook<ValidFeatureId[], AsyncState<DataView>>(() =>
|
||||
useAlertDataView(observabilityAlertFeatureIds)
|
||||
);
|
||||
|
||||
await waitForNextUpdate();
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(result.current).toEqual(mockedAsyncDataView);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DataView } from '@kbn/data-views-plugin/common';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { BASE_RAC_ALERTS_API_PATH } from '@kbn/rule-registry-plugin/common';
|
||||
import type { ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import type { AsyncState } from 'react-use/lib/useAsync';
|
||||
|
||||
import { ObservabilityAppServices } from '../application/types';
|
||||
|
||||
export function useAlertDataView(featureIds: ValidFeatureId[]): AsyncState<DataView> {
|
||||
const { http, data: dataService } = useKibana<ObservabilityAppServices>().services;
|
||||
const features = featureIds.sort().join(',');
|
||||
|
||||
const dataView = useAsync(async () => {
|
||||
const { index_name: indexNames } = await http.get<{ index_name: string[] }>(
|
||||
`${BASE_RAC_ALERTS_API_PATH}/index`,
|
||||
{
|
||||
query: { features },
|
||||
}
|
||||
);
|
||||
|
||||
return dataService.dataViews.create({ title: indexNames.join(',') });
|
||||
}, [features]);
|
||||
|
||||
return dataView;
|
||||
}
|
|
@ -1,32 +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 { useFetcher } from './use_fetcher';
|
||||
import { callObservabilityApi } from '../services/call_observability_api';
|
||||
|
||||
const NO_INDEX_NAMES: string[] = [];
|
||||
|
||||
export function useAlertIndexNames() {
|
||||
const { data: indexNames = NO_INDEX_NAMES } = useFetcher(({ signal }) => {
|
||||
return callObservabilityApi('GET /api/observability/rules/alerts/dynamic_index_pattern', {
|
||||
signal,
|
||||
params: {
|
||||
query: {
|
||||
namespace: 'default',
|
||||
registrationContexts: [
|
||||
'observability.apm',
|
||||
'observability.logs',
|
||||
'observability.metrics',
|
||||
'observability.uptime',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return indexNames;
|
||||
}
|
|
@ -48,13 +48,23 @@ const triggersActionsUiStartMock = {
|
|||
},
|
||||
};
|
||||
|
||||
const data = {
|
||||
createStart() {
|
||||
return {
|
||||
dataViews: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export const observabilityPublicPluginsStartMock = {
|
||||
createStart() {
|
||||
return {
|
||||
cases: mockCasesContract(),
|
||||
embeddable: embeddableStartMock.createStart(),
|
||||
triggersActionsUi: triggersActionsUiStartMock.createStart(),
|
||||
data: null,
|
||||
data: data.createStart(),
|
||||
lens: null,
|
||||
discover: null,
|
||||
};
|
||||
|
|
|
@ -5,24 +5,24 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { TimeHistory } from '@kbn/data-plugin/public';
|
||||
import { DataView } from '@kbn/data-views-plugin/public';
|
||||
import { SearchBar } from '@kbn/unified-search-plugin/public';
|
||||
import { Storage } from '@kbn/kibana-utils-plugin/public';
|
||||
import type { ValidFeatureId } from '@kbn/rule-data-utils';
|
||||
import { translations } from '../../../config';
|
||||
import { useAlertDataView } from '../../../hooks/use_alert_data_view';
|
||||
|
||||
type QueryLanguageType = 'lucene' | 'kuery';
|
||||
|
||||
export function AlertsSearchBar({
|
||||
dynamicIndexPatterns,
|
||||
featureIds,
|
||||
onQueryChange,
|
||||
query,
|
||||
rangeFrom,
|
||||
rangeTo,
|
||||
}: {
|
||||
dynamicIndexPatterns: DataViewBase[];
|
||||
featureIds: ValidFeatureId[];
|
||||
rangeFrom?: string;
|
||||
rangeTo?: string;
|
||||
query?: string;
|
||||
|
@ -35,20 +35,11 @@ export function AlertsSearchBar({
|
|||
return new TimeHistory(new Storage(localStorage));
|
||||
}, []);
|
||||
const [queryLanguage, setQueryLanguage] = useState<QueryLanguageType>('kuery');
|
||||
|
||||
const compatibleIndexPatterns = useMemo(
|
||||
() =>
|
||||
dynamicIndexPatterns.map((dynamicIndexPattern) => ({
|
||||
title: dynamicIndexPattern.title ?? '',
|
||||
id: dynamicIndexPattern.id ?? '',
|
||||
fields: dynamicIndexPattern.fields,
|
||||
})),
|
||||
[dynamicIndexPatterns]
|
||||
);
|
||||
const { value: dataView, loading, error } = useAlertDataView(featureIds);
|
||||
|
||||
return (
|
||||
<SearchBar
|
||||
indexPatterns={compatibleIndexPatterns as DataView[]}
|
||||
indexPatterns={loading || error ? [] : [dataView!]}
|
||||
placeholder={translations.alertsSearchBar.placeholder}
|
||||
query={{ query: query ?? '', language: queryLanguage }}
|
||||
timeHistory={timeHistory}
|
||||
|
|
|
@ -8,17 +8,15 @@
|
|||
import { EuiFlexGroup, EuiFlexItem, EuiFlyoutSize } from '@elastic/eui';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { DataViewBase } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { loadRuleAggregations } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { AlertConsumers, AlertStatus } from '@kbn/rule-data-utils';
|
||||
import { observabilityAlertFeatureIds } from '../../../../config';
|
||||
import { AlertStatusFilterButton } from '../../../../../common/typings';
|
||||
import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions';
|
||||
import { observabilityFeatureId } from '../../../../../common';
|
||||
import { useBreadcrumbs } from '../../../../hooks/use_breadcrumbs';
|
||||
import { useAlertIndexNames } from '../../../../hooks/use_alert_index_names';
|
||||
import { useHasData } from '../../../../hooks/use_has_data';
|
||||
import { usePluginContext } from '../../../../hooks/use_plugin_context';
|
||||
import { getNoDataConfig } from '../../../../utils/no_data_config';
|
||||
|
@ -38,7 +36,6 @@ import {
|
|||
ALERTS_PER_PAGE,
|
||||
ALERTS_TABLE_ID,
|
||||
BASE_ALERT_REGEX,
|
||||
NO_INDEX_PATTERNS,
|
||||
} from './constants';
|
||||
import { RuleStatsState } from './types';
|
||||
|
||||
|
@ -52,7 +49,6 @@ function AlertsPage() {
|
|||
useAlertsPageStateContainer();
|
||||
const {
|
||||
cases,
|
||||
dataViews,
|
||||
docLinks,
|
||||
http,
|
||||
notifications: { toasts },
|
||||
|
@ -84,7 +80,6 @@ function AlertsPage() {
|
|||
}),
|
||||
},
|
||||
]);
|
||||
const indexNames = useAlertIndexNames();
|
||||
|
||||
async function loadRuleStats() {
|
||||
setRuleStatsLoading(true);
|
||||
|
@ -128,23 +123,6 @@ function AlertsPage() {
|
|||
|
||||
const manageRulesHref = http.basePath.prepend('/app/observability/alerts/rules');
|
||||
|
||||
const dynamicIndexPatternsAsyncState = useAsync(async (): Promise<DataViewBase[]> => {
|
||||
if (indexNames.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
id: 'dynamic-observability-alerts-table-index-pattern',
|
||||
title: indexNames.join(','),
|
||||
fields: await dataViews.getFieldsForWildcard({
|
||||
pattern: indexNames.join(','),
|
||||
allowNoIndex: true,
|
||||
}),
|
||||
},
|
||||
];
|
||||
}, [indexNames]);
|
||||
|
||||
const onRefresh = () => {
|
||||
setRefreshNow(new Date().getTime());
|
||||
};
|
||||
|
@ -227,7 +205,7 @@ function AlertsPage() {
|
|||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<AlertsSearchBar
|
||||
dynamicIndexPatterns={dynamicIndexPatternsAsyncState.value ?? NO_INDEX_PATTERNS}
|
||||
featureIds={observabilityAlertFeatureIds}
|
||||
rangeFrom={rangeFrom}
|
||||
rangeTo={rangeTo}
|
||||
query={kuery}
|
||||
|
@ -254,12 +232,7 @@ function AlertsPage() {
|
|||
configurationId={AlertConsumers.OBSERVABILITY}
|
||||
id={ALERTS_TABLE_ID}
|
||||
flyoutSize={'s' as EuiFlyoutSize}
|
||||
featureIds={[
|
||||
AlertConsumers.APM,
|
||||
AlertConsumers.INFRASTRUCTURE,
|
||||
AlertConsumers.LOGS,
|
||||
AlertConsumers.UPTIME,
|
||||
]}
|
||||
featureIds={observabilityAlertFeatureIds}
|
||||
query={buildEsQuery(
|
||||
{
|
||||
to: rangeTo,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue