Alerting: misc ui fixes volume 4 (#34503)

This commit is contained in:
Domas
2021-05-25 10:26:10 +03:00
committed by GitHub
parent 1d3bcb0e90
commit d666defaea
11 changed files with 86 additions and 55 deletions

View File

@@ -54,21 +54,21 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
<FieldArray name="matchers" control={control}>
{({ fields, append, remove }) => (
<>
<div>Matchers</div>
<div>Matching labels</div>
<div className={styles.matchersContainer}>
{fields.map((field, index) => {
const localPath = `matchers[${index}]`;
return (
<HorizontalGroup key={field.id} align="flex-start">
<Field
label="Name"
label="Label"
invalid={!!errors.matchers?.[index]?.name}
error={errors.matchers?.[index]?.name?.message}
>
<Input
{...register(`${localPath}.name`, { required: 'Field is required' })}
defaultValue={field.name}
placeholder="label"
/>
</Field>
<Field
@@ -79,6 +79,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
<Input
{...register(`${localPath}.value`, { required: 'Field is required' })}
defaultValue={field.value}
placeholder="value"
/>
</Field>
<Field className={styles.matcherRegexField} label="Regex">

View File

@@ -38,30 +38,34 @@ export const AmRoutesExpandedRead: FC<AmRoutesExpandedReadProps> = ({ onChange,
<div className={gridStyles.valueCell}>{repeatInterval}</div>
<div className={gridStyles.titleCell}>Nested policies</div>
<div className={gridStyles.valueCell}>
<AmRoutesTable
isAddMode={isAddMode}
onCancelAdd={() => {
setIsAddMode(false);
setSubroutes((subroutes) => {
const newSubroutes = [...subroutes];
newSubroutes.pop();
return newSubroutes;
});
}}
onChange={(newRoutes) => {
onChange({
...routes,
routes: newRoutes,
});
if (isAddMode) {
{!!subroutes.length ? (
<AmRoutesTable
isAddMode={isAddMode}
onCancelAdd={() => {
setIsAddMode(false);
}
}}
receivers={receivers}
routes={subroutes}
/>
setSubroutes((subroutes) => {
const newSubroutes = [...subroutes];
newSubroutes.pop();
return newSubroutes;
});
}}
onChange={(newRoutes) => {
onChange({
...routes,
routes: newRoutes,
});
if (isAddMode) {
setIsAddMode(false);
}
}}
receivers={receivers}
routes={subroutes}
/>
) : (
<p>No nested policies configured.</p>
)}
{!isAddMode && (
<Button
className={styles.addNestedRoutingBtn}

View File

@@ -65,7 +65,7 @@ export const AmRoutesTable: FC<AmRoutesTableProps> = ({ isAddMode, onCancelAdd,
const cols: RouteTableColumnProps[] = [
{
id: 'matchingCriteria',
label: 'Matching criteria',
label: 'Matching labels',
// eslint-disable-next-line react/display-name
renderCell: (item) => <Matchers matchers={item.data.matchers} />,
size: 10,

View File

@@ -3,7 +3,7 @@ import { AlertManagerCortexConfig } from 'app/plugins/datasource/alertmanager/ty
import React, { FC, useMemo, useState } from 'react';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import { getAlertTableStyles } from '../../styles/table';
import { extractReadableNotifierTypes } from '../../utils/receivers';
import { extractNotifierTypeCounts } from '../../utils/receivers';
import { ActionIcon } from '../rules/ActionIcon';
import { ReceiversSection } from './ReceiversSection';
import { makeAMLink } from '../../utils/misc';
@@ -48,7 +48,14 @@ export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
() =>
config.alertmanager_config.receivers?.map((receiver) => ({
name: receiver.name,
types: extractReadableNotifierTypes(receiver, grafanaNotifiers.result ?? []),
types: Object.entries(extractNotifierTypeCounts(receiver, grafanaNotifiers.result ?? [])).map(
([type, count]) => {
if (count > 1) {
return `${type} (${count})`;
}
return type;
}
),
})) ?? [],
[config, grafanaNotifiers.result]
);

View File

@@ -66,8 +66,14 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
values: {
...defaultValues,
...values,
annotations: values.annotations?.filter(({ key, value }) => !!key && !!value) ?? [],
labels: values.labels?.filter(({ key }) => !!key) ?? [],
annotations:
values.annotations
?.map(({ key, value }) => ({ key: key.trim(), value: value.trim() }))
.filter(({ key, value }) => !!key && !!value) ?? [],
labels:
values.labels
?.map(({ key, value }) => ({ key: key.trim(), value: value.trim() }))
.filter(({ key }) => !!key) ?? [],
},
existing,
redirectOnSave: exitOnSave ? returnTo : undefined,

View File

@@ -8,7 +8,7 @@ interface Props {
}
export const AlertInstanceDetails: FC<Props> = ({ instance }) => {
const annotations = Object.entries(instance.annotations || {}) || [];
const annotations = (Object.entries(instance.annotations || {}) || []).filter(([_, value]) => !!value.trim());
return (
<div>

View File

@@ -24,7 +24,7 @@ export const RuleDetails: FC<Props> = ({ rule, rulesSource }) => {
const { promRule } = rule;
const annotations = Object.entries(rule.annotations);
const annotations = Object.entries(rule.annotations).filter(([_, value]) => !!value.trim());
const dataSources: Array<{ name: string; icon?: string }> = useMemo(() => {
if (isCloudRulesSource(rulesSource)) {

View File

@@ -2,7 +2,7 @@ import { css } from '@emotion/css';
import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data';
import { HorizontalGroup, Spinner, useStyles2 } from '@grafana/ui';
import { CombinedRule } from 'app/types/unified-alerting';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import React, { FC, useMemo } from 'react';
import { isAlertingRule, isRecordingRule } from '../../utils/rules';
import { AlertStateTag } from './AlertStateTag';
@@ -27,7 +27,7 @@ export const RuleState: FC<Props> = ({ rule, isDeleting, isCreating }) => {
) {
// find earliest alert
const firstActiveAt = promRule.alerts.reduce((prev, alert) => {
if (alert.activeAt) {
if (alert.activeAt && alert.state !== GrafanaAlertState.Normal) {
const activeAt = new Date(alert.activeAt);
if (prev === null || prev.getTime() > activeAt.getTime()) {
return activeAt;
@@ -36,7 +36,7 @@ export const RuleState: FC<Props> = ({ rule, isDeleting, isCreating }) => {
return prev;
}, null as Date | null);
// caclulate time elapsed from earliest alert
// calculate time elapsed from earliest alert
if (firstActiveAt) {
return (
<span title={String(firstActiveAt)} className={style.for}>

View File

@@ -21,15 +21,14 @@ const MatchersField: FC<Props> = ({ className }) => {
return (
<div className={cx(className, styles.wrapper)}>
<Field label="Matchers" required>
<Field label="Matching labels" required>
<div>
<div className={styles.matchers}>
{matchers.map((matcher, index) => {
console.log(matcher);
return (
<div className={styles.row} key={`${matcher.id}`}>
<Field
label="Name"
label="Label"
invalid={!!errors?.matchers?.[index]?.name}
error={errors?.matchers?.[index]?.name?.message}
>
@@ -38,7 +37,7 @@ const MatchersField: FC<Props> = ({ className }) => {
required: { value: true, message: 'Required.' },
})}
defaultValue={matcher.name}
placeholder="name"
placeholder="label"
/>
</Field>
<Field

View File

@@ -48,7 +48,7 @@ const SilencesTable: FC<Props> = ({ silences, alertManagerAlerts, alertManagerSo
<tr>
<th />
<th>State</th>
<th>Matchers</th>
<th>Matching labels</th>
<th>Alerts</th>
<th>Schedule</th>
{contextSrv.isEditor && <th>Action</th>}

View File

@@ -3,29 +3,43 @@ import { GrafanaManagedReceiverConfig, Receiver } from 'app/plugins/datasource/a
import { NotifierDTO } from 'app/types';
import { capitalize } from 'lodash';
// extract readable notifier types that are in use for a receiver, eg ['Slack', 'Email', 'PagerDuty']
export function extractReadableNotifierTypes(receiver: Receiver, grafanaNotifiers: NotifierDTO[]): string[] {
return [
// grafana specific receivers
...getReadabaleGrafanaNotifierTypes(receiver.grafana_managed_receiver_configs ?? [], grafanaNotifiers),
// cortex alert manager receivers
...getReadableCortexAlertManagerNotifierTypes(receiver),
];
// extract notifier type name to count map, eg { Slack: 1, Email: 2 }
type NotifierTypeCounts = Record<string, number>; // name : count
export function extractNotifierTypeCounts(receiver: Receiver, grafanaNotifiers: NotifierDTO[]): NotifierTypeCounts {
if (receiver['grafana_managed_receiver_configs']) {
return getGrafanaNotifierTypeCounts(receiver.grafana_managed_receiver_configs ?? [], grafanaNotifiers);
}
return getCortexAlertManagerNotifierTypeCounts(receiver);
}
function getReadableCortexAlertManagerNotifierTypes(receiver: Receiver): string[] {
function getCortexAlertManagerNotifierTypeCounts(receiver: Receiver): NotifierTypeCounts {
return Object.entries(receiver)
.filter(([key]) => key !== 'grafana_managed_receiver_configs' && key.endsWith('_configs')) // filter out only properties that are alert manager notifier
.filter(([_, value]) => Array.isArray(value) && !!value.length) // check that there are actually notifiers of this type configured
.map(([key]) => key.replace('_configs', '')) // remove the `_config` part from the key, making it intto a notifier name
.map((type) => receiverTypeNames[type] ?? capitalize(type)); // either map to readable name or, failing that, capitalize
.reduce<NotifierTypeCounts>((acc, [key, value]) => {
const type = key.replace('_configs', ''); // remove the `_config` part from the key, making it intto a notifier name
const name = receiverTypeNames[type] ?? capitalize(type);
return {
...acc,
[name]: (acc[name] ?? 0) + (Array.isArray(value) ? value.length : 1),
};
}, {});
}
function getReadabaleGrafanaNotifierTypes(
function getGrafanaNotifierTypeCounts(
configs: GrafanaManagedReceiverConfig[],
grafanaNotifiers: NotifierDTO[]
): string[] {
): NotifierTypeCounts {
return configs
.map((recv) => recv.type) // extract types from config
.map((type) => grafanaNotifiers.find((r) => r.type === type)?.name ?? capitalize(type)); // get readable name from notifier cofnig, or if not available, just capitalize
.map((type) => grafanaNotifiers.find((r) => r.type === type)?.name ?? capitalize(type)) // get readable name from notifier cofnig, or if not available, just capitalize
.reduce<NotifierTypeCounts>(
(acc, type) => ({
...acc,
[type]: (acc[type] ?? 0) + 1,
}),
{}
);
}