mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: separate namespace & group inputs for system alerts (#33026)
This commit is contained in:
@@ -7,9 +7,9 @@ import { RuleEditorSection } from './RuleEditorSection';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { RuleFormType, RuleFormValues } from '../../types/rule-form';
|
||||
import { DataSourcePicker, DataSourcePickerProps } from '@grafana/runtime';
|
||||
import { RuleGroupPicker } from '../RuleGroupPicker';
|
||||
import { useRulesSourcesWithRuler } from '../../hooks/useRuleSourcesWithRuler';
|
||||
import { RuleFolderPicker } from './RuleFolderPicker';
|
||||
import { GroupAndNamespaceFields } from './GroupAndNamespaceFields';
|
||||
|
||||
const alertTypeOptions: SelectableValue[] = [
|
||||
{
|
||||
@@ -123,27 +123,8 @@ export const AlertTypeStep: FC<Props> = ({ editingExistingRule }) => {
|
||||
</Field>
|
||||
)}
|
||||
</div>
|
||||
{ruleFormType === RuleFormType.system && (
|
||||
<Field
|
||||
label="Group"
|
||||
className={styles.formInput}
|
||||
error={errors.location?.message}
|
||||
invalid={!!errors.location?.message}
|
||||
>
|
||||
{dataSourceName ? (
|
||||
<InputControl
|
||||
as={RuleGroupPicker}
|
||||
name="location"
|
||||
control={control}
|
||||
dataSourceName={dataSourceName}
|
||||
rules={{
|
||||
required: { value: true, message: 'Please select a group' },
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Select placeholder="Select a data source first" onChange={() => {}} disabled={true} />
|
||||
)}
|
||||
</Field>
|
||||
{ruleFormType === RuleFormType.system && dataSourceName && (
|
||||
<GroupAndNamespaceFields dataSourceName={dataSourceName} />
|
||||
)}
|
||||
{ruleFormType === RuleFormType.threshold && (
|
||||
<Field
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { Input, Select } from '@grafana/ui';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import React, { FC, useMemo } from 'react';
|
||||
import { SelectWithAdd } from './SelectWIthAdd';
|
||||
|
||||
enum AnnotationOptions {
|
||||
description = 'Description',
|
||||
@@ -18,47 +18,21 @@ interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const AnnotationKeyInput: FC<Props> = ({ value, onChange, existingKeys, width, className }) => {
|
||||
const isCustomByDefault = !!value && !Object.keys(AnnotationOptions).includes(value); // custom by default if value does not match any of available options
|
||||
const [isCustom, setIsCustom] = useState(isCustomByDefault);
|
||||
|
||||
export const AnnotationKeyInput: FC<Props> = ({ value, existingKeys, ...rest }) => {
|
||||
const annotationOptions = useMemo(
|
||||
(): SelectableValue[] => [
|
||||
...Object.entries(AnnotationOptions)
|
||||
(): SelectableValue[] =>
|
||||
Object.entries(AnnotationOptions)
|
||||
.filter(([optKey]) => !existingKeys.includes(optKey)) // remove keys already taken in other annotations
|
||||
.map(([key, value]) => ({ value: key, label: value })),
|
||||
{ value: '__add__', label: '+ Custom name' },
|
||||
],
|
||||
[existingKeys]
|
||||
);
|
||||
|
||||
if (isCustom) {
|
||||
return (
|
||||
<Input
|
||||
width={width}
|
||||
autoFocus={true}
|
||||
value={value || ''}
|
||||
placeholder="key"
|
||||
className={className}
|
||||
onChange={(e) => onChange((e.target as HTMLInputElement).value)}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Select
|
||||
width={width}
|
||||
options={annotationOptions}
|
||||
value={value}
|
||||
className={className}
|
||||
onChange={(val: SelectableValue) => {
|
||||
const value = val?.value;
|
||||
if (value === '__add__') {
|
||||
setIsCustom(true);
|
||||
} else {
|
||||
onChange(value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<SelectWithAdd
|
||||
value={value}
|
||||
options={annotationOptions}
|
||||
custom={!!value && !Object.keys(AnnotationOptions).includes(value)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user