mirror of https://github.com/grafana/grafana
Alerting: separate namespace & group inputs for system alerts (#33026)
parent
dbe2f1871f
commit
0491fe0a5c
@ -1,64 +0,0 @@ |
||||
import { Cascader, CascaderOption } from '@grafana/ui'; |
||||
import React, { FC, useEffect, useMemo } from 'react'; |
||||
import { useDispatch } from 'react-redux'; |
||||
import { useUnifiedAlertingSelector } from '../hooks/useUnifiedAlertingSelector'; |
||||
import { fetchRulerRulesAction } from '../state/actions'; |
||||
|
||||
interface RuleGroupValue { |
||||
namespace: string; |
||||
group: string; |
||||
} |
||||
|
||||
interface Props { |
||||
value?: RuleGroupValue; |
||||
onChange: (value: RuleGroupValue) => void; |
||||
dataSourceName: string; |
||||
} |
||||
|
||||
const stringifyValue = ({ namespace, group }: RuleGroupValue) => namespace + '|||' + group; |
||||
const parseValue = (value: string): RuleGroupValue => { |
||||
const [namespace, group] = value.split('|||'); |
||||
return { namespace, group }; |
||||
}; |
||||
|
||||
export const RuleGroupPicker: FC<Props> = ({ value, onChange, dataSourceName }) => { |
||||
const rulerRequests = useUnifiedAlertingSelector((state) => state.rulerRules); |
||||
const dispatch = useDispatch(); |
||||
useEffect(() => { |
||||
dispatch(fetchRulerRulesAction(dataSourceName)); |
||||
}, [dataSourceName, dispatch]); |
||||
|
||||
const rulesConfig = rulerRequests[dataSourceName]?.result; |
||||
|
||||
const options = useMemo((): CascaderOption[] => { |
||||
if (rulesConfig) { |
||||
return Object.entries(rulesConfig).map(([namespace, group]) => { |
||||
return { |
||||
label: namespace, |
||||
value: namespace, |
||||
items: group.map(({ name }) => { |
||||
return { label: name, value: stringifyValue({ namespace, group: name }) }; |
||||
}), |
||||
}; |
||||
}); |
||||
} |
||||
return []; |
||||
}, [rulesConfig]); |
||||
|
||||
// @TODO replace cascader with separate dropdowns
|
||||
return ( |
||||
<Cascader |
||||
placeholder="Select a rule group" |
||||
onSelect={(value) => { |
||||
console.log('selected', value); |
||||
onChange(parseValue(value)); |
||||
}} |
||||
initialValue={value ? stringifyValue(value) : undefined} |
||||
displayAllSelectedLevels={true} |
||||
separator=" > " |
||||
key={JSON.stringify(options)} |
||||
options={options} |
||||
changeOnSelect={false} |
||||
/> |
||||
); |
||||
}; |
@ -0,0 +1,85 @@ |
||||
import React, { FC, useEffect, useMemo, useState } from 'react'; |
||||
import { useDispatch } from 'react-redux'; |
||||
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; |
||||
import { fetchRulerRulesAction } from '../../state/actions'; |
||||
import { RuleFormValues } from '../../types/rule-form'; |
||||
import { useFormContext } from 'react-hook-form'; |
||||
import { SelectableValue } from '@grafana/data'; |
||||
import { SelectWithAdd } from './SelectWIthAdd'; |
||||
import { Field, InputControl } from '@grafana/ui'; |
||||
import { css } from '@emotion/css'; |
||||
|
||||
interface Props { |
||||
dataSourceName: string; |
||||
} |
||||
|
||||
export const GroupAndNamespaceFields: FC<Props> = ({ dataSourceName }) => { |
||||
const { control, watch, errors, setValue } = useFormContext<RuleFormValues>(); |
||||
|
||||
const [customGroup, setCustomGroup] = useState(false); |
||||
|
||||
const rulerRequests = useUnifiedAlertingSelector((state) => state.rulerRules); |
||||
const dispatch = useDispatch(); |
||||
useEffect(() => { |
||||
dispatch(fetchRulerRulesAction(dataSourceName)); |
||||
}, [dataSourceName, dispatch]); |
||||
|
||||
const rulesConfig = rulerRequests[dataSourceName]?.result; |
||||
|
||||
const namespace = watch('namespace'); |
||||
|
||||
const namespaceOptions = useMemo( |
||||
(): Array<SelectableValue<string>> => |
||||
rulesConfig ? Object.keys(rulesConfig).map((namespace) => ({ label: namespace, value: namespace })) : [], |
||||
[rulesConfig] |
||||
); |
||||
|
||||
const groupOptions = useMemo( |
||||
(): Array<SelectableValue<string>> => |
||||
(namespace && rulesConfig?.[namespace]?.map((group) => ({ label: group.name, value: group.name }))) || [], |
||||
[namespace, rulesConfig] |
||||
); |
||||
|
||||
return ( |
||||
<> |
||||
<Field label="Namespace" error={errors.namespace?.message} invalid={!!errors.namespace?.message}> |
||||
<InputControl |
||||
as={SelectWithAdd} |
||||
className={inputStyle} |
||||
name="namespace" |
||||
options={namespaceOptions} |
||||
control={control} |
||||
width={42} |
||||
rules={{ |
||||
required: { value: true, message: 'Required.' }, |
||||
}} |
||||
onChange={(values) => { |
||||
setValue('group', ''); //reset if namespace changes
|
||||
return values[0]; |
||||
}} |
||||
onCustomChange={(custom: boolean) => { |
||||
custom && setCustomGroup(true); |
||||
}} |
||||
/> |
||||
</Field> |
||||
<Field label="Group" error={errors.group?.message} invalid={!!errors.group?.message}> |
||||
<InputControl |
||||
as={SelectWithAdd} |
||||
name="group" |
||||
className={inputStyle} |
||||
options={groupOptions} |
||||
width={42} |
||||
custom={customGroup} |
||||
control={control} |
||||
rules={{ |
||||
required: { value: true, message: 'Required.' }, |
||||
}} |
||||
/> |
||||
</Field> |
||||
</> |
||||
); |
||||
}; |
||||
|
||||
const inputStyle = css` |
||||
width: 330px; |
||||
`;
|
@ -0,0 +1,79 @@ |
||||
import { SelectableValue } from '@grafana/data'; |
||||
import { Input, Select } from '@grafana/ui'; |
||||
import React, { FC, useEffect, useMemo, useState } from 'react'; |
||||
|
||||
interface Props { |
||||
onChange: (value: string) => void; |
||||
options: Array<SelectableValue<string>>; |
||||
value?: string; |
||||
addLabel?: string; |
||||
className?: string; |
||||
placeholder?: string; |
||||
custom?: boolean; |
||||
onCustomChange?: (custom: boolean) => void; |
||||
width?: number; |
||||
disabled?: boolean; |
||||
} |
||||
|
||||
export const SelectWithAdd: FC<Props> = ({ |
||||
value, |
||||
onChange, |
||||
options, |
||||
className, |
||||
placeholder, |
||||
width, |
||||
custom, |
||||
onCustomChange, |
||||
disabled = false, |
||||
addLabel = '+ Add new', |
||||
}) => { |
||||
const [isCustom, setIsCustom] = useState(custom); |
||||
|
||||
useEffect(() => { |
||||
if (custom) { |
||||
setIsCustom(custom); |
||||
} |
||||
}, [custom]); |
||||
|
||||
const _options = useMemo((): Array<SelectableValue<string>> => [...options, { value: '__add__', label: addLabel }], [ |
||||
options, |
||||
addLabel, |
||||
]); |
||||
|
||||
if (isCustom) { |
||||
return ( |
||||
<Input |
||||
width={width} |
||||
autoFocus={!custom} |
||||
value={value || ''} |
||||
placeholder={placeholder} |
||||
className={className} |
||||
disabled={disabled} |
||||
onChange={(e) => onChange((e.target as HTMLInputElement).value)} |
||||
/> |
||||
); |
||||
} else { |
||||
return ( |
||||
<Select |
||||
width={width} |
||||
options={_options} |
||||
value={value} |
||||
className={className} |
||||
placeholder={placeholder} |
||||
disabled={disabled} |
||||
onChange={(val: SelectableValue) => { |
||||
const value = val?.value; |
||||
if (value === '__add__') { |
||||
setIsCustom(true); |
||||
if (onCustomChange) { |
||||
onCustomChange(true); |
||||
} |
||||
onChange(''); |
||||
} else { |
||||
onChange(value); |
||||
} |
||||
}} |
||||
/> |
||||
); |
||||
} |
||||
}; |
Loading…
Reference in new issue