Alerting: separate namespace & group inputs for system alerts (#33026)

This commit is contained in:
Domas
2021-04-16 14:57:33 +03:00
committed by GitHub
parent dbe2f1871f
commit 0491fe0a5c
9 changed files with 196 additions and 140 deletions

View File

@@ -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

View File

@@ -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}
/>
);
};

View File

@@ -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;
`;

View File

@@ -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);
}
}}
/>
);
}
};