mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Actionable Observability] Integrate alert search bar on rule details page (#144718)
Resolves #143962 ## 📝 Summary In this PR, an alerts search bar was added to the rule details page by syncing its state to the URL. This will enable navigating to the alerts table for a specific rule with a filtered state based on active or recovered. ### Notes - Renamed alert page container to alert search bar container and used it both in alerts and rule details page (it will be responsible to sync search bar params to the URL) --> moved to a shared component - Moved AlertsStatusFilter to be a sub-component of the shared observability search bar - Allowed ObservabilityAlertSearchBar to be used both as a stand-alone component and as a wired component with syncing params to the URL (ObservabilityAlertSearchBar, ObservabilityAlertSearchbarWithUrlSync) - Set a minHeight for the Alerts and Execution tab, otherwise, the page will have extra scroll on the tab change while content is loading (very annoying!) ## 🎨 Preview  ## 🧪 How to test - Create a rule and go to the rule details page - Click on the alerts tab and change the search criteria, you should be able to see the criteria in the query parameter - Refresh the page, alerts tab should be selected and you should be able to see the filters that you applied in the previous step - As a side test, check alert search bar on alerts page as well, it should work as before Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
b1179e72ff
commit
ef7c1a689b
25 changed files with 549 additions and 292 deletions
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { Query } from '@kbn/es-query';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { observabilityAlertFeatureIds } from '../../../config';
|
||||
import { ObservabilityAppServices } from '../../../application/types';
|
||||
import { AlertsStatusFilter } from './components';
|
||||
import { ALERT_STATUS_QUERY, DEFAULT_QUERIES } from './constants';
|
||||
import { AlertSearchBarProps } from './types';
|
||||
import { buildEsQuery } from '../../../utils/build_es_query';
|
||||
import { AlertStatus } from '../../../../common/typings';
|
||||
|
||||
const getAlertStatusQuery = (status: string): Query[] => {
|
||||
return status ? [{ query: ALERT_STATUS_QUERY[status], language: 'kuery' }] : [];
|
||||
};
|
||||
|
||||
export function AlertSearchBar({
|
||||
appName,
|
||||
rangeFrom,
|
||||
setRangeFrom,
|
||||
rangeTo,
|
||||
setRangeTo,
|
||||
kuery,
|
||||
setKuery,
|
||||
status,
|
||||
setStatus,
|
||||
setEsQuery,
|
||||
queries = DEFAULT_QUERIES,
|
||||
}: AlertSearchBarProps) {
|
||||
const {
|
||||
data: {
|
||||
query: {
|
||||
timefilter: { timefilter: timeFilterService },
|
||||
},
|
||||
},
|
||||
triggersActionsUi: { getAlertsSearchBar: AlertsSearchBar },
|
||||
} = useKibana<ObservabilityAppServices>().services;
|
||||
|
||||
const onStatusChange = useCallback(
|
||||
(alertStatus: AlertStatus) => {
|
||||
setEsQuery(
|
||||
buildEsQuery(
|
||||
{
|
||||
to: rangeTo,
|
||||
from: rangeFrom,
|
||||
},
|
||||
kuery,
|
||||
[...getAlertStatusQuery(alertStatus), ...queries]
|
||||
)
|
||||
);
|
||||
},
|
||||
[kuery, queries, rangeFrom, rangeTo, setEsQuery]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onStatusChange(status);
|
||||
}, [onStatusChange, status]);
|
||||
|
||||
const onSearchBarParamsChange = useCallback(
|
||||
({ dateRange, query }) => {
|
||||
timeFilterService.setTime(dateRange);
|
||||
setRangeFrom(dateRange.from);
|
||||
setRangeTo(dateRange.to);
|
||||
setKuery(query);
|
||||
setEsQuery(
|
||||
buildEsQuery(
|
||||
{
|
||||
to: rangeTo,
|
||||
from: rangeFrom,
|
||||
},
|
||||
query,
|
||||
[...getAlertStatusQuery(status), ...queries]
|
||||
)
|
||||
);
|
||||
},
|
||||
[
|
||||
timeFilterService,
|
||||
setRangeFrom,
|
||||
setRangeTo,
|
||||
setKuery,
|
||||
setEsQuery,
|
||||
rangeTo,
|
||||
rangeFrom,
|
||||
status,
|
||||
queries,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<AlertsSearchBar
|
||||
appName={appName}
|
||||
featureIds={observabilityAlertFeatureIds}
|
||||
rangeFrom={rangeFrom}
|
||||
rangeTo={rangeTo}
|
||||
query={kuery}
|
||||
onQueryChange={onSearchBarParamsChange}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertsStatusFilter
|
||||
status={status}
|
||||
onChange={(id) => {
|
||||
setStatus(id as AlertStatus);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
}
|
|
@ -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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import {
|
||||
alertSearchBarStateContainer,
|
||||
Provider,
|
||||
useAlertSearchBarStateContainer,
|
||||
} from './containers';
|
||||
import { AlertSearchBar } from './alert_search_bar';
|
||||
import { AlertSearchBarWithUrlSyncProps } from './types';
|
||||
|
||||
function InternalAlertSearchbarWithUrlSync(props: AlertSearchBarWithUrlSyncProps) {
|
||||
const stateProps = useAlertSearchBarStateContainer();
|
||||
|
||||
return <AlertSearchBar {...props} {...stateProps} />;
|
||||
}
|
||||
|
||||
export function AlertSearchbarWithUrlSync(props: AlertSearchBarWithUrlSyncProps) {
|
||||
return (
|
||||
<Provider value={alertSearchBarStateContainer}>
|
||||
<InternalAlertSearchbarWithUrlSync {...props} />
|
||||
</Provider>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { ALL_ALERTS, ACTIVE_ALERTS, RECOVERED_ALERTS } from '../constants';
|
||||
import { AlertStatusFilterProps } from '../types';
|
||||
|
||||
const options: EuiButtonGroupOptionProps[] = [
|
||||
{
|
||||
id: ALL_ALERTS.status,
|
||||
label: ALL_ALERTS.label,
|
||||
value: ALL_ALERTS.query,
|
||||
'data-test-subj': 'alert-status-filter-show-all-button',
|
||||
},
|
||||
{
|
||||
id: ACTIVE_ALERTS.status,
|
||||
label: ACTIVE_ALERTS.label,
|
||||
value: ACTIVE_ALERTS.query,
|
||||
'data-test-subj': 'alert-status-filter-active-button',
|
||||
},
|
||||
{
|
||||
id: RECOVERED_ALERTS.status,
|
||||
label: RECOVERED_ALERTS.label,
|
||||
value: RECOVERED_ALERTS.query,
|
||||
'data-test-subj': 'alert-status-filter-recovered-button',
|
||||
},
|
||||
];
|
||||
|
||||
export function AlertsStatusFilter({ status, onChange }: AlertStatusFilterProps) {
|
||||
return (
|
||||
<EuiButtonGroup
|
||||
legend="Filter by"
|
||||
color="primary"
|
||||
options={options}
|
||||
idSelected={status}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -5,5 +5,4 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { Provider, alertsPageStateContainer } from './state_container';
|
||||
export { useAlertsPageStateContainer } from './use_alerts_page_state_container';
|
||||
export { AlertsStatusFilter } from './alerts_status_filter';
|
|
@ -5,17 +5,12 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiButtonGroup, EuiButtonGroupOptionProps } from '@elastic/eui';
|
||||
import { Query } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React from 'react';
|
||||
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED, ALERT_STATUS } from '@kbn/rule-data-utils';
|
||||
import { AlertStatus } from '../../../../common/typings';
|
||||
import { AlertStatusFilter } from '../../../../common/typings';
|
||||
|
||||
export interface AlertStatusFilterProps {
|
||||
status: AlertStatus;
|
||||
onChange: (id: string, value: string) => void;
|
||||
}
|
||||
export const DEFAULT_QUERIES: Query[] = [];
|
||||
|
||||
export const ALL_ALERTS: AlertStatusFilter = {
|
||||
status: '',
|
||||
|
@ -45,36 +40,3 @@ export const ALERT_STATUS_QUERY = {
|
|||
[ACTIVE_ALERTS.status]: ACTIVE_ALERTS.query,
|
||||
[RECOVERED_ALERTS.status]: RECOVERED_ALERTS.query,
|
||||
};
|
||||
|
||||
const options: EuiButtonGroupOptionProps[] = [
|
||||
{
|
||||
id: ALL_ALERTS.status,
|
||||
label: ALL_ALERTS.label,
|
||||
value: ALL_ALERTS.query,
|
||||
'data-test-subj': 'alert-status-filter-show-all-button',
|
||||
},
|
||||
{
|
||||
id: ACTIVE_ALERTS.status,
|
||||
label: ACTIVE_ALERTS.label,
|
||||
value: ACTIVE_ALERTS.query,
|
||||
'data-test-subj': 'alert-status-filter-active-button',
|
||||
},
|
||||
{
|
||||
id: RECOVERED_ALERTS.status,
|
||||
label: RECOVERED_ALERTS.label,
|
||||
value: RECOVERED_ALERTS.query,
|
||||
'data-test-subj': 'alert-status-filter-recovered-button',
|
||||
},
|
||||
];
|
||||
|
||||
export function AlertsStatusFilter({ status, onChange }: AlertStatusFilterProps) {
|
||||
return (
|
||||
<EuiButtonGroup
|
||||
legend="Filter by"
|
||||
color="primary"
|
||||
options={options}
|
||||
idSelected={status}
|
||||
onChange={onChange}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 { Provider, alertSearchBarStateContainer } from './state_container';
|
||||
export { useAlertSearchBarStateContainer } from './use_alert_search_bar_state_container';
|
|
@ -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 {
|
||||
createStateContainer,
|
||||
createStateContainerReactHelpers,
|
||||
} from '@kbn/kibana-utils-plugin/public';
|
||||
import { AlertStatus } from '../../../../../common/typings';
|
||||
import { ALL_ALERTS } from '../constants';
|
||||
|
||||
interface AlertSearchBarContainerState {
|
||||
rangeFrom: string;
|
||||
rangeTo: string;
|
||||
kuery: string;
|
||||
status: AlertStatus;
|
||||
}
|
||||
|
||||
interface AlertSearchBarStateTransitions {
|
||||
setRangeFrom: (
|
||||
state: AlertSearchBarContainerState
|
||||
) => (rangeFrom: string) => AlertSearchBarContainerState;
|
||||
setRangeTo: (
|
||||
state: AlertSearchBarContainerState
|
||||
) => (rangeTo: string) => AlertSearchBarContainerState;
|
||||
setKuery: (
|
||||
state: AlertSearchBarContainerState
|
||||
) => (kuery: string) => AlertSearchBarContainerState;
|
||||
setStatus: (
|
||||
state: AlertSearchBarContainerState
|
||||
) => (status: AlertStatus) => AlertSearchBarContainerState;
|
||||
}
|
||||
|
||||
const defaultState: AlertSearchBarContainerState = {
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
kuery: '',
|
||||
status: ALL_ALERTS.status as AlertStatus,
|
||||
};
|
||||
|
||||
const transitions: AlertSearchBarStateTransitions = {
|
||||
setRangeFrom: (state) => (rangeFrom) => ({ ...state, rangeFrom }),
|
||||
setRangeTo: (state) => (rangeTo) => ({ ...state, rangeTo }),
|
||||
setKuery: (state) => (kuery) => ({ ...state, kuery }),
|
||||
setStatus: (state) => (status) => ({ ...state, status }),
|
||||
};
|
||||
|
||||
const alertSearchBarStateContainer = createStateContainer(defaultState, transitions);
|
||||
|
||||
type AlertSearchBarStateContainer = typeof alertSearchBarStateContainer;
|
||||
|
||||
const { Provider, useContainer } = createStateContainerReactHelpers<AlertSearchBarStateContainer>();
|
||||
|
||||
export { Provider, alertSearchBarStateContainer, useContainer, defaultState };
|
||||
export type {
|
||||
AlertSearchBarStateContainer,
|
||||
AlertSearchBarContainerState,
|
||||
AlertSearchBarStateTransitions,
|
||||
};
|
|
@ -20,11 +20,11 @@ import { useTimefilterService } from '../../../../hooks/use_timefilter_service';
|
|||
import {
|
||||
useContainer,
|
||||
defaultState,
|
||||
AlertsPageStateContainer,
|
||||
AlertsPageContainerState,
|
||||
AlertSearchBarStateContainer,
|
||||
AlertSearchBarContainerState,
|
||||
} from './state_container';
|
||||
|
||||
export function useAlertsPageStateContainer() {
|
||||
export function useAlertSearchBarStateContainer() {
|
||||
const stateContainer = useContainer();
|
||||
|
||||
useUrlStateSyncEffect(stateContainer);
|
||||
|
@ -47,7 +47,7 @@ export function useAlertsPageStateContainer() {
|
|||
};
|
||||
}
|
||||
|
||||
function useUrlStateSyncEffect(stateContainer: AlertsPageStateContainer) {
|
||||
function useUrlStateSyncEffect(stateContainer: AlertSearchBarStateContainer) {
|
||||
const history = useHistory();
|
||||
const timefilterService = useTimefilterService();
|
||||
|
||||
|
@ -68,11 +68,11 @@ function useUrlStateSyncEffect(stateContainer: AlertsPageStateContainer) {
|
|||
}
|
||||
|
||||
function setupUrlStateSync(
|
||||
stateContainer: AlertsPageStateContainer,
|
||||
stateContainer: AlertSearchBarStateContainer,
|
||||
stateStorage: IKbnUrlStateStorage
|
||||
) {
|
||||
// This handles filling the state when an incomplete URL set is provided
|
||||
const setWithDefaults = (changedState: Partial<AlertsPageContainerState> | null) => {
|
||||
const setWithDefaults = (changedState: Partial<AlertSearchBarContainerState> | null) => {
|
||||
stateContainer.set({ ...defaultState, ...changedState });
|
||||
};
|
||||
|
||||
|
@ -88,10 +88,10 @@ function setupUrlStateSync(
|
|||
|
||||
function syncUrlStateWithInitialContainerState(
|
||||
timefilterService: TimefilterContract,
|
||||
stateContainer: AlertsPageStateContainer,
|
||||
stateContainer: AlertSearchBarStateContainer,
|
||||
urlStateStorage: IKbnUrlStateStorage
|
||||
) {
|
||||
const urlState = urlStateStorage.get<Partial<AlertsPageContainerState>>('_a');
|
||||
const urlState = urlStateStorage.get<Partial<AlertSearchBarContainerState>>('_a');
|
||||
|
||||
if (urlState) {
|
||||
const newState = {
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 { AlertSearchBar as ObservabilityAlertSearchBar } from './alert_search_bar';
|
||||
export { AlertSearchbarWithUrlSync as ObservabilityAlertSearchbarWithUrlSync } from './alert_search_bar_with_url_sync';
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { BoolQuery, Query } from '@kbn/es-query';
|
||||
import { AlertStatus } from '../../../../common/typings';
|
||||
|
||||
export interface AlertStatusFilterProps {
|
||||
status: AlertStatus;
|
||||
onChange: (id: string, value: string) => void;
|
||||
}
|
||||
|
||||
interface AlertSearchBarContainerState {
|
||||
rangeFrom: string;
|
||||
rangeTo: string;
|
||||
kuery: string;
|
||||
status: AlertStatus;
|
||||
}
|
||||
|
||||
interface AlertSearchBarStateTransitions {
|
||||
setRangeFrom: (rangeFrom: string) => AlertSearchBarContainerState;
|
||||
setRangeTo: (rangeTo: string) => AlertSearchBarContainerState;
|
||||
setKuery: (kuery: string) => AlertSearchBarContainerState;
|
||||
setStatus: (status: AlertStatus) => AlertSearchBarContainerState;
|
||||
}
|
||||
|
||||
export interface AlertSearchBarWithUrlSyncProps {
|
||||
appName: string;
|
||||
setEsQuery: (query: { bool: BoolQuery }) => void;
|
||||
queries?: Query[];
|
||||
}
|
||||
|
||||
export interface AlertSearchBarProps
|
||||
extends AlertSearchBarContainerState,
|
||||
AlertSearchBarStateTransitions,
|
||||
AlertSearchBarWithUrlSyncProps {}
|
|
@ -11,7 +11,7 @@ import { createObservabilityRuleTypeRegistryMock } from '../../../../rules/obser
|
|||
import AlertsFlyoutBody from './alerts_flyout_body';
|
||||
import { inventoryThresholdAlert } from '../../../../rules/fixtures/example_alerts';
|
||||
import { parseAlert } from '../parse_alert';
|
||||
import { RULE_DETAILS_PAGE_ID } from '../../../rule_details/types';
|
||||
import { RULE_DETAILS_PAGE_ID } from '../../../rule_details/constants';
|
||||
|
||||
describe('AlertsFlyoutBody', () => {
|
||||
jest
|
||||
|
|
|
@ -26,7 +26,7 @@ import {
|
|||
} from '@kbn/rule-data-utils';
|
||||
import moment from 'moment-timezone';
|
||||
import { useKibana, useUiSetting } from '@kbn/kibana-react-plugin/public';
|
||||
import { RULE_DETAILS_PAGE_ID } from '../../../rule_details/types';
|
||||
import { RULE_DETAILS_PAGE_ID } from '../../../rule_details/constants';
|
||||
import { asDuration } from '../../../../../common/utils/formatters';
|
||||
import { translations, paths } from '../../../../config';
|
||||
import { AlertStatusIndicator } from '../../../../components/shared/alert_status_indicator';
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
|
@ -11,4 +11,3 @@ export * from './severity_badge';
|
|||
export * from './workflow_status_filter';
|
||||
export * from './filter_for_value';
|
||||
export * from './parse_alert';
|
||||
export * from './alerts_status_filter';
|
||||
|
|
|
@ -10,7 +10,7 @@ import React from 'react';
|
|||
import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers';
|
||||
import { ObservabilityActions, ObservabilityActionsProps } from './observability_actions';
|
||||
import { inventoryThresholdAlert } from '../../../rules/fixtures/example_alerts';
|
||||
import { RULE_DETAILS_PAGE_ID } from '../../rule_details/types';
|
||||
import { RULE_DETAILS_PAGE_ID } from '../../rule_details/constants';
|
||||
import { createObservabilityRuleTypeRegistryMock } from '../../../rules/observability_rule_type_registry_mock';
|
||||
import { TimelineNonEcsData } from '@kbn/timelines-plugin/common';
|
||||
import * as pluginContext from '../../../hooks/use_plugin_context';
|
||||
|
|
|
@ -26,7 +26,7 @@ import { parseAlert } from './parse_alert';
|
|||
import { translations, paths } from '../../../config';
|
||||
import { ADD_TO_EXISTING_CASE, ADD_TO_NEW_CASE } from '../containers/alerts_table/translations';
|
||||
import { ObservabilityAppServices } from '../../../application/types';
|
||||
import { RULE_DETAILS_PAGE_ID } from '../../rule_details/types';
|
||||
import { RULE_DETAILS_PAGE_ID } from '../../rule_details/constants';
|
||||
import type { TopAlert } from '../containers/alerts_page/types';
|
||||
import { ObservabilityRuleTypeRegistry } from '../../..';
|
||||
import { ALERT_DETAILS_PAGE_ID } from '../../alert_details/types';
|
||||
|
|
|
@ -7,13 +7,13 @@
|
|||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiFlyoutSize } from '@elastic/eui';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Query, BoolQuery } from '@kbn/es-query';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { BoolQuery } from '@kbn/es-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { loadRuleAggregations } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { AlertStatus } from '../../../../../common/typings';
|
||||
import { ObservabilityAlertSearchbarWithUrlSync } from '../../../../components/shared/alert_search_bar';
|
||||
import { observabilityAlertFeatureIds } from '../../../../config';
|
||||
import { useGetUserCasesPermissions } from '../../../../hooks/use_get_user_cases_permissions';
|
||||
import { observabilityFeatureId } from '../../../../../common';
|
||||
|
@ -21,42 +21,23 @@ import { useBreadcrumbs } from '../../../../hooks/use_breadcrumbs';
|
|||
import { useHasData } from '../../../../hooks/use_has_data';
|
||||
import { usePluginContext } from '../../../../hooks/use_plugin_context';
|
||||
import { getNoDataConfig } from '../../../../utils/no_data_config';
|
||||
import { buildEsQuery } from '../../../../utils/build_es_query';
|
||||
import { LoadingObservability } from '../../../overview';
|
||||
import {
|
||||
Provider,
|
||||
alertsPageStateContainer,
|
||||
useAlertsPageStateContainer,
|
||||
} from '../state_container';
|
||||
import './styles.scss';
|
||||
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';
|
||||
import { ALERTS_PER_PAGE, ALERTS_SEARCH_BAR_ID, ALERTS_TABLE_ID } from './constants';
|
||||
import { RuleStatsState } from './types';
|
||||
|
||||
const getAlertStatusQuery = (status: string): Query[] => {
|
||||
return status ? [{ query: ALERT_STATUS_QUERY[status], language: 'kuery' }] : [];
|
||||
};
|
||||
|
||||
function AlertsPage() {
|
||||
export function AlertsPage() {
|
||||
const { ObservabilityPageTemplate, observabilityRuleTypeRegistry } = usePluginContext();
|
||||
const {
|
||||
cases,
|
||||
docLinks,
|
||||
http,
|
||||
notifications: { toasts },
|
||||
triggersActionsUi: {
|
||||
alertsTableConfigurationRegistry,
|
||||
getAlertsStateTable: AlertsStateTable,
|
||||
getAlertsSearchBar: AlertsSearchBar,
|
||||
},
|
||||
data: {
|
||||
query: {
|
||||
timefilter: { timefilter: timeFilterService },
|
||||
},
|
||||
},
|
||||
triggersActionsUi: { alertsTableConfigurationRegistry, getAlertsStateTable: AlertsStateTable },
|
||||
} = useKibana<ObservabilityAppServices>().services;
|
||||
|
||||
const [ruleStatsLoading, setRuleStatsLoading] = useState<boolean>(false);
|
||||
const [ruleStats, setRuleStats] = useState<RuleStatsState>({
|
||||
total: 0,
|
||||
|
@ -66,18 +47,7 @@ function AlertsPage() {
|
|||
snoozed: 0,
|
||||
});
|
||||
const { hasAnyData, isAllRequestsComplete } = useHasData();
|
||||
const { rangeFrom, setRangeFrom, rangeTo, setRangeTo, kuery, setKuery, status, setStatus } =
|
||||
useAlertsPageStateContainer();
|
||||
const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>(
|
||||
buildEsQuery(
|
||||
{
|
||||
to: rangeTo,
|
||||
from: rangeFrom,
|
||||
},
|
||||
kuery,
|
||||
getAlertStatusQuery(status)
|
||||
)
|
||||
);
|
||||
const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>();
|
||||
|
||||
useBreadcrumbs([
|
||||
{
|
||||
|
@ -129,46 +99,6 @@ function AlertsPage() {
|
|||
|
||||
const manageRulesHref = http.basePath.prepend('/app/observability/alerts/rules');
|
||||
|
||||
const onStatusChange = useCallback(
|
||||
(alertStatus: AlertStatus) => {
|
||||
setEsQuery(
|
||||
buildEsQuery(
|
||||
{
|
||||
to: rangeTo,
|
||||
from: rangeFrom,
|
||||
},
|
||||
kuery,
|
||||
getAlertStatusQuery(alertStatus)
|
||||
)
|
||||
);
|
||||
},
|
||||
[kuery, rangeFrom, rangeTo]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
onStatusChange(status);
|
||||
}, [onStatusChange, status]);
|
||||
|
||||
const onSearchBarParamsChange = useCallback(
|
||||
({ dateRange, query }) => {
|
||||
timeFilterService.setTime(dateRange);
|
||||
setRangeFrom(dateRange.from);
|
||||
setRangeTo(dateRange.to);
|
||||
setKuery(query);
|
||||
setEsQuery(
|
||||
buildEsQuery(
|
||||
{
|
||||
to: rangeTo,
|
||||
from: rangeFrom,
|
||||
},
|
||||
query,
|
||||
getAlertStatusQuery(status)
|
||||
)
|
||||
);
|
||||
},
|
||||
[rangeFrom, setRangeFrom, rangeTo, status, setRangeTo, setKuery, timeFilterService]
|
||||
);
|
||||
|
||||
// If there is any data, set hasData to true otherwise we need to wait till all the data is loaded before setting hasData to true or false; undefined indicates the data is still loading.
|
||||
const hasData = hasAnyData === true || (isAllRequestsComplete === false ? undefined : false);
|
||||
|
||||
|
@ -199,56 +129,33 @@ function AlertsPage() {
|
|||
>
|
||||
<EuiFlexGroup direction="column" gutterSize="s">
|
||||
<EuiFlexItem>
|
||||
<AlertsSearchBar
|
||||
appName={'observability-alerts'}
|
||||
featureIds={observabilityAlertFeatureIds}
|
||||
rangeFrom={rangeFrom}
|
||||
rangeTo={rangeTo}
|
||||
query={kuery}
|
||||
onQueryChange={onSearchBarParamsChange}
|
||||
<ObservabilityAlertSearchbarWithUrlSync
|
||||
appName={ALERTS_SEARCH_BAR_ID}
|
||||
setEsQuery={setEsQuery}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup justifyContent="spaceBetween" alignItems="center">
|
||||
<EuiFlexItem grow={false}>
|
||||
<AlertsStatusFilter
|
||||
status={status}
|
||||
onChange={(id) => {
|
||||
setStatus(id as AlertStatus);
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem>
|
||||
<CasesContext
|
||||
owner={[observabilityFeatureId]}
|
||||
permissions={userCasesPermissions}
|
||||
features={{ alerts: { sync: false } }}
|
||||
>
|
||||
<AlertsStateTable
|
||||
alertsTableConfigurationRegistry={alertsTableConfigurationRegistry}
|
||||
configurationId={AlertConsumers.OBSERVABILITY}
|
||||
id={ALERTS_TABLE_ID}
|
||||
flyoutSize={'s' as EuiFlyoutSize}
|
||||
featureIds={observabilityAlertFeatureIds}
|
||||
query={esQuery}
|
||||
showExpandToDetails={false}
|
||||
pageSize={ALERTS_PER_PAGE}
|
||||
/>
|
||||
{esQuery && (
|
||||
<AlertsStateTable
|
||||
alertsTableConfigurationRegistry={alertsTableConfigurationRegistry}
|
||||
configurationId={AlertConsumers.OBSERVABILITY}
|
||||
id={ALERTS_TABLE_ID}
|
||||
flyoutSize={'s' as EuiFlyoutSize}
|
||||
featureIds={observabilityAlertFeatureIds}
|
||||
query={esQuery}
|
||||
showExpandToDetails={false}
|
||||
pageSize={ALERTS_PER_PAGE}
|
||||
/>
|
||||
)}
|
||||
</CasesContext>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</ObservabilityPageTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
export function WrappedAlertsPage() {
|
||||
return (
|
||||
<Provider value={alertsPageStateContainer}>
|
||||
<AlertsPage />
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,5 +6,6 @@
|
|||
*/
|
||||
|
||||
export const ALERTS_PAGE_ID = 'alerts-o11y';
|
||||
export const ALERTS_SEARCH_BAR_ID = 'alerts-search-bar-o11y';
|
||||
export const ALERTS_PER_PAGE = 50;
|
||||
export const ALERTS_TABLE_ID = 'xpack.observability.alerts.alert.table';
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export { WrappedAlertsPage as AlertsPage } from './alerts_page';
|
||||
export { AlertsPage } from './alerts_page';
|
||||
export type { TopAlert } from './types';
|
||||
|
|
|
@ -6,4 +6,3 @@
|
|||
*/
|
||||
|
||||
export * from './alerts_page';
|
||||
export * from './state_container';
|
||||
|
|
|
@ -1,52 +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 {
|
||||
createStateContainer,
|
||||
createStateContainerReactHelpers,
|
||||
} from '@kbn/kibana-utils-plugin/public';
|
||||
import { AlertStatus } from '../../../../../common/typings';
|
||||
import { ALL_ALERTS } from '../..';
|
||||
|
||||
interface AlertsPageContainerState {
|
||||
rangeFrom: string;
|
||||
rangeTo: string;
|
||||
kuery: string;
|
||||
status: AlertStatus;
|
||||
}
|
||||
|
||||
interface AlertsPageStateTransitions {
|
||||
setRangeFrom: (
|
||||
state: AlertsPageContainerState
|
||||
) => (rangeFrom: string) => AlertsPageContainerState;
|
||||
setRangeTo: (state: AlertsPageContainerState) => (rangeTo: string) => AlertsPageContainerState;
|
||||
setKuery: (state: AlertsPageContainerState) => (kuery: string) => AlertsPageContainerState;
|
||||
setStatus: (state: AlertsPageContainerState) => (status: AlertStatus) => AlertsPageContainerState;
|
||||
}
|
||||
|
||||
const defaultState: AlertsPageContainerState = {
|
||||
rangeFrom: 'now-15m',
|
||||
rangeTo: 'now',
|
||||
kuery: '',
|
||||
status: ALL_ALERTS.status as AlertStatus,
|
||||
};
|
||||
|
||||
const transitions: AlertsPageStateTransitions = {
|
||||
setRangeFrom: (state) => (rangeFrom) => ({ ...state, rangeFrom }),
|
||||
setRangeTo: (state) => (rangeTo) => ({ ...state, rangeTo }),
|
||||
setKuery: (state) => (kuery) => ({ ...state, kuery }),
|
||||
setStatus: (state) => (status) => ({ ...state, status }),
|
||||
};
|
||||
|
||||
const alertsPageStateContainer = createStateContainer(defaultState, transitions);
|
||||
|
||||
type AlertsPageStateContainer = typeof alertsPageStateContainer;
|
||||
|
||||
const { Provider, useContainer } = createStateContainerReactHelpers<AlertsPageStateContainer>();
|
||||
|
||||
export { Provider, alertsPageStateContainer, useContainer, defaultState };
|
||||
export type { AlertsPageStateContainer, AlertsPageContainerState, AlertsPageStateTransitions };
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
export const EXECUTION_TAB = 'execution';
|
||||
export const ALERTS_TAB = 'alerts';
|
||||
export const EVENT_ERROR_LOG_TAB = 'rule_error_log_list';
|
||||
export const RULE_DETAILS_PAGE_ID = 'rule-details-alerts-o11y';
|
||||
export const RULE_DETAILS_ALERTS_SEARCH_BAR_ID = 'rule-details-alerts-search-bar-o11y';
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useHistory, useParams, useLocation } from 'react-router-dom';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
EuiText,
|
||||
|
@ -14,13 +14,14 @@ import {
|
|||
EuiButtonEmpty,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiFlyoutSize,
|
||||
EuiPanel,
|
||||
EuiPopover,
|
||||
EuiTabbedContent,
|
||||
EuiEmptyPrompt,
|
||||
EuiSuperSelectOption,
|
||||
EuiButton,
|
||||
EuiFlyoutSize,
|
||||
EuiTabbedContentTab,
|
||||
} from '@elastic/eui';
|
||||
|
||||
import {
|
||||
|
@ -32,17 +33,21 @@ import {
|
|||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
// TODO: use a Delete modal from triggersActionUI when it's sharable
|
||||
import { ALERTS_FEATURE_ID, RuleExecutionStatusErrorReasons } from '@kbn/alerting-plugin/common';
|
||||
import { Query, BoolQuery } from '@kbn/es-query';
|
||||
import { AlertConsumers } from '@kbn/rule-data-utils';
|
||||
import { RuleDefinitionProps } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { fromQuery, toQuery } from '../../utils/url';
|
||||
import { ObservabilityAlertSearchbarWithUrlSync } from '../../components/shared/alert_search_bar';
|
||||
import { DeleteModalConfirmation } from './components/delete_modal_confirmation';
|
||||
import { CenterJustifiedSpinner } from './components/center_justified_spinner';
|
||||
import {
|
||||
RuleDetailsPathParams,
|
||||
EVENT_LOG_LIST_TAB,
|
||||
ALERT_LIST_TAB,
|
||||
EXECUTION_TAB,
|
||||
ALERTS_TAB,
|
||||
RULE_DETAILS_PAGE_ID,
|
||||
} from './types';
|
||||
RULE_DETAILS_ALERTS_SEARCH_BAR_ID,
|
||||
} from './constants';
|
||||
import { RuleDetailsPathParams, TabId } from './types';
|
||||
import { useBreadcrumbs } from '../../hooks/use_breadcrumbs';
|
||||
import { usePluginContext } from '../../hooks/use_plugin_context';
|
||||
import { useFetchRule } from '../../hooks/use_fetch_rule';
|
||||
|
@ -63,7 +68,7 @@ export function RuleDetailsPage() {
|
|||
ruleTypeRegistry,
|
||||
getEditAlertFlyout,
|
||||
getRuleEventLogList,
|
||||
getAlertsStateTable,
|
||||
getAlertsStateTable: AlertsStateTable,
|
||||
getRuleAlertsSummary,
|
||||
getRuleStatusPanel,
|
||||
getRuleDefinition,
|
||||
|
@ -74,6 +79,8 @@ export function RuleDetailsPage() {
|
|||
|
||||
const { ruleId } = useParams<RuleDetailsPathParams>();
|
||||
const { ObservabilityPageTemplate, observabilityRuleTypeRegistry } = usePluginContext();
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
const filteredRuleTypes = useMemo(
|
||||
() => observabilityRuleTypeRegistry.list(),
|
||||
|
@ -84,12 +91,34 @@ export function RuleDetailsPage() {
|
|||
const { ruleTypes } = useLoadRuleTypes({
|
||||
filteredRuleTypes,
|
||||
});
|
||||
const [tabId, setTabId] = useState<TabId>(
|
||||
(toQuery(location.search)?.tabId as TabId) || EXECUTION_TAB
|
||||
);
|
||||
const [features, setFeatures] = useState<string>('');
|
||||
const [ruleType, setRuleType] = useState<RuleType<string, string>>();
|
||||
const [ruleToDelete, setRuleToDelete] = useState<string[]>([]);
|
||||
const [isPageLoading, setIsPageLoading] = useState(false);
|
||||
const [editFlyoutVisible, setEditFlyoutVisible] = useState<boolean>(false);
|
||||
const [isRuleEditPopoverOpen, setIsRuleEditPopoverOpen] = useState(false);
|
||||
const [esQuery, setEsQuery] = useState<{ bool: BoolQuery }>();
|
||||
const ruleQuery = useRef([
|
||||
{ query: `kibana.alert.rule.uuid: ${ruleId}`, language: 'kuery' },
|
||||
] as Query[]);
|
||||
|
||||
const updateUrl = (nextQuery: { tabId: TabId }) => {
|
||||
history.push({
|
||||
...location,
|
||||
search: fromQuery({
|
||||
...toQuery(location.search),
|
||||
...nextQuery,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
const onTabIdChange = (newTabId: TabId) => {
|
||||
setTabId(newTabId);
|
||||
updateUrl({ tabId: newTabId });
|
||||
};
|
||||
|
||||
const NOTIFY_WHEN_OPTIONS = useRef<Array<EuiSuperSelectOption<unknown>>>([]);
|
||||
useEffect(() => {
|
||||
|
@ -157,40 +186,26 @@ export function RuleDetailsPage() {
|
|||
? !ruleTypeRegistry.get(rule.ruleTypeId).requiresAppContext
|
||||
: false);
|
||||
|
||||
const alertStateProps = {
|
||||
alertsTableConfigurationRegistry,
|
||||
configurationId: observabilityFeatureId,
|
||||
id: RULE_DETAILS_PAGE_ID,
|
||||
flyoutSize: 's' as EuiFlyoutSize,
|
||||
featureIds: [features] as AlertConsumers[],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
term: {
|
||||
'kibana.alert.rule.uuid': ruleId,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
showExpandToDetails: false,
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
const tabs: EuiTabbedContentTab[] = [
|
||||
{
|
||||
id: EVENT_LOG_LIST_TAB,
|
||||
id: EXECUTION_TAB,
|
||||
name: i18n.translate('xpack.observability.ruleDetails.rule.eventLogTabText', {
|
||||
defaultMessage: 'Execution history',
|
||||
}),
|
||||
'data-test-subj': 'eventLogListTab',
|
||||
content: getRuleEventLogList<'default'>({
|
||||
ruleId: rule?.id,
|
||||
ruleType,
|
||||
} as RuleEventLogListProps),
|
||||
content: (
|
||||
<EuiFlexGroup style={{ minHeight: 600 }} direction={'column'}>
|
||||
<EuiFlexItem>
|
||||
{getRuleEventLogList<'default'>({
|
||||
ruleId: rule?.id,
|
||||
ruleType,
|
||||
} as RuleEventLogListProps)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: ALERT_LIST_TAB,
|
||||
id: ALERTS_TAB,
|
||||
name: i18n.translate('xpack.observability.ruleDetails.rule.alertsTabText', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
|
@ -198,7 +213,27 @@ export function RuleDetailsPage() {
|
|||
content: (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
{getAlertsStateTable(alertStateProps)}
|
||||
<ObservabilityAlertSearchbarWithUrlSync
|
||||
appName={RULE_DETAILS_ALERTS_SEARCH_BAR_ID}
|
||||
setEsQuery={setEsQuery}
|
||||
queries={ruleQuery.current}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup style={{ minHeight: 450 }} direction={'column'}>
|
||||
<EuiFlexItem>
|
||||
{esQuery && (
|
||||
<AlertsStateTable
|
||||
alertsTableConfigurationRegistry={alertsTableConfigurationRegistry}
|
||||
configurationId={observabilityFeatureId}
|
||||
id={RULE_DETAILS_PAGE_ID}
|
||||
flyoutSize={'s' as EuiFlyoutSize}
|
||||
featureIds={[features] as AlertConsumers[]}
|
||||
query={esQuery}
|
||||
showExpandToDetails={false}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</>
|
||||
),
|
||||
},
|
||||
|
@ -324,7 +359,14 @@ export function RuleDetailsPage() {
|
|||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="l" />
|
||||
<EuiTabbedContent data-test-subj="ruleDetailsTabbedContent" tabs={tabs} />
|
||||
<EuiTabbedContent
|
||||
data-test-subj="ruleDetailsTabbedContent"
|
||||
tabs={tabs}
|
||||
selectedTab={tabs.find((tab) => tab.id === tabId)}
|
||||
onTabClick={(tab) => {
|
||||
onTabIdChange(tab.id as TabId);
|
||||
}}
|
||||
/>
|
||||
{editFlyoutVisible &&
|
||||
getEditAlertFlyout({
|
||||
initialRule: rule,
|
||||
|
|
|
@ -6,12 +6,10 @@
|
|||
*/
|
||||
|
||||
import { HttpSetup } from '@kbn/core/public';
|
||||
import {
|
||||
Rule,
|
||||
RuleSummary,
|
||||
RuleType,
|
||||
ActionTypeRegistryContract,
|
||||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { Rule, RuleSummary, RuleType } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { ALERTS_TAB, EXECUTION_TAB } from './constants';
|
||||
|
||||
export type TabId = typeof ALERTS_TAB | typeof EXECUTION_TAB;
|
||||
|
||||
export interface RuleDetailsPathParams {
|
||||
ruleId: string;
|
||||
|
@ -36,15 +34,6 @@ export interface FetchRuleSummaryProps {
|
|||
ruleId: string;
|
||||
http: HttpSetup;
|
||||
}
|
||||
export interface FetchRuleActionConnectorsProps {
|
||||
http: HttpSetup;
|
||||
ruleActions: any[];
|
||||
}
|
||||
|
||||
export interface FetchRuleExecutionLogProps {
|
||||
http: HttpSetup;
|
||||
ruleId: string;
|
||||
}
|
||||
|
||||
export interface FetchRuleSummary {
|
||||
isLoadingRuleSummary: boolean;
|
||||
|
@ -65,19 +54,3 @@ export interface AlertListItem {
|
|||
isMuted: boolean;
|
||||
sortPriority: number;
|
||||
}
|
||||
export interface ItemTitleRuleSummaryProps {
|
||||
children: string;
|
||||
}
|
||||
export interface ItemValueRuleSummaryProps {
|
||||
itemValue: string;
|
||||
extraSpace?: boolean;
|
||||
}
|
||||
export interface ActionsProps {
|
||||
ruleActions: any[];
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
}
|
||||
|
||||
export const EVENT_LOG_LIST_TAB = 'rule_event_log_list';
|
||||
export const ALERT_LIST_TAB = 'rule_alert_list';
|
||||
export const EVENT_ERROR_LOG_TAB = 'rule_error_log_list';
|
||||
export const RULE_DETAILS_PAGE_ID = 'rule-details-alerts-o11y';
|
||||
|
|
93
x-pack/plugins/observability/public/utils/url.test.ts
Normal file
93
x-pack/plugins/observability/public/utils/url.test.ts
Normal file
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* 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 { toQuery, fromQuery } from './url';
|
||||
|
||||
describe('toQuery', () => {
|
||||
it('should parse string to object', () => {
|
||||
expect(toQuery('?foo=bar&name=john%20doe')).toEqual({
|
||||
foo: 'bar',
|
||||
name: 'john doe',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromQuery', () => {
|
||||
it('should not encode the following characters', () => {
|
||||
expect(
|
||||
fromQuery({
|
||||
a: true,
|
||||
b: 5000,
|
||||
c: ':',
|
||||
})
|
||||
).toEqual('a=true&b=5000&c=:');
|
||||
});
|
||||
|
||||
it('should encode the following characters', () => {
|
||||
expect(
|
||||
fromQuery({
|
||||
a: '@',
|
||||
b: '.',
|
||||
c: ';',
|
||||
d: ' ',
|
||||
})
|
||||
).toEqual('a=%40&b=.&c=%3B&d=%20');
|
||||
});
|
||||
|
||||
it('should handle null and undefined', () => {
|
||||
expect(
|
||||
fromQuery({
|
||||
a: undefined,
|
||||
b: null,
|
||||
})
|
||||
).toEqual('a=&b=');
|
||||
});
|
||||
|
||||
it('should handle arrays', () => {
|
||||
expect(
|
||||
fromQuery({
|
||||
arr: ['a', 'b'],
|
||||
})
|
||||
).toEqual('arr=a%2Cb');
|
||||
});
|
||||
|
||||
it('should parse object to string', () => {
|
||||
expect(
|
||||
fromQuery({
|
||||
traceId: 'bar',
|
||||
transactionId: 'john doe',
|
||||
})
|
||||
).toEqual('traceId=bar&transactionId=john%20doe');
|
||||
});
|
||||
|
||||
it('should not encode range params', () => {
|
||||
expect(
|
||||
fromQuery({
|
||||
rangeFrom: '2019-03-03T12:00:00.000Z',
|
||||
rangeTo: '2019-03-05T12:00:00.000Z',
|
||||
})
|
||||
).toEqual('rangeFrom=2019-03-03T12:00:00.000Z&rangeTo=2019-03-05T12:00:00.000Z');
|
||||
});
|
||||
|
||||
it('should handle undefined, boolean, and number values without throwing errors', () => {
|
||||
expect(
|
||||
fromQuery({
|
||||
flyoutDetailTab: undefined,
|
||||
refreshPaused: true,
|
||||
refreshInterval: 5000,
|
||||
})
|
||||
).toEqual('flyoutDetailTab=&refreshPaused=true&refreshInterval=5000');
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromQuery and toQuery', () => {
|
||||
it('should encode and decode correctly', () => {
|
||||
expect(
|
||||
fromQuery(toQuery('?name=john%20doe&path=a%2Fb&rangeFrom=2019-03-03T12:00:00.000Z'))
|
||||
).toEqual('name=john%20doe&path=a%2Fb&rangeFrom=2019-03-03T12:00:00.000Z');
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue