mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: misc ui fixes volume 4 (#34503)
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>}
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user