Skip to content

Commit

Permalink
[Actionable Observability] Refactor data view creation in alerts sear…
Browse files Browse the repository at this point in the history
…ch bar (elastic#142474)

* Refactor creating dataView for alert table

* Add test for use_alert_data_view

* Use [] as default value for indexPatterns
  • Loading branch information
maryam-saeidi authored and WafaaNasr committed Oct 14, 2022
1 parent 3284ba1 commit 73a6965
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 78 deletions.
16 changes: 16 additions & 0 deletions x-pack/plugins/observability/public/config/alert_feature_ids.ts
Original file line number Diff line number Diff line change
@@ -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,
];
1 change: 1 addition & 0 deletions x-pack/plugins/observability/public/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

export { paths } from './paths';
export { translations } from './translations';
export { observabilityAlertFeatureIds } from './alert_feature_ids';

export enum AlertingPages {
alerts = 'alerts',
Expand Down
100 changes: 100 additions & 0 deletions x-pack/plugins/observability/public/hooks/use_alert_data_view.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
33 changes: 33 additions & 0 deletions x-pack/plugins/observability/public/hooks/use_alert_data_view.ts
Original file line number Diff line number Diff line change
@@ -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;
}
32 changes: 0 additions & 32 deletions x-pack/plugins/observability/public/hooks/use_alert_index_names.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -38,7 +36,6 @@ import {
ALERTS_PER_PAGE,
ALERTS_TABLE_ID,
BASE_ALERT_REGEX,
NO_INDEX_PATTERNS,
} from './constants';
import { RuleStatsState } from './types';

Expand All @@ -52,7 +49,6 @@ function AlertsPage() {
useAlertsPageStateContainer();
const {
cases,
dataViews,
docLinks,
http,
notifications: { toasts },
Expand Down Expand Up @@ -84,7 +80,6 @@ function AlertsPage() {
}),
},
]);
const indexNames = useAlertIndexNames();

async function loadRuleStats() {
setRuleStatsLoading(true);
Expand Down Expand Up @@ -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());
};
Expand Down Expand Up @@ -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}
Expand All @@ -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,
Expand Down

0 comments on commit 73a6965

Please sign in to comment.