mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[RAC] Link inventory alerts to the right inventory view (#113553)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
d08f091d4a
commit
27c7c6fd82
7 changed files with 135 additions and 25 deletions
|
@ -5,7 +5,10 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import * as rt from 'io-ts';
|
||||
import { Unit } from '@elastic/datemath';
|
||||
import { ANOMALY_THRESHOLD } from '../../infra_ml';
|
||||
import { InventoryItemType, SnapshotMetricType } from '../../inventory_models/types';
|
||||
import { SnapshotCustomMetricInput } from '../../http_api';
|
||||
|
||||
// TODO: Have threshold and inventory alerts import these types from this file instead of from their
|
||||
// local directories
|
||||
|
@ -54,3 +57,25 @@ export interface MetricAnomalyParams {
|
|||
threshold: Exclude<ANOMALY_THRESHOLD, ANOMALY_THRESHOLD.LOW>;
|
||||
influencerFilter: rt.TypeOf<typeof metricAnomalyInfluencerFilterRT> | undefined;
|
||||
}
|
||||
|
||||
// Types for the executor
|
||||
|
||||
export interface InventoryMetricConditions {
|
||||
metric: SnapshotMetricType;
|
||||
timeSize: number;
|
||||
timeUnit: Unit;
|
||||
sourceId?: string;
|
||||
threshold: number[];
|
||||
comparator: Comparator;
|
||||
customMetric?: SnapshotCustomMetricInput;
|
||||
warningThreshold?: number[];
|
||||
warningComparator?: Comparator;
|
||||
}
|
||||
|
||||
export interface InventoryMetricThresholdParams {
|
||||
criteria: InventoryMetricConditions[];
|
||||
filterQuery?: string;
|
||||
nodeType: InventoryItemType;
|
||||
sourceId?: string;
|
||||
alertOnNoData?: boolean;
|
||||
}
|
||||
|
|
|
@ -5,15 +5,48 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { ALERT_REASON } from '@kbn/rule-data-utils';
|
||||
import { ALERT_REASON, ALERT_RULE_PARAMS, TIMESTAMP } from '@kbn/rule-data-utils';
|
||||
import { encode } from 'rison-node';
|
||||
import { stringify } from 'query-string';
|
||||
import { ObservabilityRuleTypeFormatter } from '../../../../observability/public';
|
||||
import { InventoryMetricThresholdParams } from '../../../common/alerting/metrics';
|
||||
|
||||
export const formatReason: ObservabilityRuleTypeFormatter = ({ fields }) => {
|
||||
const reason = fields[ALERT_REASON] ?? '-';
|
||||
const link = '/app/metrics/inventory'; // TODO https://github.com/elastic/kibana/issues/106497
|
||||
const ruleParams = parseRuleParams(fields[ALERT_RULE_PARAMS]);
|
||||
|
||||
let link = '/app/metrics/link-to/inventory?';
|
||||
|
||||
if (ruleParams) {
|
||||
const linkToParams: Record<string, any> = {
|
||||
nodeType: ruleParams.nodeType,
|
||||
timestamp: Date.parse(fields[TIMESTAMP]),
|
||||
customMetric: '',
|
||||
};
|
||||
|
||||
// We always pick the first criteria metric for the URL
|
||||
const criteria = ruleParams.criteria[0];
|
||||
if (criteria.customMetric && criteria.customMetric.id !== 'alert-custom-metric') {
|
||||
const customMetric = encode(criteria.customMetric);
|
||||
linkToParams.customMetric = customMetric;
|
||||
linkToParams.metric = customMetric;
|
||||
} else {
|
||||
linkToParams.metric = encode({ type: criteria.metric });
|
||||
}
|
||||
|
||||
link += stringify(linkToParams);
|
||||
}
|
||||
|
||||
return {
|
||||
reason,
|
||||
link,
|
||||
};
|
||||
};
|
||||
|
||||
function parseRuleParams(params?: string): InventoryMetricThresholdParams | undefined {
|
||||
try {
|
||||
return typeof params === 'string' ? JSON.parse(params) : undefined;
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { match as RouteMatch, Redirect, Route, Switch } from 'react-router-dom';
|
|||
|
||||
import { RedirectToNodeDetail } from './redirect_to_node_detail';
|
||||
import { RedirectToHostDetailViaIP } from './redirect_to_host_detail_via_ip';
|
||||
import { RedirectToInventory } from './redirect_to_inventory';
|
||||
import { inventoryModels } from '../../../common/inventory_models';
|
||||
|
||||
interface LinkToPageProps {
|
||||
|
@ -29,6 +30,7 @@ export const LinkToMetricsPage: React.FC<LinkToPageProps> = (props) => {
|
|||
path={`${props.match.url}/host-detail-via-ip/:hostIp`}
|
||||
component={RedirectToHostDetailViaIP}
|
||||
/>
|
||||
<Route path={`${props.match.url}/inventory`} component={RedirectToInventory} />
|
||||
<Redirect to="/" />
|
||||
</Switch>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { parse } from 'query-string';
|
||||
import { Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
// FIXME what would be the right way to build this query string?
|
||||
const QUERY_STRING_TEMPLATE =
|
||||
"?waffleFilter=(expression:'',kind:kuery)&waffleTime=(currentTime:{timestamp},isAutoReloading:!f)&waffleOptions=(accountId:'',autoBounds:!t,boundsOverride:(max:1,min:0),customMetrics:!({customMetric}),customOptions:!(),groupBy:!(),legend:(palette:cool,reverseColors:!f,steps:10),metric:{metric},nodeType:{nodeType},region:'',sort:(by:name,direction:desc),timelineOpen:!f,view:map)";
|
||||
|
||||
export const RedirectToInventory: React.FC<RouteComponentProps> = ({ location }) => {
|
||||
const parsedQueryString = parseQueryString(location.search);
|
||||
|
||||
const inventoryQueryString = QUERY_STRING_TEMPLATE.replace(
|
||||
/{(\w+)}/g,
|
||||
(_, key) => parsedQueryString[key] || ''
|
||||
);
|
||||
|
||||
return <Redirect to={'/inventory' + inventoryQueryString} />;
|
||||
};
|
||||
|
||||
function parseQueryString(search: string): Record<string, string> {
|
||||
if (search.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const obj = parse(search.substring(1));
|
||||
|
||||
// Force all values into string. If they are empty don't create the keys
|
||||
for (const key in obj) {
|
||||
if (Object.hasOwnProperty.call(obj, key)) {
|
||||
if (!obj[key]) {
|
||||
delete obj[key];
|
||||
}
|
||||
if (Array.isArray(obj.key)) {
|
||||
obj[key] = obj[key]![0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj as Record<string, string>;
|
||||
}
|
|
@ -11,7 +11,7 @@ import { ALERT_REASON, ALERT_RULE_PARAMS } from '@kbn/rule-data-utils';
|
|||
import moment from 'moment';
|
||||
import { getCustomMetricLabel } from '../../../../common/formatters/get_custom_metric_label';
|
||||
import { toMetricOpt } from '../../../../common/snapshot_metric_i18n';
|
||||
import { AlertStates, InventoryMetricConditions } from './types';
|
||||
import { AlertStates } from './types';
|
||||
import {
|
||||
ActionGroupIdsOf,
|
||||
ActionGroup,
|
||||
|
@ -20,10 +20,11 @@ import {
|
|||
RecoveredActionGroup,
|
||||
} from '../../../../../alerting/common';
|
||||
import { AlertInstance, AlertTypeState } from '../../../../../alerting/server';
|
||||
import { InventoryItemType, SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import { InfraBackendLibs } from '../../infra_types';
|
||||
import { METRIC_FORMATTERS } from '../../../../common/formatters/snapshot_metric_formats';
|
||||
import { createFormatter } from '../../../../common/formatters';
|
||||
import { InventoryMetricThresholdParams } from '../../../../common/alerting/metrics';
|
||||
import {
|
||||
buildErrorAlertReason,
|
||||
buildFiredAlertReason,
|
||||
|
@ -33,19 +34,10 @@ import {
|
|||
} from '../common/messages';
|
||||
import { evaluateCondition } from './evaluate_condition';
|
||||
|
||||
interface InventoryMetricThresholdParams {
|
||||
criteria: InventoryMetricConditions[];
|
||||
filterQuery: string | undefined;
|
||||
nodeType: InventoryItemType;
|
||||
sourceId?: string;
|
||||
alertOnNoData?: boolean;
|
||||
}
|
||||
|
||||
type InventoryMetricThresholdAllowedActionGroups = ActionGroupIdsOf<
|
||||
typeof FIRED_ACTIONS | typeof WARNING_ACTIONS
|
||||
>;
|
||||
|
||||
export type InventoryMetricThresholdAlertTypeParams = Record<string, any>;
|
||||
export type InventoryMetricThresholdAlertTypeState = AlertTypeState; // no specific state used
|
||||
export type InventoryMetricThresholdAlertInstanceState = AlertInstanceState; // no specific state used
|
||||
export type InventoryMetricThresholdAlertInstanceContext = AlertInstanceContext; // no specific instance context used
|
||||
|
@ -64,14 +56,13 @@ type InventoryMetricThresholdAlertInstanceFactory = (
|
|||
|
||||
export const createInventoryMetricThresholdExecutor = (libs: InfraBackendLibs) =>
|
||||
libs.metricsRules.createLifecycleRuleExecutor<
|
||||
InventoryMetricThresholdAlertTypeParams,
|
||||
InventoryMetricThresholdParams & Record<string, unknown>,
|
||||
InventoryMetricThresholdAlertTypeState,
|
||||
InventoryMetricThresholdAlertInstanceState,
|
||||
InventoryMetricThresholdAlertInstanceContext,
|
||||
InventoryMetricThresholdAllowedActionGroups
|
||||
>(async ({ services, params }) => {
|
||||
const { criteria, filterQuery, sourceId, nodeType, alertOnNoData } =
|
||||
params as InventoryMetricThresholdParams;
|
||||
const { criteria, filterQuery, sourceId, nodeType, alertOnNoData } = params;
|
||||
if (criteria.length === 0) throw new Error('Cannot execute an alert with 0 conditions');
|
||||
const { alertWithLifecycle, savedObjectsClient } = services;
|
||||
const alertInstanceFactory: InventoryMetricThresholdAlertInstanceFactory = (id, reason) =>
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { schema, Type } from '@kbn/config-schema';
|
||||
import { Unit } from '@elastic/datemath';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PluginSetupContract } from '../../../../../alerting/server';
|
||||
import {
|
||||
|
@ -26,21 +27,32 @@ import {
|
|||
metricActionVariableDescription,
|
||||
thresholdActionVariableDescription,
|
||||
} from '../common/messages';
|
||||
import {
|
||||
SnapshotMetricTypeKeys,
|
||||
SnapshotMetricType,
|
||||
InventoryItemType,
|
||||
} from '../../../../common/inventory_models/types';
|
||||
import {
|
||||
SNAPSHOT_CUSTOM_AGGREGATIONS,
|
||||
SnapshotCustomAggregation,
|
||||
} from '../../../../common/http_api/snapshot_api';
|
||||
|
||||
const condition = schema.object({
|
||||
threshold: schema.arrayOf(schema.number()),
|
||||
comparator: oneOfLiterals(Object.values(Comparator)),
|
||||
timeUnit: schema.string(),
|
||||
comparator: oneOfLiterals(Object.values(Comparator)) as Type<Comparator>,
|
||||
timeUnit: schema.string() as Type<Unit>,
|
||||
timeSize: schema.number(),
|
||||
metric: schema.string(),
|
||||
metric: oneOfLiterals(Object.keys(SnapshotMetricTypeKeys)) as Type<SnapshotMetricType>,
|
||||
warningThreshold: schema.maybe(schema.arrayOf(schema.number())),
|
||||
warningComparator: schema.maybe(oneOfLiterals(Object.values(Comparator))),
|
||||
warningComparator: schema.maybe(oneOfLiterals(Object.values(Comparator))) as Type<
|
||||
Comparator | undefined
|
||||
>,
|
||||
customMetric: schema.maybe(
|
||||
schema.object({
|
||||
type: schema.literal('custom'),
|
||||
id: schema.string(),
|
||||
field: schema.string(),
|
||||
aggregation: schema.string(),
|
||||
aggregation: oneOfLiterals(SNAPSHOT_CUSTOM_AGGREGATIONS) as Type<SnapshotCustomAggregation>,
|
||||
label: schema.maybe(schema.string()),
|
||||
})
|
||||
),
|
||||
|
@ -59,7 +71,7 @@ export async function registerMetricInventoryThresholdAlertType(
|
|||
params: schema.object(
|
||||
{
|
||||
criteria: schema.arrayOf(condition),
|
||||
nodeType: schema.string(),
|
||||
nodeType: schema.string() as Type<InventoryItemType>,
|
||||
filterQuery: schema.maybe(
|
||||
schema.string({ validate: validateIsStringElasticsearchJSONFilter })
|
||||
),
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
import { Unit } from '@elastic/datemath';
|
||||
import { SnapshotCustomMetricInput } from '../../../../common/http_api/snapshot_api';
|
||||
import { SnapshotMetricType } from '../../../../common/inventory_models/types';
|
||||
import { Comparator, AlertStates } from '../common/types';
|
||||
import { Comparator, AlertStates, Aggregators } from '../common/types';
|
||||
|
||||
export { Comparator, AlertStates };
|
||||
export { Comparator, AlertStates, Aggregators };
|
||||
|
||||
export const METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID = 'metrics.alert.inventory.threshold';
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue