import { css, cx } from '@emotion/css'; import { flattenDeep, compact } from 'lodash'; import React, { FC, useCallback, useEffect, useMemo, useState } from 'react'; import { FieldArrayMethodProps, useFieldArray, useFormContext } from 'react-hook-form'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Stack } from '@grafana/experimental'; import { Button, Field, InlineLabel, Label, useStyles2, Tooltip, Icon, Input, LoadingPlaceholder } from '@grafana/ui'; import { useDispatch } from 'app/types'; import { RulerRuleGroupDTO } from 'app/types/unified-alerting-dto'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; import { fetchRulerRulesIfNotFetchedYet } from '../../state/actions'; import { RuleFormValues } from '../../types/rule-form'; import AlertLabelDropdown from '../AlertLabelDropdown'; interface Props { className?: string; dataSourceName?: string | null; } const useGetCustomLabels = (dataSourceName: string): { loading: boolean; labelsByKey: Record } => { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchRulerRulesIfNotFetchedYet(dataSourceName)); }, [dispatch, dataSourceName]); const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules); const rulerRequest = rulerRuleRequests[dataSourceName]; const result = rulerRequest?.result || {}; //store all labels in a flat array and remove empty values const labels = compact( flattenDeep( Object.keys(result).map((ruleGroupKey) => result[ruleGroupKey].map((ruleItem: RulerRuleGroupDTO) => ruleItem.rules.map((item) => item.labels)) ) ) ); const labelsByKey: Record = {}; labels.forEach((label: Record) => { Object.entries(label).forEach(([key, value]) => { labelsByKey[key] = [...new Set([...(labelsByKey[key] || []), value])]; }); }); return { loading: rulerRequest?.loading, labelsByKey }; }; function mapLabelsToOptions(items: string[] = []): Array> { return items.map((item) => ({ label: item, value: item })); } const RemoveButton: FC<{ remove: (index?: number | number[] | undefined) => void; className: string; index: number; }> = ({ remove, className, index }) => ( ); const LabelsWithSuggestions: FC<{ dataSourceName: string }> = ({ dataSourceName }) => { const styles = useStyles2(getStyles); const { register, control, watch, formState: { errors }, setValue, } = useFormContext(); const labels = watch('labels'); const { fields, remove, append } = useFieldArray({ control, name: 'labels' }); const { loading, labelsByKey } = useGetCustomLabels(dataSourceName); const [selectedKey, setSelectedKey] = useState(''); const keys = useMemo(() => { return mapLabelsToOptions(Object.keys(labelsByKey)); }, [labelsByKey]); const getValuesForLabel = useCallback( (key: string) => { return mapLabelsToOptions(labelsByKey[key]); }, [labelsByKey] ); const values = useMemo(() => { return getValuesForLabel(selectedKey); }, [selectedKey, getValuesForLabel]); return ( <> {loading && } {!loading && ( <> {fields.map((field, index) => { return (
{ setValue(`labels.${index}.key`, newValue.value); setSelectedKey(newValue.value); }} type="key" /> = { setValue(`labels.${index}.value`, newValue.value); }} onOpenMenu={() => { setSelectedKey(labels[index].key); }} type="value" />
); })} )} ); }; const LabelsWithoutSuggestions: FC = () => { const styles = useStyles2(getStyles); const { register, control, watch, formState: { errors }, } = useFormContext(); const labels = watch('labels'); const { fields, remove, append } = useFieldArray({ control, name: 'labels' }); return ( <> {fields.map((field, index) => { return (
=
); })} ); }; const LabelsField: FC = ({ className, dataSourceName }) => { const styles = useStyles2(getStyles); return (
} > <>
Labels
{dataSourceName && } {!dataSourceName && }
); }; const getStyles = (theme: GrafanaTheme2) => { return { icon: css` margin-right: ${theme.spacing(0.5)}; `, wrapper: css` margin-bottom: ${theme.spacing(4)}; `, flexColumn: css` display: flex; flex-direction: column; `, flexRow: css` display: flex; flex-direction: row; justify-content: flex-start; & + button { margin-left: ${theme.spacing(0.5)}; } `, deleteLabelButton: css` margin-left: ${theme.spacing(0.5)}; align-self: flex-start; `, addLabelButton: css` flex-grow: 0; align-self: flex-start; `, centerAlignRow: css` align-items: baseline; `, equalSign: css` align-self: flex-start; width: 28px; justify-content: center; margin-left: ${theme.spacing(0.5)}; `, labelInput: css` width: 175px; margin-bottom: ${theme.spacing(1)}; & + & { margin-left: ${theme.spacing(1)}; } `, }; }; export default LabelsField;