mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Synthetics] Monitor status rule, show to which monitors rule applies !! (#209281)
## Summary Fixes https://github.com/elastic/kibana/issues/198688 Monitor status rule, show to which monitors rule applies !! With filters shows to which monitors the rule applies <img width="1728" alt="image" src="https://github.com/user-attachments/assets/5536ad12-d0ed-4394-a511-8dd826bf3b56" /> Also showing an inspect flyout with query details <img width="1728" alt="image" src="https://github.com/user-attachments/assets/d441e4f9-8f0c-4fac-a2ca-5f214f8d2709" />
This commit is contained in:
parent
5da812ceda
commit
ac89e472a6
21 changed files with 466 additions and 61 deletions
|
@ -54,4 +54,6 @@ export enum SYNTHETICS_API_URLS {
|
|||
SYNTHETICS_MONITORS_PROJECT_DELETE = '/api/synthetics/project/{projectName}/monitors/_bulk_delete',
|
||||
|
||||
DYNAMIC_SETTINGS = `/api/synthetics/settings`,
|
||||
|
||||
INSPECT_STATUS_RULE = '/internal/synthetics/inspect_status_rule',
|
||||
}
|
||||
|
|
|
@ -71,9 +71,17 @@ export const AlertStatusCodec = t.interface({
|
|||
pendingConfigs: t.record(t.string, AlertPendingStatusMetaDataCodec),
|
||||
enabledMonitorQueryIds: t.array(t.string),
|
||||
staleDownConfigs: t.record(t.string, StaleAlertStatusMetaDataCodec),
|
||||
maxPeriod: t.number,
|
||||
});
|
||||
|
||||
export type StaleDownConfig = t.TypeOf<typeof StaleAlertStatusMetaDataCodec>;
|
||||
export type AlertStatusMetaData = t.TypeOf<typeof AlertStatusMetaDataCodec>;
|
||||
export type AlertOverviewStatus = t.TypeOf<typeof AlertStatusCodec>;
|
||||
export type StatusRuleInspect = AlertOverviewStatus & {
|
||||
monitors: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
}>;
|
||||
};
|
||||
export type AlertStatusConfigs = Record<string, AlertStatusMetaData>;
|
||||
|
|
|
@ -9,7 +9,7 @@ import React, { useEffect } from 'react';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { Filter } from '@kbn/es-query';
|
||||
import { EuiFormRow } from '@elastic/eui';
|
||||
import { EuiFormRow, EuiSkeletonText } from '@elastic/eui';
|
||||
import { useSyntheticsDataView } from '../../contexts/synthetics_data_view_context';
|
||||
import { ClientPluginsStart } from '../../../../plugin';
|
||||
|
||||
|
@ -40,6 +40,10 @@ export function AlertSearchBar({
|
|||
return () => sub.unsubscribe();
|
||||
}, [onChange, query]);
|
||||
|
||||
if (!dataView) {
|
||||
return <EuiSkeletonText lines={1} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiFormRow
|
||||
label={i18n.translate('xpack.synthetics.list.search.title', {
|
||||
|
@ -51,7 +55,7 @@ export function AlertSearchBar({
|
|||
appName="synthetics"
|
||||
iconType="search"
|
||||
placeholder={PLACEHOLDER}
|
||||
indexPatterns={dataView ? [dataView] : []}
|
||||
indexPatterns={[dataView]}
|
||||
onChange={(queryN) => {
|
||||
onChange({
|
||||
kqlQuery: String(queryN.query),
|
||||
|
|
|
@ -10,6 +10,7 @@ import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/p
|
|||
import { Filter } from '@kbn/es-query';
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
import { SyntheticsMonitorStatusRuleParams as StatusRuleParams } from '@kbn/response-ops-rule-params/synthetics_monitor_status';
|
||||
import { StatusRuleViz } from './status_rule_viz';
|
||||
import { FieldFilters } from './common/field_filters';
|
||||
import { AlertSearchBar } from './query_bar';
|
||||
import { StatusRuleExpression } from './status_rule_expression';
|
||||
|
@ -32,8 +33,9 @@ export const StatusRuleComponent: React.FC<{
|
|||
<AlertSearchBar kqlQuery={ruleParams.kqlQuery ?? ''} onChange={onFiltersChange} />
|
||||
<EuiSpacer size="m" />
|
||||
<FieldFilters ruleParams={ruleParams} setRuleParams={setRuleParams} />
|
||||
<StatusRuleExpression ruleParams={ruleParams} setRuleParams={setRuleParams} />
|
||||
<StatusRuleViz ruleParams={ruleParams} />
|
||||
<EuiSpacer size="m" />
|
||||
<StatusRuleExpression ruleParams={ruleParams} setRuleParams={setRuleParams} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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, { useEffect } from 'react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPopover,
|
||||
EuiPopoverTitle,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useInspectorContext } from '@kbn/observability-shared-plugin/public';
|
||||
import { RuleMonitorsTable } from '../rule_monitors_table';
|
||||
import { apiService } from '../../../../utils/api_service';
|
||||
import { inspectStatusRuleAction } from '../../state/alert_rules';
|
||||
import { selectInspectStatusRule } from '../../state/alert_rules/selectors';
|
||||
import { StatusRuleParamsProps } from './status_rule_ui';
|
||||
import { ClientPluginsStart } from '../../../../plugin';
|
||||
|
||||
export const StatusRuleViz = ({
|
||||
ruleParams,
|
||||
}: {
|
||||
ruleParams: StatusRuleParamsProps['ruleParams'];
|
||||
}) => {
|
||||
const { data } = useSelector(selectInspectStatusRule);
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
services: { inspector },
|
||||
} = useKibana<ClientPluginsStart>();
|
||||
|
||||
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
||||
|
||||
const { inspectorAdapters, addInspectorRequest } = useInspectorContext();
|
||||
|
||||
const inspect = () => {
|
||||
inspector.open(inspectorAdapters);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
apiService.addInspectorRequest = addInspectorRequest;
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
inspectorAdapters?.requests?.reset();
|
||||
dispatch(inspectStatusRuleAction.get(ruleParams));
|
||||
}, [ruleParams, dispatch, inspectorAdapters?.requests]);
|
||||
|
||||
return (
|
||||
<EuiCallOut iconType="search" size="s">
|
||||
<EuiFlexGroup alignItems="center" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
{i18n.translate('xpack.synthetics.statusRuleViz.ruleAppliesToFlexItemLabel', {
|
||||
defaultMessage: 'Rule applies to ',
|
||||
})}
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiPopover
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={() => setIsPopoverOpen(false)}
|
||||
button={
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="syntheticsStatusRuleVizMonitorQueryIDsButton"
|
||||
size="xs"
|
||||
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
|
||||
>
|
||||
{i18n.translate('xpack.synthetics.statusRuleViz.monitorQueryIdsPopoverButton', {
|
||||
defaultMessage:
|
||||
'{total} existing {total, plural, one {monitor} other {monitors}}',
|
||||
values: { total: data?.monitors.length },
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
}
|
||||
>
|
||||
<EuiPopoverTitle>
|
||||
{i18n.translate('xpack.synthetics.statusRuleViz.monitorsPopoverTitleLabel', {
|
||||
defaultMessage: 'Monitors',
|
||||
})}
|
||||
</EuiPopoverTitle>
|
||||
{i18n.translate('xpack.synthetics.statusRuleViz.ruleAppliesToFollowingPopoverLabel', {
|
||||
defaultMessage: 'Rule applies to following existing monitors.',
|
||||
})}
|
||||
<EuiSpacer size="s" />
|
||||
<RuleMonitorsTable />
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
{/* to push detail button to end*/}
|
||||
<EuiFlexItem />
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="syntheticsStatusRuleVizInspectButton"
|
||||
onClick={inspect}
|
||||
iconType="inspect"
|
||||
size="xs"
|
||||
>
|
||||
{i18n.translate('xpack.synthetics.rules.details', {
|
||||
defaultMessage: 'Details',
|
||||
})}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* 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 { Criteria, EuiLink, EuiInMemoryTable, EuiSearchBarProps } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { uniqBy } from 'lodash';
|
||||
import { selectInspectStatusRule } from '../state/alert_rules/selectors';
|
||||
import { ClientPluginsStart } from '../../../plugin';
|
||||
|
||||
export const RuleMonitorsTable = () => {
|
||||
const {
|
||||
services: { http },
|
||||
} = useKibana<ClientPluginsStart>();
|
||||
const { data } = useSelector(selectInspectStatusRule);
|
||||
|
||||
const [pageIndex, setPageIndex] = React.useState(0);
|
||||
const [pageSize, setPageSize] = React.useState(10);
|
||||
|
||||
const onTableChange = ({ page }: Criteria<any>) => {
|
||||
if (page) {
|
||||
setPageIndex(page.index);
|
||||
setPageSize(page.size);
|
||||
}
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: 'name',
|
||||
name: i18n.translate('xpack.synthetics.ruleDetails.monitorsTable.nameColumn', {
|
||||
defaultMessage: 'Name',
|
||||
}),
|
||||
render: (name: string, monitor: { id: string }) => (
|
||||
<EuiLink
|
||||
data-test-subj="ColumnsLink"
|
||||
href={http.basePath.prepend(`/app/synthetics/monitors/${monitor.id}`)}
|
||||
>
|
||||
{name}
|
||||
</EuiLink>
|
||||
),
|
||||
},
|
||||
{
|
||||
field: 'id',
|
||||
name: i18n.translate('xpack.synthetics.ruleDetails.monitorsTable.idColumn', {
|
||||
defaultMessage: 'ID',
|
||||
}),
|
||||
},
|
||||
{
|
||||
field: 'type',
|
||||
name: i18n.translate('xpack.synthetics.ruleDetails.monitorsTable.type', {
|
||||
defaultMessage: 'Type',
|
||||
}),
|
||||
},
|
||||
];
|
||||
|
||||
const search: EuiSearchBarProps = {
|
||||
box: {
|
||||
incremental: true,
|
||||
schema: true,
|
||||
},
|
||||
filters: [
|
||||
{
|
||||
type: 'field_value_selection',
|
||||
field: 'type',
|
||||
name: i18n.translate('xpack.synthetics.ruleDetails.monitorsTable.typeFilter', {
|
||||
defaultMessage: 'Type',
|
||||
}),
|
||||
multiSelect: false,
|
||||
options:
|
||||
uniqBy(
|
||||
data?.monitors.map((monitor) => ({
|
||||
value: monitor.type,
|
||||
name: monitor.type,
|
||||
})),
|
||||
'value'
|
||||
) ?? [],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiInMemoryTable
|
||||
tableLayout="auto"
|
||||
css={{ width: 600 }}
|
||||
items={data?.monitors ?? []}
|
||||
columns={columns}
|
||||
onChange={onTableChange}
|
||||
search={search}
|
||||
pagination={{
|
||||
pageIndex,
|
||||
pageSize,
|
||||
showPerPageOptions: true,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -9,6 +9,7 @@ import React from 'react';
|
|||
import { CoreStart } from '@kbn/core/public';
|
||||
import { Provider as ReduxProvider } from 'react-redux';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
import { InspectorContextProvider } from '@kbn/observability-shared-plugin/public';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { RuleTypeParamsExpressionProps } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { EuiSpacer, EuiText } from '@elastic/eui';
|
||||
|
@ -32,25 +33,27 @@ export default function MonitorStatusAlert({ coreStart, plugins, params }: Props
|
|||
const queryClient = new QueryClient();
|
||||
const { ruleParams } = params;
|
||||
return (
|
||||
<ReduxProvider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<KibanaContextProvider services={{ ...coreStart, ...plugins }}>
|
||||
{params.id && isEmpty(ruleParams) && (
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.alertRule.monitorStatus.description"
|
||||
defaultMessage="Manage synthetics monitor status rule actions."
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
<InspectorContextProvider>
|
||||
<ReduxProvider store={store}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<KibanaContextProvider services={{ ...coreStart, ...plugins }}>
|
||||
{params.id && isEmpty(ruleParams) && (
|
||||
<EuiText>
|
||||
<FormattedMessage
|
||||
id="xpack.synthetics.alertRule.monitorStatus.description"
|
||||
defaultMessage="Manage synthetics monitor status rule actions."
|
||||
/>
|
||||
</EuiText>
|
||||
)}
|
||||
|
||||
{(!params.id || !isEmpty(ruleParams)) && (
|
||||
<StatusRuleComponent ruleParams={ruleParams} setRuleParams={params.setRuleParams} />
|
||||
)}
|
||||
{(!params.id || !isEmpty(ruleParams)) && (
|
||||
<StatusRuleComponent ruleParams={ruleParams} setRuleParams={params.setRuleParams} />
|
||||
)}
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
</KibanaContextProvider>
|
||||
</QueryClientProvider>
|
||||
</ReduxProvider>
|
||||
<EuiSpacer size="m" />
|
||||
</KibanaContextProvider>
|
||||
</QueryClientProvider>
|
||||
</ReduxProvider>
|
||||
</InspectorContextProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { StatusRuleInspect } from '../../../../../common/runtime_types/alert_rules/common';
|
||||
import { StatusRuleParamsProps } from '../../components/alerts/status_rule_ui';
|
||||
import { DEFAULT_ALERT_RESPONSE } from '../../../../../common/types/default_alerts';
|
||||
import { createAsyncAction } from '../utils/actions';
|
||||
|
||||
|
@ -23,3 +25,8 @@ export const enableDefaultAlertingSilentlyAction = createAsyncAction<void, DEFAU
|
|||
export const updateDefaultAlertingAction = createAsyncAction<void, DEFAULT_ALERT_RESPONSE>(
|
||||
'updateDefaultAlertingAction'
|
||||
);
|
||||
|
||||
export const inspectStatusRuleAction = createAsyncAction<
|
||||
StatusRuleParamsProps['ruleParams'],
|
||||
StatusRuleInspect
|
||||
>('inspectStatusRuleAction');
|
||||
|
|
|
@ -5,10 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { StatusRuleInspect } from '../../../../../common/runtime_types/alert_rules/common';
|
||||
import { StatusRuleParamsProps } from '../../components/alerts/status_rule_ui';
|
||||
import { SYNTHETICS_API_URLS } from '../../../../../common/constants';
|
||||
import { DEFAULT_ALERT_RESPONSE } from '../../../../../common/types/default_alerts';
|
||||
import { apiService } from '../../../../utils/api_service';
|
||||
|
||||
export async function inspectStatusAlertAPI(
|
||||
ruleParams: StatusRuleParamsProps['ruleParams']
|
||||
): Promise<StatusRuleInspect> {
|
||||
return apiService.post(SYNTHETICS_API_URLS.INSPECT_STATUS_RULE, ruleParams);
|
||||
}
|
||||
|
||||
export async function getDefaultAlertingAPI(): Promise<DEFAULT_ALERT_RESPONSE> {
|
||||
return apiService.get(SYNTHETICS_API_URLS.ENABLE_DEFAULT_ALERTING);
|
||||
}
|
||||
|
|
|
@ -11,10 +11,16 @@ import {
|
|||
enableDefaultAlertingAction,
|
||||
enableDefaultAlertingSilentlyAction,
|
||||
getDefaultAlertingAction,
|
||||
inspectStatusRuleAction,
|
||||
updateDefaultAlertingAction,
|
||||
} from './actions';
|
||||
import { fetchEffectFactory } from '../utils/fetch_effect';
|
||||
import { enableDefaultAlertingAPI, getDefaultAlertingAPI, updateDefaultAlertingAPI } from './api';
|
||||
import {
|
||||
enableDefaultAlertingAPI,
|
||||
getDefaultAlertingAPI,
|
||||
inspectStatusAlertAPI,
|
||||
updateDefaultAlertingAPI,
|
||||
} from './api';
|
||||
|
||||
export function* getDefaultAlertingEffect() {
|
||||
yield takeLeading(
|
||||
|
@ -68,6 +74,21 @@ export function* updateDefaultAlertingEffect() {
|
|||
);
|
||||
}
|
||||
|
||||
export function* inspectStatusRuleEffect() {
|
||||
yield takeLeading(
|
||||
inspectStatusRuleAction.get,
|
||||
fetchEffectFactory(
|
||||
inspectStatusAlertAPI,
|
||||
inspectStatusRuleAction.success,
|
||||
inspectStatusRuleAction.fail,
|
||||
'',
|
||||
i18n.translate('xpack.synthetics.settings.statusRule.inspect', {
|
||||
defaultMessage: 'Failed to inspect monitor status rule type.',
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const successMessage = i18n.translate('xpack.synthetics.settings.enableAlerting', {
|
||||
defaultMessage:
|
||||
'Monitor status rule successfully updated. Changes will take effect on the next rule execution.',
|
||||
|
|
|
@ -6,26 +6,34 @@
|
|||
*/
|
||||
|
||||
import { createReducer } from '@reduxjs/toolkit';
|
||||
import { StatusRuleInspect } from '../../../../../common/runtime_types/alert_rules/common';
|
||||
import { DEFAULT_ALERT_RESPONSE } from '../../../../../common/types/default_alerts';
|
||||
import { IHttpSerializedFetchError } from '..';
|
||||
import {
|
||||
enableDefaultAlertingAction,
|
||||
enableDefaultAlertingSilentlyAction,
|
||||
getDefaultAlertingAction,
|
||||
inspectStatusRuleAction,
|
||||
updateDefaultAlertingAction,
|
||||
} from './actions';
|
||||
|
||||
export interface DefaultAlertingState {
|
||||
inspectData?: StatusRuleInspect;
|
||||
data?: DEFAULT_ALERT_RESPONSE;
|
||||
success: boolean | null;
|
||||
loading: boolean;
|
||||
error: IHttpSerializedFetchError | null;
|
||||
inspectLoading: boolean;
|
||||
inspectError?: IHttpSerializedFetchError | null;
|
||||
}
|
||||
|
||||
const initialSettingState: DefaultAlertingState = {
|
||||
success: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
inspectData: undefined,
|
||||
inspectLoading: false,
|
||||
inspectError: null,
|
||||
};
|
||||
|
||||
export const defaultAlertingReducer = createReducer(initialSettingState, (builder) => {
|
||||
|
@ -60,6 +68,18 @@ export const defaultAlertingReducer = createReducer(initialSettingState, (builde
|
|||
state.error = action.payload;
|
||||
state.loading = false;
|
||||
state.success = false;
|
||||
})
|
||||
.addCase(inspectStatusRuleAction.get, (state) => {
|
||||
state.inspectLoading = true;
|
||||
})
|
||||
.addCase(inspectStatusRuleAction.success, (state, action) => {
|
||||
state.inspectData = action.payload;
|
||||
state.inspectLoading = false;
|
||||
state.inspectError = null;
|
||||
})
|
||||
.addCase(inspectStatusRuleAction.fail, (state, action) => {
|
||||
state.inspectError = action.payload;
|
||||
state.inspectLoading = false;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -12,3 +12,10 @@ const getState = (appState: SyntheticsAppState) => appState.defaultAlerting;
|
|||
export const selectSyntheticsAlerts = createSelector(getState, (state) => state.data);
|
||||
export const selectSyntheticsAlertsLoading = createSelector(getState, (state) => state.loading);
|
||||
export const selectSyntheticsAlertsLoaded = createSelector(getState, (state) => state.success);
|
||||
export const selectInspectStatusRule = createSelector(getState, (state) => {
|
||||
return {
|
||||
loading: state.inspectLoading,
|
||||
data: state.inspectData,
|
||||
error: state.inspectError,
|
||||
};
|
||||
});
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
enableDefaultAlertingEffect,
|
||||
enableDefaultAlertingSilentlyEffect,
|
||||
getDefaultAlertingEffect,
|
||||
inspectStatusRuleEffect,
|
||||
updateDefaultAlertingEffect,
|
||||
} from './alert_rules/effects';
|
||||
import { executeEsQueryEffect } from './elasticsearch';
|
||||
|
@ -80,6 +81,7 @@ export const rootEffect = function* root(): Generator {
|
|||
fork(quietFetchMonitorStatusHeatmap),
|
||||
fork(fetchOverviewTrendStats),
|
||||
fork(refreshOverviewTrendStats),
|
||||
fork(inspectStatusRuleEffect),
|
||||
...privateLocationsEffects.map((effect) => fork(effect)),
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -130,6 +130,7 @@ export const mockState: SyntheticsAppState = {
|
|||
locationMonitors: [],
|
||||
},
|
||||
defaultAlerting: {
|
||||
inspectLoading: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
success: null,
|
||||
|
|
|
@ -12,6 +12,7 @@ import { observabilityPaths } from '@kbn/observability-plugin/common';
|
|||
import apm from 'elastic-apm-node';
|
||||
import { SYNTHETICS_ALERT_RULE_TYPES } from '@kbn/rule-data-utils';
|
||||
import { syntheticsMonitorStatusRuleParamsSchema } from '@kbn/response-ops-rule-params/synthetics_monitor_status';
|
||||
import { SyntheticsEsClient } from '../../lib';
|
||||
import { AlertOverviewStatus } from '../../../common/runtime_types/alert_rules/common';
|
||||
import { StatusRuleExecutorOptions } from './types';
|
||||
import { syntheticsRuleFieldMap } from '../../../common/rules/synthetics_rule_field_map';
|
||||
|
@ -26,6 +27,7 @@ import {
|
|||
import { getActionVariables } from '../action_variables';
|
||||
import { STATUS_RULE_NAME } from '../translations';
|
||||
import { SyntheticsMonitorClient } from '../../synthetics_service/synthetics_monitor/synthetics_monitor_client';
|
||||
import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants';
|
||||
|
||||
export const registerSyntheticsStatusCheckRule = (
|
||||
server: SyntheticsServerSetup,
|
||||
|
@ -55,7 +57,7 @@ export const registerSyntheticsStatusCheckRule = (
|
|||
executor: async (options: StatusRuleExecutorOptions) => {
|
||||
apm.setTransactionName('Synthetics Status Rule Executor');
|
||||
const { state: ruleState, params, services, spaceId } = options;
|
||||
const { alertsClient, uiSettingsClient } = services;
|
||||
const { alertsClient, uiSettingsClient, scopedClusterClient, savedObjectsClient } = services;
|
||||
if (!alertsClient) {
|
||||
throw new AlertsClientError();
|
||||
}
|
||||
|
@ -70,7 +72,15 @@ export const registerSyntheticsStatusCheckRule = (
|
|||
const groupBy = params?.condition?.groupBy ?? 'locationId';
|
||||
const groupByLocation = groupBy === 'locationId';
|
||||
|
||||
const statusRule = new StatusRuleExecutor(server, syntheticsMonitorClient, options);
|
||||
const esClient = new SyntheticsEsClient(
|
||||
savedObjectsClient,
|
||||
scopedClusterClient.asCurrentUser,
|
||||
{
|
||||
heartbeatIndices: SYNTHETICS_INDEX_PATTERN,
|
||||
}
|
||||
);
|
||||
|
||||
const statusRule = new StatusRuleExecutor(esClient, server, syntheticsMonitorClient, options);
|
||||
|
||||
const { downConfigs, staleDownConfigs, upConfigs } = await statusRule.getDownChecks(
|
||||
ruleState.meta?.downConfigs as AlertOverviewStatus['downConfigs']
|
||||
|
|
|
@ -28,39 +28,42 @@ export async function queryFilterMonitors({
|
|||
return;
|
||||
}
|
||||
const filters = toElasticsearchQuery(fromKueryExpression(ruleParams.kqlQuery));
|
||||
const { body: result } = await esClient.search({
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
FINAL_SUMMARY_FILTER,
|
||||
getRangeFilter({ from: 'now-24h/m', to: 'now/m' }),
|
||||
getTimeSpanFilter(),
|
||||
{
|
||||
term: {
|
||||
'meta.space_id': spaceId,
|
||||
const { body: result } = await esClient.search(
|
||||
{
|
||||
body: {
|
||||
size: 0,
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
FINAL_SUMMARY_FILTER,
|
||||
getRangeFilter({ from: 'now-24h/m', to: 'now/m' }),
|
||||
getTimeSpanFilter(),
|
||||
{
|
||||
term: {
|
||||
'meta.space_id': spaceId,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
bool: {
|
||||
should: filters,
|
||||
{
|
||||
bool: {
|
||||
should: filters,
|
||||
},
|
||||
},
|
||||
},
|
||||
...getFilters(ruleParams),
|
||||
],
|
||||
...getFilters(ruleParams),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
aggs: {
|
||||
ids: {
|
||||
terms: {
|
||||
size: 10000,
|
||||
field: 'config_id',
|
||||
aggs: {
|
||||
ids: {
|
||||
terms: {
|
||||
size: 10000,
|
||||
field: 'config_id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
'queryFilterMonitors'
|
||||
);
|
||||
|
||||
return result.aggregations?.ids.buckets.map((bucket) => bucket.key as string);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { intersection } from 'lodash';
|
|||
import { AlertStatusMetaData } from '../../../../common/runtime_types/alert_rules/common';
|
||||
import {
|
||||
FINAL_SUMMARY_FILTER,
|
||||
getTimespanFilter,
|
||||
getRangeFilter,
|
||||
SUMMARY_FILTER,
|
||||
} from '../../../../common/constants/client_defaults';
|
||||
import { OverviewPing } from '../../../../common/runtime_types';
|
||||
|
@ -75,7 +75,7 @@ export async function queryMonitorStatusAlert({
|
|||
bool: {
|
||||
filter: [
|
||||
...(includeRetests ? [SUMMARY_FILTER] : [FINAL_SUMMARY_FILTER]),
|
||||
getTimespanFilter({ from: range.from, to: range.to }),
|
||||
getRangeFilter({ from: range.from, to: range.to }),
|
||||
{
|
||||
terms: {
|
||||
'monitor.id': idsToQuery,
|
||||
|
@ -137,7 +137,10 @@ export async function queryMonitorStatusAlert({
|
|||
});
|
||||
}
|
||||
|
||||
const { body: result } = await esClient.search<OverviewPing, typeof params>(params);
|
||||
const { body: result } = await esClient.search<OverviewPing, typeof params>(
|
||||
params,
|
||||
'Monitors status rule query'
|
||||
);
|
||||
|
||||
result.aggregations?.id.buckets.forEach(({ location, key: queryId }) => {
|
||||
const locationSummaries = location.buckets.map(
|
||||
|
|
|
@ -17,6 +17,8 @@ import * as locationsUtils from '../../synthetics_service/get_all_locations';
|
|||
import type { PublicLocation } from '../../../common/runtime_types';
|
||||
import { SyntheticsServerSetup } from '../../types';
|
||||
import { AlertStatusMetaData } from '../../../common/runtime_types/alert_rules/common';
|
||||
import { SyntheticsEsClient } from '../../lib';
|
||||
import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants';
|
||||
|
||||
describe('StatusRuleExecutor', () => {
|
||||
// @ts-ignore
|
||||
|
@ -65,8 +67,11 @@ describe('StatusRuleExecutor', () => {
|
|||
|
||||
const mockStart = coreMock.createStart();
|
||||
const uiSettingsClient = mockStart.uiSettings.asScopedToClient(soClient);
|
||||
const esClient = new SyntheticsEsClient(soClient, mockEsClient, {
|
||||
heartbeatIndices: SYNTHETICS_INDEX_PATTERN,
|
||||
});
|
||||
|
||||
const statusRule = new StatusRuleExecutor(serverMock, monitorClient, {
|
||||
const statusRule = new StatusRuleExecutor(esClient, serverMock, monitorClient, {
|
||||
params: {},
|
||||
services: {
|
||||
uiSettingsClient,
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
AlertStatusConfigs,
|
||||
AlertStatusMetaData,
|
||||
StaleDownConfig,
|
||||
StatusRuleInspect,
|
||||
} from '../../../common/runtime_types/alert_rules/common';
|
||||
import { queryFilterMonitors } from './queries/filter_monitors';
|
||||
import { MonitorSummaryStatusRule, StatusRuleExecutorOptions } from './types';
|
||||
|
@ -37,7 +38,6 @@ import { queryMonitorStatusAlert } from './queries/query_monitor_status_alert';
|
|||
import { parseArrayFilters } from '../../routes/common';
|
||||
import { SyntheticsServerSetup } from '../../types';
|
||||
import { SyntheticsEsClient } from '../../lib';
|
||||
import { SYNTHETICS_INDEX_PATTERN } from '../../../common/constants';
|
||||
import {
|
||||
getAllMonitors,
|
||||
processMonitors,
|
||||
|
@ -67,20 +67,19 @@ export class StatusRuleExecutor {
|
|||
ruleName: string;
|
||||
|
||||
constructor(
|
||||
esClient: SyntheticsEsClient,
|
||||
server: SyntheticsServerSetup,
|
||||
syntheticsMonitorClient: SyntheticsMonitorClient,
|
||||
options: StatusRuleExecutorOptions
|
||||
) {
|
||||
const { services, params, previousStartedAt, rule } = options;
|
||||
const { scopedClusterClient, savedObjectsClient } = services;
|
||||
const { savedObjectsClient } = services;
|
||||
this.ruleName = rule.name;
|
||||
this.logger = server.logger;
|
||||
this.previousStartedAt = previousStartedAt;
|
||||
this.params = params;
|
||||
this.soClient = savedObjectsClient;
|
||||
this.esClient = new SyntheticsEsClient(this.soClient, scopedClusterClient.asCurrentUser, {
|
||||
heartbeatIndices: SYNTHETICS_INDEX_PATTERN,
|
||||
});
|
||||
this.esClient = esClient;
|
||||
this.server = server;
|
||||
this.syntheticsMonitorClient = syntheticsMonitorClient;
|
||||
this.hasCustomCondition = !isEmpty(this.params);
|
||||
|
@ -97,6 +96,7 @@ export class StatusRuleExecutor {
|
|||
this.dateFormat = await uiSettingsClient.get('dateFormat');
|
||||
const timezone = await uiSettingsClient.get('dateFormat:tz');
|
||||
this.tz = timezone === 'Browser' ? 'UTC' : timezone;
|
||||
return await this.getMonitors();
|
||||
}
|
||||
|
||||
async getMonitors() {
|
||||
|
@ -135,9 +135,8 @@ export class StatusRuleExecutor {
|
|||
}
|
||||
|
||||
async getDownChecks(prevDownConfigs: AlertStatusConfigs = {}): Promise<AlertOverviewStatus> {
|
||||
await this.init();
|
||||
const { enabledMonitorQueryIds, maxPeriod, monitorLocationIds, monitorLocationsMap } =
|
||||
await this.getMonitors();
|
||||
await this.init();
|
||||
|
||||
const range = this.getRange(maxPeriod);
|
||||
|
||||
|
@ -151,6 +150,7 @@ export class StatusRuleExecutor {
|
|||
staleDownConfigs,
|
||||
enabledMonitorQueryIds,
|
||||
pendingConfigs: {},
|
||||
maxPeriod,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -203,6 +203,7 @@ export class StatusRuleExecutor {
|
|||
...currentStatus,
|
||||
staleDownConfigs,
|
||||
pendingConfigs: {},
|
||||
maxPeriod,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -430,6 +431,26 @@ export class StatusRuleExecutor {
|
|||
context,
|
||||
});
|
||||
}
|
||||
|
||||
getRuleThresholdOverview = async (): Promise<StatusRuleInspect> => {
|
||||
const data = await this.getDownChecks({});
|
||||
return {
|
||||
...data,
|
||||
monitors: [
|
||||
...this.monitors.map((monitor) => ({
|
||||
id: monitor.id,
|
||||
name: monitor.attributes.name,
|
||||
type: monitor.attributes.type,
|
||||
})),
|
||||
// add some 1000 dummy monitors to test the pagination
|
||||
...new Array(1000).fill(null).map((_, index) => ({
|
||||
id: `dummy-${index}`,
|
||||
name: `dummy-${index}`,
|
||||
type: 'http',
|
||||
})),
|
||||
],
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const getDoesMonitorMeetLocationThreshold = ({
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { syntheticsInspectStatusRuleRoute } from './rules/inspect_status_rule';
|
||||
import { syntheticsGetLatestTestRunRoute } from './pings/get_latest_test_run';
|
||||
import { deleteSyntheticsParamsBulkRoute } from './settings/params/delete_params_bulk';
|
||||
import { deleteSyntheticsMonitorBulkRoute } from './monitor_cruds/bulk_cruds/delete_monitor_bulk';
|
||||
|
@ -101,6 +102,7 @@ export const syntheticsAppRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
|
|||
createPostDynamicSettingsRoute,
|
||||
syntheticsGetPingHeatmapRoute,
|
||||
createOverviewTrendsRoute,
|
||||
syntheticsInspectStatusRuleRoute,
|
||||
];
|
||||
|
||||
export const syntheticsAppPublicRestApiRoutes: SyntheticsRestApiRouteFactory[] = [
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 {
|
||||
SyntheticsMonitorStatusRuleParams,
|
||||
syntheticsMonitorStatusRuleParamsSchema,
|
||||
} from '@kbn/response-ops-rule-params/synthetics_monitor_status';
|
||||
import { StatusRuleExecutorOptions } from '../../alert_rules/status_rule/types';
|
||||
import { StatusRuleExecutor } from '../../alert_rules/status_rule/status_rule_executor';
|
||||
import { SyntheticsRestApiRouteFactory } from '../types';
|
||||
import { SYNTHETICS_API_URLS } from '../../../common/constants';
|
||||
|
||||
export const syntheticsInspectStatusRuleRoute: SyntheticsRestApiRouteFactory = () => ({
|
||||
method: 'POST',
|
||||
path: SYNTHETICS_API_URLS.INSPECT_STATUS_RULE,
|
||||
validate: {
|
||||
body: syntheticsMonitorStatusRuleParamsSchema,
|
||||
},
|
||||
handler: async ({
|
||||
request,
|
||||
server,
|
||||
syntheticsMonitorClient,
|
||||
savedObjectsClient,
|
||||
spaceId,
|
||||
context,
|
||||
syntheticsEsClient,
|
||||
}): Promise<any> => {
|
||||
const { uiSettings, elasticsearch } = await context.core;
|
||||
|
||||
const { client: esClient } = elasticsearch;
|
||||
|
||||
const ruleParams = request.body as SyntheticsMonitorStatusRuleParams;
|
||||
const services = {
|
||||
scopedClusterClient: esClient,
|
||||
savedObjectsClient,
|
||||
uiSettingsClient: uiSettings.client,
|
||||
} as unknown as StatusRuleExecutorOptions['services'];
|
||||
|
||||
const statusRule = new StatusRuleExecutor(syntheticsEsClient, server, syntheticsMonitorClient, {
|
||||
spaceId,
|
||||
services,
|
||||
params: ruleParams,
|
||||
state: {} as any,
|
||||
previousStartedAt: new Date(),
|
||||
rule: { name: 'Inspect Status Rule', id: 'inspect-status-rule' } as any,
|
||||
logger: server.logger,
|
||||
executionId: 'inspect-status-rule',
|
||||
startedAt: new Date(),
|
||||
isServerless: false,
|
||||
startedAtOverridden: false,
|
||||
flappingSettings: {} as any,
|
||||
getTimeRange: {} as any,
|
||||
} as StatusRuleExecutorOptions);
|
||||
|
||||
return await statusRule.getRuleThresholdOverview();
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue