[Actionable Observability] Move the alerts search bar to triggers action UI plugin (#144541)

* Move alerts search bar to triggers action use package

* Fix useAlertDataView test

* Fix mocks for useAlertDataView test

* Fix type issue and add getAlertsSearchBar mock
This commit is contained in:
Maryam Saeidi 2022-11-07 10:28:40 +01:00 committed by GitHub
parent 8ed9d8251e
commit 4d46bd79e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 174 additions and 92 deletions

View file

@ -122,9 +122,4 @@ export const translations = {
}
),
},
alertsSearchBar: {
placeholder: i18n.translate('xpack.observability.alerts.searchBarPlaceholder', {
defaultMessage: 'Search alerts (e.g. kibana.alert.evaluation.threshold > 75)',
}),
},
};

View file

@ -1,66 +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 React, { useState } from 'react';
import { DataView } from '@kbn/data-views-plugin/common';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import type { ValidFeatureId } from '@kbn/rule-data-utils';
import { translations } from '../../../config';
import { ObservabilityAppServices } from '../../../application/types';
import { useAlertDataView } from '../../../hooks/use_alert_data_view';
type QueryLanguageType = 'lucene' | 'kuery';
const NO_INDEX_PATTERNS: DataView[] = [];
export function AlertsSearchBar({
appName,
featureIds,
query,
onQueryChange,
rangeFrom,
rangeTo,
}: {
appName: string;
featureIds: ValidFeatureId[];
rangeFrom?: string;
rangeTo?: string;
query?: string;
onQueryChange: ({}: {
dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' };
query?: string;
}) => void;
}) {
const {
unifiedSearch: {
ui: { SearchBar },
},
} = useKibana<ObservabilityAppServices>().services;
const [queryLanguage, setQueryLanguage] = useState<QueryLanguageType>('kuery');
const { value: dataView, loading, error } = useAlertDataView(featureIds);
return (
<SearchBar
appName={appName}
indexPatterns={loading || error ? NO_INDEX_PATTERNS : [dataView!]}
placeholder={translations.alertsSearchBar.placeholder}
query={{ query: query ?? '', language: queryLanguage }}
dateRangeFrom={rangeFrom}
dateRangeTo={rangeTo}
displayStyle="inPage"
showFilterBar={false}
onQuerySubmit={({ dateRange, query: nextQuery }) => {
onQueryChange({
dateRange,
query: typeof nextQuery?.query === 'string' ? nextQuery.query : '',
});
setQueryLanguage((nextQuery?.language ?? 'kuery') as QueryLanguageType);
}}
/>
);
}

View file

@ -9,7 +9,6 @@ export * from './alerts_flyout';
export * from './render_cell_value';
export * from './severity_badge';
export * from './workflow_status_filter';
export * from './alerts_search_bar';
export * from './filter_for_value';
export * from './parse_alert';
export * from './alerts_status_filter';

View file

@ -29,7 +29,7 @@ import {
useAlertsPageStateContainer,
} from '../state_container';
import './styles.scss';
import { AlertsStatusFilter, AlertsSearchBar, ALERT_STATUS_QUERY } from '../../components';
import { AlertsStatusFilter, ALERT_STATUS_QUERY } from '../../components';
import { renderRuleStats } from '../../components/rule_stats';
import { ObservabilityAppServices } from '../../../../application/types';
import { ALERTS_PER_PAGE, ALERTS_TABLE_ID } from './constants';
@ -46,7 +46,11 @@ function AlertsPage() {
docLinks,
http,
notifications: { toasts },
triggersActionsUi: { alertsTableConfigurationRegistry, getAlertsStateTable: AlertsStateTable },
triggersActionsUi: {
alertsTableConfigurationRegistry,
getAlertsStateTable: AlertsStateTable,
getAlertsSearchBar: AlertsSearchBar,
},
data: {
query: {
timefilter: { timefilter: timeFilterService },

View file

@ -38,7 +38,6 @@ import {
} from '@kbn/triggers-actions-ui-plugin/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { GuidedOnboardingPluginStart } from '@kbn/guided-onboarding-plugin/public';
import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import { observabilityAppId, observabilityFeatureId, casesPath } from '../common';
import { createLazyObservabilityPageTemplate } from './components/shared';
import { registerDataHandler } from './data_handler';
@ -95,7 +94,6 @@ export interface ObservabilityPublicPluginsStart {
actionTypeRegistry: ActionTypeRegistryContract;
security: SecurityPluginStart;
guidedOnboarding: GuidedOnboardingPluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
}
export type ObservabilityPublicStart = ReturnType<Plugin['start']>;

View file

@ -23290,7 +23290,6 @@
"xpack.observability.alerts.ruleStats.loadError": "Impossible de charger les statistiques de règles",
"xpack.observability.alerts.ruleStats.muted": "Répété",
"xpack.observability.alerts.ruleStats.ruleCount": "Nombre de règles",
"xpack.observability.alerts.searchBarPlaceholder": "Alertes de recherche (par exemple, kibana.alert.evaluation.threshold > 75)",
"xpack.observability.alerts.workflowStatusFilter.acknowledgedButtonLabel": "Reconnue(s)",
"xpack.observability.alerts.workflowStatusFilter.closedButtonLabel": "Fermé",
"xpack.observability.alerts.workflowStatusFilter.openButtonLabel": "Ouvrir",

View file

@ -23269,7 +23269,6 @@
"xpack.observability.alerts.ruleStats.loadError": "ルール統計情報を読み込めません",
"xpack.observability.alerts.ruleStats.muted": "スヌーズ済み",
"xpack.observability.alerts.ruleStats.ruleCount": "ルール数",
"xpack.observability.alerts.searchBarPlaceholder": "検索アラートkibana.alert.evaluation.threshold > 75",
"xpack.observability.alerts.workflowStatusFilter.acknowledgedButtonLabel": "認識",
"xpack.observability.alerts.workflowStatusFilter.closedButtonLabel": "終了",
"xpack.observability.alerts.workflowStatusFilter.openButtonLabel": "開く",

View file

@ -23300,7 +23300,6 @@
"xpack.observability.alerts.ruleStats.loadError": "无法加载规则统计信息",
"xpack.observability.alerts.ruleStats.muted": "已暂停",
"xpack.observability.alerts.ruleStats.ruleCount": "规则计数",
"xpack.observability.alerts.searchBarPlaceholder": "搜索告警(例如 kibana.alert.evaluation.threshold > 75",
"xpack.observability.alerts.workflowStatusFilter.acknowledgedButtonLabel": "已确认",
"xpack.observability.alerts.workflowStatusFilter.closedButtonLabel": "已关闭",
"xpack.observability.alerts.workflowStatusFilter.openButtonLabel": "打开",

View file

@ -6,35 +6,41 @@
*/
import { DataView } from '@kbn/data-views-plugin/common';
import { AlertConsumers } from '@kbn/rule-data-utils';
import { createStartServicesMock } from '../../common/lib/kibana/kibana_react.mock';
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();
const mockUseKibanaReturnValue = createStartServicesMock();
jest.mock('@kbn/kibana-react-plugin/public', () => ({
__esModule: true,
useKibana: jest.fn(() => mockUseKibanaReturnValue),
useKibana: jest.fn(() => ({
services: mockUseKibanaReturnValue,
})),
}));
describe('useAlertDataView', () => {
const mockedDataView = 'dataView';
const observabilityAlertFeatureIds: ValidFeatureId[] = [
AlertConsumers.APM,
AlertConsumers.INFRASTRUCTURE,
AlertConsumers.LOGS,
AlertConsumers.UPTIME,
];
beforeEach(() => {
mockUseKibanaReturnValue.services.http.get.mockImplementation(async () => ({
mockUseKibanaReturnValue.http.get = jest.fn().mockReturnValue({
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
);
});
mockUseKibanaReturnValue.data.dataViews.create = jest.fn().mockReturnValue(mockedDataView);
});
afterEach(() => {
@ -77,7 +83,7 @@ describe('useAlertDataView', () => {
it('returns error with no data when error happens', async () => {
const error = new Error('http error');
mockUseKibanaReturnValue.services.http.get.mockImplementation(async () => {
mockUseKibanaReturnValue.http.get = jest.fn().mockImplementation(async () => {
throw error;
});

View file

@ -11,11 +11,10 @@ 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';
import { TriggersAndActionsUiServices } from '../..';
export function useAlertDataView(featureIds: ValidFeatureId[]): AsyncState<DataView> {
const { http, data: dataService } = useKibana<ObservabilityAppServices>().services;
const { http, data: dataService } = useKibana<TriggersAndActionsUiServices>().services;
const features = featureIds.sort().join(',');
const dataView = useAsync(async () => {

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useCallback, useState } from 'react';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { NO_INDEX_PATTERNS } from './constants';
import { SEARCH_BAR_PLACEHOLDER } from './translations';
import { AlertsSearchBarProps, QueryLanguageType } from './types';
import { useAlertDataView } from '../../hooks/use_alert_data_view';
import { TriggersAndActionsUiServices } from '../../..';
// TODO Share buildEsQuery to be used between AlertsSearchBar and AlertsStateTable component https://github.com/elastic/kibana/issues/144615
export function AlertsSearchBar({
appName,
featureIds,
query,
onQueryChange,
rangeFrom,
rangeTo,
}: AlertsSearchBarProps) {
const {
unifiedSearch: {
ui: { SearchBar },
},
} = useKibana<TriggersAndActionsUiServices>().services;
const [queryLanguage, setQueryLanguage] = useState<QueryLanguageType>('kuery');
const { value: dataView, loading, error } = useAlertDataView(featureIds);
const onQuerySubmit = useCallback(
(payload) => {
const { dateRange, query: nextQuery } = payload;
onQueryChange({
dateRange,
query: typeof nextQuery?.query === 'string' ? nextQuery.query : '',
});
setQueryLanguage((nextQuery?.language ?? 'kuery') as QueryLanguageType);
},
[onQueryChange, setQueryLanguage]
);
return (
<SearchBar
appName={appName}
indexPatterns={loading || error ? NO_INDEX_PATTERNS : [dataView!]}
placeholder={SEARCH_BAR_PLACEHOLDER}
query={{ query: query ?? '', language: queryLanguage }}
dateRangeFrom={rangeFrom}
dateRangeTo={rangeTo}
displayStyle="inPage"
showFilterBar={false}
onQuerySubmit={onQuerySubmit}
/>
);
}
// eslint-disable-next-line import/no-default-export
export { AlertsSearchBar as default };

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { DataView } from '@kbn/data-views-plugin/common';
export const NO_INDEX_PATTERNS: DataView[] = [];

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export { AlertsSearchBar } from './alerts_search_bar';
export type { AlertsSearchBarProps } from './types';

View file

@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { i18n } from '@kbn/i18n';
export const SEARCH_BAR_PLACEHOLDER = i18n.translate(
'xpack.triggersActionsUI.alertsSearchBar.placeholder',
{
defaultMessage: 'Search alerts (e.g. kibana.alert.evaluation.threshold > 75)',
}
);

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { ValidFeatureId } from '@kbn/rule-data-utils';
export type QueryLanguageType = 'lucene' | 'kuery';
export interface AlertsSearchBarProps {
appName: string;
featureIds: ValidFeatureId[];
rangeFrom?: string;
rangeTo?: string;
query?: string;
onQueryChange: ({}: {
dateRange: { from: string; to: string; mode?: 'absolute' | 'relative' };
query?: string;
}) => void;
}

View file

@ -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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiLoadingSpinner } from '@elastic/eui';
import React, { lazy, Suspense } from 'react';
import type { AlertsSearchBarProps } from '../application/sections/alerts_search_bar';
const AlertsSearchBarLazy: React.FC<AlertsSearchBarProps> = lazy(
() => import('../application/sections/alerts_search_bar/alerts_search_bar')
);
export const getAlertsSearchBarLazy = (props: AlertsSearchBarProps) => (
<Suspense fallback={<EuiLoadingSpinner />}>
<AlertsSearchBarLazy {...props} />
</Suspense>
);

View file

@ -33,8 +33,10 @@ import { getRuleTagBadgeLazy } from './common/get_rule_tag_badge';
import { getRuleEventLogListLazy } from './common/get_rule_event_log_list';
import { getRulesListLazy } from './common/get_rules_list';
import { getAlertsTableStateLazy } from './common/get_alerts_table_state';
import { getAlertsSearchBarLazy } from './common/get_alerts_search_bar';
import { getRulesListNotifyBadgeLazy } from './common/get_rules_list_notify_badge';
import { AlertsTableStateProps } from './application/sections/alerts_table/alerts_table_state';
import { AlertsSearchBarProps } from './application/sections/alerts_search_bar';
import { CreateConnectorFlyoutProps } from './application/sections/action_connector_form/create_connector_flyout';
import { EditConnectorFlyoutProps } from './application/sections/action_connector_form/edit_connector_flyout';
import { getActionFormLazy } from './common/get_action_form';
@ -86,6 +88,9 @@ function createStartMock(): TriggersAndActionsUIPublicPluginStart {
getAlertsStateTable: (props: AlertsTableStateProps) => {
return getAlertsTableStateLazy(props);
},
getAlertsSearchBar: (props: AlertsSearchBarProps) => {
return getAlertsSearchBarLazy(props);
},
getAlertsTable: (props: AlertsTableProps) => {
return getAlertsTableLazy(props);
},

View file

@ -23,6 +23,7 @@ import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
import type { AlertsSearchBarProps } from './application/sections/alerts_search_bar';
import { TypeRegistry } from './application/type_registry';
import { getAddConnectorFlyoutLazy } from './common/get_add_connector_flyout';
@ -72,6 +73,7 @@ import { registerAlertsTableConfiguration } from './application/sections/alerts_
import { PLUGIN_ID, CONNECTORS_PLUGIN_ID } from './common/constants';
import type { AlertsTableStateProps } from './application/sections/alerts_table/alerts_table_state';
import { getAlertsTableStateLazy } from './common/get_alerts_table_state';
import { getAlertsSearchBarLazy } from './common/get_alerts_search_bar';
import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form';
import type { FieldBrowserProps } from './application/sections/field_browser/types';
import { getRuleDefinitionLazy } from './common/get_rule_definition';
@ -108,6 +110,7 @@ export interface TriggersAndActionsUIPublicPluginStart {
) => ReactElement<RuleEditProps>;
getAlertsTable: (props: AlertsTableProps) => ReactElement<AlertsTableProps>;
getAlertsStateTable: (props: AlertsTableStateProps) => ReactElement<AlertsTableStateProps>;
getAlertsSearchBar: (props: AlertsSearchBarProps) => ReactElement<AlertsSearchBarProps>;
getFieldBrowser: (props: FieldBrowserProps) => ReactElement<FieldBrowserProps>;
getRuleStatusDropdown: (props: RuleStatusDropdownProps) => ReactElement<RuleStatusDropdownProps>;
getRuleTagFilter: (props: RuleTagFilterProps) => ReactElement<RuleTagFilterProps>;
@ -378,6 +381,9 @@ export class Plugin
getAlertsStateTable: (props: AlertsTableStateProps) => {
return getAlertsTableStateLazy(props);
},
getAlertsSearchBar: (props: AlertsSearchBarProps) => {
return getAlertsSearchBarLazy(props);
},
getAlertsTable: (props: AlertsTableProps) => {
return getAlertsTableLazy(props);
},