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