[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:
Alejandro Fernández Gómez 2021-10-18 12:19:57 +02:00 committed by GitHub
parent d08f091d4a
commit 27c7c6fd82
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 135 additions and 25 deletions

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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>
);

View file

@ -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>;
}

View file

@ -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) =>

View file

@ -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 })
),

View file

@ -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';