diff --git a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx index 44e96fd3dd0..e2e98488f1f 100644 --- a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx @@ -4,8 +4,6 @@ import React from 'react'; import { Route } from 'react-router-dom'; import { TestProvider } from 'test/helpers/TestProvider'; import { ui } from 'test/helpers/alertingRuleEditor'; -import { clickSelectOptionMatch } from 'test/helpers/selectOptionInTest'; -import { byRole } from 'testing-library-selector'; import { locationService, setDataSourceSrv } from '@grafana/runtime'; import { ADD_NEW_FOLER_OPTION } from 'app/core/components/Select/FolderPicker'; @@ -153,8 +151,8 @@ describe('RuleEditor grafana managed rules', () => { expect(nameInput).toHaveValue('my great new rule'); //check that folder is in the list expect(ui.inputs.folder.get()).toHaveTextContent(new RegExp(folder.title)); - expect(ui.inputs.annotationValue(0).get()).toHaveValue('some description'); - expect(ui.inputs.annotationValue(1).get()).toHaveValue('some summary'); + expect(ui.inputs.annotationValue(0).get()).toHaveValue('some summary'); + expect(ui.inputs.annotationValue(1).get()).toHaveValue('some description'); //check that slashed folders are not in the list expect(ui.inputs.folder.get()).toHaveTextContent(new RegExp(folder.title)); @@ -170,9 +168,9 @@ describe('RuleEditor grafana managed rules', () => { // expect(within(folderInput).queryByText("Folders with '/' character are not allowed.")).not.toBeInTheDocument(); // add an annotation - await clickSelectOptionMatch(ui.inputs.annotationKey(2).get(), /Add new/); - await userEvent.type(byRole('textbox').get(ui.inputs.annotationKey(2).get()), 'custom'); - await userEvent.type(ui.inputs.annotationValue(2).get(), 'value'); + await userEvent.click(screen.getByText('Add custom annotation')); + await userEvent.type(screen.getByPlaceholderText('Enter custom annotation name...'), 'custom'); + await userEvent.type(screen.getByPlaceholderText('Enter custom annotation content...'), 'value'); //add a label await userEvent.type(getLabelInput(ui.inputs.labelKey(2).get()), 'custom{enter}'); diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.v1.test.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.v1.test.tsx index be44912898a..0e41790c7c6 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.v1.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.v1.test.tsx @@ -257,11 +257,9 @@ describe('Receivers', () => { await waitFor(() => expect(ui.testContactPointModal.get()).toBeInTheDocument(), { timeout: 1000 }); await userEvent.click(ui.customContactPointOption.get()); - await waitFor(() => expect(ui.contactPointAnnotationSelect(0).get()).toBeInTheDocument()); // enter custom annotations and labels - await clickSelectOption(ui.contactPointAnnotationSelect(0).get(), 'Description'); - await userEvent.type(ui.contactPointAnnotationValue(0).get(), 'Test contact point'); + await userEvent.type(screen.getByPlaceholderText('Enter a description...'), 'Test contact point'); await userEvent.type(ui.contactPointLabelKey(0).get(), 'foo'); await userEvent.type(ui.contactPointLabelValue(0).get(), 'bar'); await userEvent.click(ui.testContactPoint.get()); diff --git a/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx b/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx index a552329b90e..eaad2d40ce2 100644 --- a/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx +++ b/public/app/features/alerting/unified/components/receivers/form/TestContactPointModal.tsx @@ -7,6 +7,7 @@ import { Modal, Button, Label, useStyles2, RadioButtonGroup } from '@grafana/ui' import { TestReceiversAlert } from 'app/plugins/datasource/alertmanager/types'; import { Annotations, Labels } from 'app/types/unified-alerting-dto'; +import { defaultAnnotations } from '../../../utils/constants'; import AnnotationsField from '../../rule-editor/AnnotationsField'; import LabelsField from '../../rule-editor/LabelsField'; @@ -34,7 +35,7 @@ enum NotificationType { const notificationOptions = Object.values(NotificationType).map((value) => ({ label: value, value: value })); const defaultValues: FormFields = { - annotations: [{ key: '', value: '' }], + annotations: [...defaultAnnotations], labels: [{ key: '', value: '' }], }; diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationHeaderField.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationHeaderField.tsx new file mode 100644 index 00000000000..99d7b2fd71a --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationHeaderField.tsx @@ -0,0 +1,78 @@ +import { css } from '@emotion/css'; +import React from 'react'; +import { FieldArrayWithId, useFormContext } from 'react-hook-form'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { InputControl, useStyles2 } from '@grafana/ui'; + +import { RuleFormValues } from '../../types/rule-form'; +import { Annotation, annotationDescriptions, annotationLabels } from '../../utils/constants'; + +import CustomAnnotationHeaderField from './CustomAnnotationHeaderField'; + +const AnnotationHeaderField = ({ + annotationField, + annotations, + annotation, + index, +}: { + annotationField: FieldArrayWithId; + annotations: Array<{ key: string; value: string }>; + annotation: Annotation; + index: number; +}) => { + const styles = useStyles2(getStyles); + const { control } = useFormContext(); + return ( +
+ +
{annotationDescriptions[annotation]}
+
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + annotationTitle: css` + color: ${theme.colors.text.primary}; + margin-bottom: 3px; + `, + + annotationContainer: css` + margin-top: 5px; + `, + + annotationDescription: css` + color: ${theme.colors.text.secondary}; + `, +}); + +export default AnnotationHeaderField; diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx index cc5040ed5c9..2412a2aa2a7 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.test.tsx @@ -33,7 +33,7 @@ jest.mock( ); const ui = { - setDashboardButton: byRole('button', { name: 'Set dashboard and panel' }), + setDashboardButton: byRole('button', { name: 'Link dashboard and panel' }), annotationKeys: byTestId('annotation-key-', { exact: false }), annotationValues: byTestId('annotation-value-', { exact: false }), dashboardPicker: { @@ -154,18 +154,12 @@ describe('AnnotationsField', function () { await user.click(ui.dashboardPicker.confirmButton.get()); - const annotationKeyElements = ui.annotationKeys.getAll(); const annotationValueElements = ui.annotationValues.getAll(); expect(ui.dashboardPicker.dialog.query()).not.toBeInTheDocument(); - expect(annotationKeyElements).toHaveLength(2); expect(annotationValueElements).toHaveLength(2); - - expect(annotationKeyElements[0]).toHaveTextContent('Dashboard UID'); expect(annotationValueElements[0]).toHaveTextContent('dash-test-uid'); - - expect(annotationKeyElements[1]).toHaveTextContent('Panel ID'); expect(annotationValueElements[1]).toHaveTextContent('2'); }); diff --git a/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.tsx b/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.tsx index 58df2ededd9..18529115b2e 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AnnotationsField.tsx @@ -1,18 +1,21 @@ import { css, cx } from '@emotion/css'; import produce from 'immer'; -import React, { useCallback } from 'react'; +import React, { useEffect, useState } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; import { useToggle } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; import { Stack } from '@grafana/experimental'; -import { Button, Field, Input, InputControl, Label, TextArea, useStyles2 } from '@grafana/ui'; +import { Button, Field, Input, TextArea, useStyles2 } from '@grafana/ui'; +import { DashboardDataDTO } from 'app/types'; +import { dashboardApi } from '../../api/dashboardApi'; import { RuleFormValues } from '../../types/rule-form'; -import { Annotation } from '../../utils/constants'; +import { Annotation, annotationLabels } from '../../utils/constants'; -import { AnnotationKeyInput } from './AnnotationKeyInput'; -import { DashboardPicker } from './DashboardPicker'; +import AnnotationHeaderField from './AnnotationHeaderField'; +import DashboardAnnotationField from './DashboardAnnotationField'; +import { DashboardPicker, PanelDTO } from './DashboardPicker'; const AnnotationsField = () => { const styles = useStyles2(getStyles); @@ -27,16 +30,31 @@ const AnnotationsField = () => { } = useFormContext(); const annotations = watch('annotations'); - const existingKeys = useCallback( - (index: number): string[] => annotations.filter((_, idx: number) => idx !== index).map(({ key }) => key), - [annotations] - ); - const { fields, append, remove } = useFieldArray({ control, name: 'annotations' }); const selectedDashboardUid = annotations.find((annotation) => annotation.key === Annotation.dashboardUID)?.value; const selectedPanelId = annotations.find((annotation) => annotation.key === Annotation.panelID)?.value; + const [selectedDashboard, setSelectedDashboard] = useState(undefined); + const [selectedPanel, setSelectedPanel] = useState(undefined); + + const { useDashboardQuery } = dashboardApi; + + const { currentData: dashboardResult, isFetching: isDashboardFetching } = useDashboardQuery( + { uid: selectedDashboardUid ?? '' }, + { skip: !selectedDashboardUid } + ); + + useEffect(() => { + if (isDashboardFetching) { + return; + } + + setSelectedDashboard(dashboardResult?.dashboard); + const currentPanel = dashboardResult?.dashboard?.panels?.find((panel) => panel.id.toString() === selectedPanelId); + setSelectedPanel(currentPanel); + }, [selectedPanelId, dashboardResult, isDashboardFetching]); + const setSelectedDashboardAndPanelId = (dashboardUid: string, panelId: string) => { const updatedAnnotations = produce(annotations, (draft) => { const dashboardAnnotation = draft.find((a) => a.key === Annotation.dashboardUID); @@ -59,76 +77,104 @@ const AnnotationsField = () => { setShowPanelSelector(false); }; + const handleDeleteDashboardAnnotation = () => { + const updatedAnnotations = annotations.filter( + (a) => a.key !== Annotation.dashboardUID && a.key !== Annotation.panelID + ); + setValue('annotations', updatedAnnotations); + setSelectedDashboard(undefined); + setSelectedPanel(undefined); + }; + + const handleEditDashboardAnnotation = () => { + setShowPanelSelector(true); + }; + return ( <> -
- {fields.map((annotationField, index) => { + {fields.map((annotationField, index: number) => { const isUrl = annotations[index]?.key?.toLocaleLowerCase().endsWith('url'); const ValueInputComponent = isUrl ? Input : TextArea; - + // eslint-disable-next-line + const annotation = annotationField.key as Annotation; return (
- - ( - - )} - control={control} - rules={{ required: { value: !!annotations[index]?.value, message: 'Required.' } }} +
+ - - - - -
+ } +
); })} - - +
+ + {!selectedDashboard && ( + + )} +
{showPanelSelector && ( ({ textarea: css` height: 76px; `, - addAnnotationsButton: css` - flex-grow: 0; - align-self: flex-start; - margin-left: 148px; + addAnnotationsButtonContainer: css` + margin-top: ${theme.spacing(1)}; + gap: ${theme.spacing(1)}; + display: flex; `, flexColumn: css` display: flex; @@ -169,7 +215,29 @@ const getStyles = (theme: GrafanaTheme2) => ({ justify-content: flex-start; `, flexRowItemMargin: css` - margin-left: ${theme.spacing(0.5)}; + margin-top: ${theme.spacing(1)}; + `, + deleteAnnotationButton: css` + display: inline-block; + margin-top: 10px; + margin-left: 10px; + `, + + annotationTitle: css` + color: ${theme.colors.text.primary}; + margin-bottom: 3px; + `, + + annotationContainer: css` + margin-top: 5px; + `, + + annotationDescription: css` + color: ${theme.colors.text.secondary}; + `, + + annotationValueContainer: css` + display: flex; `, }); diff --git a/public/app/features/alerting/unified/components/rule-editor/CustomAnnotationHeaderField.tsx b/public/app/features/alerting/unified/components/rule-editor/CustomAnnotationHeaderField.tsx new file mode 100644 index 00000000000..41d2a9f2087 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/CustomAnnotationHeaderField.tsx @@ -0,0 +1,39 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Input, useStyles2 } from '@grafana/ui'; + +interface CustomAnnotationHeaderFieldProps { + field: { onChange: () => void; onBlur: () => void; value: string; name: string }; +} + +const CustomAnnotationHeaderField = ({ field }: CustomAnnotationHeaderFieldProps) => { + const styles = useStyles2(getStyles); + + return ( +
+ Custom annotation name and content + +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + annotationTitle: css` + color: ${theme.colors.text.primary}; + margin-bottom: 3px; + `, + + customAnnotationInput: css` + margin-top: 5px; + width: 100%; + `, +}); + +export default CustomAnnotationHeaderField; diff --git a/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx b/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx new file mode 100644 index 00000000000..7b935a67f86 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/DashboardAnnotationField.tsx @@ -0,0 +1,84 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, useStyles2 } from '@grafana/ui'; +import { DashboardDataDTO } from 'app/types'; + +import { makeDashboardLink, makePanelLink } from '../../utils/misc'; + +import { PanelDTO } from './DashboardPicker'; + +const DashboardAnnotationField = ({ + dashboard, + panel, + dashboardUid, + panelId, + onEditClick, + onDeleteClick, +}: { + dashboard?: DashboardDataDTO; + panel?: PanelDTO; + dashboardUid: string; //fallback + panelId: string; //fallback + onEditClick: () => void; + onDeleteClick: () => void; +}) => { + const styles = useStyles2(getStyles); + + const dashboardLink = makeDashboardLink(dashboard?.uid || dashboardUid); + const panelLink = makePanelLink(dashboard?.uid || dashboardUid, panel?.id.toString() || panelId); + return ( +
+ {dashboard && ( + + {dashboard.title} + + )} + + {!dashboard && Dashboard {dashboardUid} } + + {panel && ( + + {panel.title || ''} + + )} + + {!panel && - Panel {panelId}} + + {(dashboard || panel) && ( + <> + + + + )} +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + container: css` + margin-top: 5px; + `, + + noLink: css` + color: ${theme.colors.text.secondary}; + `, + link: css` + color: ${theme.colors.text.link}; + margin-right: ${theme.spacing(1.5)}; + `, + + icon: css` + margin-right: ${theme.spacing(1)}; + cursor: pointer; + `, +}); + +export default DashboardAnnotationField; diff --git a/public/app/features/alerting/unified/components/rule-editor/DetailsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/DetailsStep.tsx index a33071e7a05..43af268c8d0 100644 --- a/public/app/features/alerting/unified/components/rule-editor/DetailsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/DetailsStep.tsx @@ -1,40 +1,62 @@ +import { css } from '@emotion/css'; import React from 'react'; import { useFormContext } from 'react-hook-form'; +import { GrafanaTheme2 } from '@grafana/data'; +import { Icon, useStyles2 } from '@grafana/ui'; + import { RuleFormType, RuleFormValues } from '../../types/rule-form'; +import { HoverCard } from '../HoverCard'; import AnnotationsField from './AnnotationsField'; import { GroupAndNamespaceFields } from './GroupAndNamespaceFields'; import { RuleEditorSection } from './RuleEditorSection'; -function getDescription(ruleType: RuleFormType | undefined) { +function getDescription(ruleType: RuleFormType | undefined, styles: { [key: string]: string }) { + const annotationsText = 'Add annotations to provide more context in your alert notifications.'; + if (ruleType === RuleFormType.cloudRecording) { return 'Select the Namespace and Group for your recording rule.'; } const docsLink = 'https://grafana.com/docs/grafana/latest/alerting/fundamentals/annotation-label/variables-label-annotation'; + + const HelpContent = () => ( +
+
+ Annotations +
+
+ Annotations add metadata to provide more information on the alert in your alert notifications. For example, add + a Summary annotation to tell you which value caused the alert to fire or which server it happened on. +
+
Annotations can contain a combination of text and template code.
+ +
+ ); const LinkToDocs = () => ( - - Click{' '} - - here{' '} - - for documentation on how to template annotations and labels. - + } placement={'bottom-start'}> + + Need help? + + ); if (ruleType === RuleFormType.grafana) { return ( - {' '} - Write a summary to help you better manage your alerts. + {` ${annotationsText} `} + ); } if (ruleType === RuleFormType.cloudAlerting) { return ( - {' '} - Select the Namespace and evaluation group for your alert. Write a summary to help you better manage your alerts.{' '} + {`Select the Namespace and evaluation group for your alert. ${annotationsText} `} ); } @@ -44,6 +66,8 @@ function getDescription(ruleType: RuleFormType | undefined) { export function DetailsStep() { const { watch } = useFormContext(); + const styles = useStyles2(getStyles); + const ruleFormType = watch('type'); const dataSourceName = watch('dataSourceName'); const type = watch('type'); @@ -51,8 +75,8 @@ export function DetailsStep() { return ( {(ruleFormType === RuleFormType.cloudRecording || ruleFormType === RuleFormType.cloudAlerting) && dataSourceName && } @@ -61,3 +85,42 @@ export function DetailsStep() { ); } + +const getStyles = (theme: GrafanaTheme2) => ({ + needHelpText: css` + color: ${theme.colors.text.primary}; + font-size: ${theme.typography.size.sm}; + margin-bottom: ${theme.spacing(0.5)}; + cursor: pointer; + text-underline-position: under; + `, + + needHelpTooltip: css` + max-width: 300px; + font-size: ${theme.typography.size.sm}; + margin-left: 5px; + + div { + margin-top: 5px; + margin-bottom: 5px; + } + `, + + tooltipHeader: css` + color: ${theme.colors.text.primary}; + font-weight: bold; + `, + + tooltipLink: css` + color: ${theme.colors.text.link}; + cursor: pointer; + + &:hover { + text-decoration: underline; + } + `, + + underline: css` + text-decoration: underline; + `, +}); diff --git a/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx b/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx index 46fcd00641d..3ff637806c3 100644 --- a/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/RuleEditorSection.tsx @@ -25,7 +25,7 @@ export const RuleEditorSection = ({
- {description &&

{description}

} + {description &&
{description}
} {children}
diff --git a/public/app/features/alerting/unified/utils/constants.ts b/public/app/features/alerting/unified/utils/constants.ts index 1ccc1643407..0e4160965f9 100644 --- a/public/app/features/alerting/unified/utils/constants.ts +++ b/public/app/features/alerting/unified/utils/constants.ts @@ -29,3 +29,18 @@ export const annotationLabels: Record = { [Annotation.panelID]: 'Panel ID', [Annotation.alertId]: 'Alert ID', }; + +export const annotationDescriptions: Record = { + [Annotation.description]: 'Description of what the alert rule does', + [Annotation.summary]: 'Short summary of what happened and why', + [Annotation.runbookURL]: 'Webpage where you keep your runbook for the alert', + [Annotation.dashboardUID]: '', + [Annotation.panelID]: '', + [Annotation.alertId]: '', +}; + +export const defaultAnnotations = [ + { key: Annotation.summary, value: '' }, + { key: Annotation.description, value: '' }, + { key: Annotation.runbookURL, value: '' }, +]; diff --git a/public/app/features/alerting/unified/utils/rule-form.ts b/public/app/features/alerting/unified/utils/rule-form.ts index fd3bdfffe01..47dc2df3d74 100644 --- a/public/app/features/alerting/unified/utils/rule-form.ts +++ b/public/app/features/alerting/unified/utils/rule-form.ts @@ -34,7 +34,7 @@ import { EvalFunction } from '../../state/alertDef'; import { RuleFormType, RuleFormValues } from '../types/rule-form'; import { getRulesAccess } from './access-control'; -import { Annotation } from './constants'; +import { Annotation, defaultAnnotations } from './constants'; import { getDefaultOrFirstCompatibleDataSource, isGrafanaRulesSource } from './datasource'; import { arrayToRecord, recordToArray } from './misc'; import { isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from './rules'; @@ -51,11 +51,7 @@ export const getDefaultFormValues = (): RuleFormValues => { name: '', uid: '', labels: [{ key: '', value: '' }], - annotations: [ - { key: Annotation.summary, value: '' }, - { key: Annotation.description, value: '' }, - { key: Annotation.runbookURL, value: '' }, - ], + annotations: defaultAnnotations, dataSourceName: null, type: canCreateGrafanaRules ? RuleFormType.grafana : canCreateCloudRules ? RuleFormType.cloudAlerting : undefined, // viewers can't create prom alerts group: '', @@ -98,8 +94,35 @@ export function formValuesToRulerRuleDTO(values: RuleFormValues): RulerRuleDTO { throw new Error(`unexpected rule type: ${type}`); } -function listifyLabelsOrAnnotations(item: Labels | Annotations | undefined): Array<{ key: string; value: string }> { - return [...recordToArray(item || {}), { key: '', value: '' }]; +function listifyLabelsOrAnnotations( + item: Labels | Annotations | undefined, + addEmpty: boolean +): Array<{ key: string; value: string }> { + const list = [...recordToArray(item || {})]; + if (addEmpty) { + list.push({ key: '', value: '' }); + } + return list; +} + +//make sure default annotations are always shown in order even if empty +function normalizeDefaultAnnotations(annotations: Array<{ key: string; value: string }>) { + const orderedAnnotations = [...annotations]; + const defaultAnnotationKeys = defaultAnnotations.map((annotation) => annotation.key); + + defaultAnnotationKeys.forEach((defaultAnnotationKey, index) => { + const fieldIndex = orderedAnnotations.findIndex((field) => field.key === defaultAnnotationKey); + + if (fieldIndex === -1) { + //add the default annotation if abstent + const emptyValue = { key: defaultAnnotationKey, value: '' }; + orderedAnnotations.splice(index, 0, emptyValue); + } else if (fieldIndex !== index) { + //move it to the correct position if present + orderedAnnotations.splice(index, 0, orderedAnnotations.splice(fieldIndex, 1)[0]); + } + }); + return orderedAnnotations; } export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): PostableRuleGrafanaRuleDTO { @@ -143,8 +166,8 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF execErrState: ga.exec_err_state, queries: ga.data, condition: ga.condition, - annotations: listifyLabelsOrAnnotations(rule.annotations), - labels: listifyLabelsOrAnnotations(rule.labels), + annotations: normalizeDefaultAnnotations(listifyLabelsOrAnnotations(rule.annotations, false)), + labels: listifyLabelsOrAnnotations(rule.labels, true), folder: { title: namespace, uid: ga.namespace_uid }, isPaused: ga.is_paused, }; @@ -194,8 +217,8 @@ export function alertingRulerRuleToRuleForm( expression: rule.expr, forTime, forTimeUnit, - annotations: listifyLabelsOrAnnotations(rule.annotations), - labels: listifyLabelsOrAnnotations(rule.labels), + annotations: listifyLabelsOrAnnotations(rule.annotations, false), + labels: listifyLabelsOrAnnotations(rule.labels, true), }; } @@ -205,7 +228,7 @@ export function recordingRulerRuleToRuleForm( return { name: rule.record, expression: rule.expr, - labels: listifyLabelsOrAnnotations(rule.labels), + labels: listifyLabelsOrAnnotations(rule.labels, true), }; }