Alerting: small rule form ux improvements (#36941)

* dedupe folder option for existing grafana rules

* update test mocks

* change toggle to chevron for expanding error state ui

* fix some strict lint errors
This commit is contained in:
Domas
2021-07-21 18:01:05 +03:00
committed by GitHub
parent 5a0221a8c4
commit cac4b4b443
11 changed files with 37 additions and 26 deletions

View File

@@ -58,7 +58,7 @@ export class FolderPicker extends PureComponent<Props, State> {
}; };
getOptions = async (query: string) => { getOptions = async (query: string) => {
const { rootName, enableReset, initialTitle, permissionLevel, showRoot } = this.props; const { rootName, enableReset, initialTitle, permissionLevel, initialFolderId, showRoot } = this.props;
const params = { const params = {
query, query,
type: 'dash-folder', type: 'dash-folder',
@@ -74,8 +74,13 @@ export class FolderPicker extends PureComponent<Props, State> {
options.unshift({ label: rootName, value: 0 }); options.unshift({ label: rootName, value: 0 });
} }
if (enableReset && query === '' && initialTitle !== '') { if (
options.unshift({ label: initialTitle, value: undefined }); enableReset &&
query === '' &&
initialTitle !== '' &&
!options.find((option) => option.label === initialTitle)
) {
options.unshift({ label: initialTitle, value: initialFolderId });
} }
return options; return options;
@@ -122,9 +127,8 @@ export class FolderPicker extends PureComponent<Props, State> {
folder = options.find((option) => option.value === initialFolderId) || null; folder = options.find((option) => option.value === initialFolderId) || null;
} else if (enableReset && initialTitle) { } else if (enableReset && initialTitle) {
folder = resetFolder; folder = resetFolder;
} else if (initialTitle && initialFolderId === -1) { } else if (initialFolderId) {
// @TODO temporary, we don't know the id for alerting rule folder in some cases folder = options.find((option) => option.id === initialFolderId) || null;
folder = options.find((option) => option.label === initialTitle) || null;
} }
if (!folder && !this.props.allowEmpty) { if (!folder && !this.props.allowEmpty) {

View File

@@ -87,6 +87,7 @@ const mockGrafanaRule = {
grafana_alert: { grafana_alert: {
condition: 'B', condition: 'B',
exec_err_state: GrafanaAlertStateDecision.Alerting, exec_err_state: GrafanaAlertStateDecision.Alerting,
namespace_id: 11,
namespace_uid: 'namespaceuid123', namespace_uid: 'namespaceuid123',
no_data_state: GrafanaAlertStateDecision.NoData, no_data_state: GrafanaAlertStateDecision.NoData,
title: 'Test alert', title: 'Test alert',

View File

@@ -7,14 +7,16 @@ interface Props extends HTMLAttributes<HTMLButtonElement> {
onToggle: (isCollapsed: boolean) => void; onToggle: (isCollapsed: boolean) => void;
size?: IconSize; size?: IconSize;
className?: string; className?: string;
text?: string;
} }
export const CollapseToggle: FC<Props> = ({ isCollapsed, onToggle, className, size = 'xl', ...restOfProps }) => { export const CollapseToggle: FC<Props> = ({ isCollapsed, onToggle, className, text, size = 'xl', ...restOfProps }) => {
const styles = useStyles(getStyles); const styles = useStyles(getStyles);
return ( return (
<button className={cx(styles.expandButton, className)} onClick={() => onToggle(!isCollapsed)} {...restOfProps}> <button className={cx(styles.expandButton, className)} onClick={() => onToggle(!isCollapsed)} {...restOfProps}>
<Icon size={size} name={isCollapsed ? 'angle-right' : 'angle-down'} /> <Icon size={size} name={isCollapsed ? 'angle-right' : 'angle-down'} />
{text}
</button> </button>
); );
}; };
@@ -26,6 +28,9 @@ export const getStyles = () => ({
outline: none !important; outline: none !important;
display: inline-flex;
align-items: center;
svg { svg {
margin-bottom: 0; margin-bottom: 0;
} }

View File

@@ -63,7 +63,7 @@ function useQueryMappers(dataSourceName: string): QueryMappers {
case 'loki': case 'loki':
case 'prometheus': case 'prometheus':
return { return {
mapToValue: (query: PromQuery | LokiQuery) => query.expr, mapToValue: (query: DataQuery) => (query as PromQuery | LokiQuery).expr,
mapToQuery: (existing: DataQuery, value: string | undefined) => ({ ...existing, expr: value }), mapToQuery: (existing: DataQuery, value: string | undefined) => ({ ...existing, expr: value }),
}; };
default: default:

View File

@@ -1,7 +1,7 @@
import React, { FC, useState } from 'react'; import React, { FC, useState } from 'react';
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import { GrafanaTheme, parseDuration, durationToMilliseconds } from '@grafana/data'; import { parseDuration, durationToMilliseconds, GrafanaTheme2 } from '@grafana/data';
import { Field, InlineLabel, Input, InputControl, Switch, useStyles } from '@grafana/ui'; import { Field, InlineLabel, Input, InputControl, useStyles2 } from '@grafana/ui';
import { useFormContext, RegisterOptions } from 'react-hook-form'; import { useFormContext, RegisterOptions } from 'react-hook-form';
import { RuleFormValues } from '../../types/rule-form'; import { RuleFormValues } from '../../types/rule-form';
import { positiveDurationValidationPattern, durationValidationPattern } from '../../utils/time'; import { positiveDurationValidationPattern, durationValidationPattern } from '../../utils/time';
@@ -10,6 +10,7 @@ import { GrafanaAlertStatePicker } from './GrafanaAlertStatePicker';
import { RuleEditorSection } from './RuleEditorSection'; import { RuleEditorSection } from './RuleEditorSection';
import { PreviewRule } from './PreviewRule'; import { PreviewRule } from './PreviewRule';
import { GrafanaConditionEvalWarning } from './GrafanaConditionEvalWarning'; import { GrafanaConditionEvalWarning } from './GrafanaConditionEvalWarning';
import { CollapseToggle } from '../CollapseToggle';
const MIN_TIME_RANGE_STEP_S = 10; // 10 seconds const MIN_TIME_RANGE_STEP_S = 10; // 10 seconds
@@ -43,7 +44,7 @@ const evaluateEveryValidationOptions: RegisterOptions = {
}; };
export const GrafanaConditionsStep: FC = () => { export const GrafanaConditionsStep: FC = () => {
const styles = useStyles(getStyles); const styles = useStyles2(getStyles);
const [showErrorHandling, setShowErrorHandling] = useState(false); const [showErrorHandling, setShowErrorHandling] = useState(false);
const { const {
register, register,
@@ -83,9 +84,12 @@ export const GrafanaConditionsStep: FC = () => {
</div> </div>
</Field> </Field>
<GrafanaConditionEvalWarning /> <GrafanaConditionEvalWarning />
<Field label="Configure no data and error handling" horizontal={true} className={styles.switchField}> <CollapseToggle
<Switch value={showErrorHandling} onChange={() => setShowErrorHandling(!showErrorHandling)} /> isCollapsed={!showErrorHandling}
</Field> onToggle={(collapsed) => setShowErrorHandling(!collapsed)}
text="Configure no data and error handling"
className={styles.collapseToggle}
/>
{showErrorHandling && ( {showErrorHandling && (
<> <>
<Field label="Alert state if no data or all values are null"> <Field label="Alert state if no data or all values are null">
@@ -121,7 +125,7 @@ export const GrafanaConditionsStep: FC = () => {
); );
}; };
const getStyles = (theme: GrafanaTheme) => ({ const getStyles = (theme: GrafanaTheme2) => ({
inlineField: css` inlineField: css`
margin-bottom: 0; margin-bottom: 0;
`, `,
@@ -131,12 +135,7 @@ const getStyles = (theme: GrafanaTheme) => ({
justify-content: flex-start; justify-content: flex-start;
align-items: flex-start; align-items: flex-start;
`, `,
switchField: css` collapseToggle: css`
display: inline-flex; margin: ${theme.spacing(2, 0, 2, -1)};
flex-direction: row-reverse;
margin-top: ${theme.spacing.md};
& > div:first-child {
margin-left: ${theme.spacing.sm};
}
`, `,
}); });

View File

@@ -63,7 +63,7 @@ export const QueryWrapper: FC<Props> = ({
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<QueryEditorRow <QueryEditorRow<DataQuery>
dataSource={dsSettings} dataSource={dsSettings}
onChangeDataSource={!isExpression ? (settings) => onChangeDataSource(settings, index) : undefined} onChangeDataSource={!isExpression ? (settings) => onChangeDataSource(settings, index) : undefined}
id={query.refId} id={query.refId}

View File

@@ -98,7 +98,7 @@ const RulesFilter = () => {
<Label>View as</Label> <Label>View as</Label>
<RadioButtonGroup <RadioButtonGroup
options={ViewOptions} options={ViewOptions}
value={String(queryParams['view'] ?? 'group')} value={String(queryParams['view'] || 'group')}
onChange={handleViewChange} onChange={handleViewChange}
/> />
</div> </div>

View File

@@ -9,7 +9,7 @@ import { isUndefined, omitBy } from 'lodash';
const defaultValueAndType: [string, string] = ['', timeOptions[0].value]; const defaultValueAndType: [string, string] = ['', timeOptions[0].value];
const matchersToArrayFieldMatchers = (matchers: Record<string, string> | undefined, isRegex: boolean): Matcher[] => const matchersToArrayFieldMatchers = (matchers: Record<string, string> | undefined, isRegex: boolean): Matcher[] =>
Object.entries(matchers ?? {}).reduce( Object.entries(matchers ?? {}).reduce<Matcher[]>(
(acc, [name, value]) => [ (acc, [name, value]) => [
...acc, ...acc,
{ {

View File

@@ -78,6 +78,7 @@ describe('alertRuleToQueries', () => {
const grafanaAlert = { const grafanaAlert = {
condition: 'B', condition: 'B',
exec_err_state: GrafanaAlertStateDecision.Alerting, exec_err_state: GrafanaAlertStateDecision.Alerting,
namespace_id: 11,
namespace_uid: 'namespaceuid123', namespace_uid: 'namespaceuid123',
no_data_state: GrafanaAlertStateDecision.NoData, no_data_state: GrafanaAlertStateDecision.NoData,
title: 'Test alert', title: 'Test alert',

View File

@@ -106,7 +106,7 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF
condition: ga.condition, condition: ga.condition,
annotations: listifyLabelsOrAnnotations(rule.annotations), annotations: listifyLabelsOrAnnotations(rule.annotations),
labels: listifyLabelsOrAnnotations(rule.labels), labels: listifyLabelsOrAnnotations(rule.labels),
folder: { title: namespace, id: -1 }, folder: { title: namespace, id: ga.namespace_id },
}; };
} else { } else {
throw new Error('Unexpected type of rule for grafana rules source'); throw new Error('Unexpected type of rule for grafana rules source');

View File

@@ -120,6 +120,7 @@ export interface PostableGrafanaRuleDefinition {
export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition { export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
uid: string; uid: string;
namespace_uid: string; namespace_uid: string;
namespace_id: number;
} }
export interface RulerGrafanaRuleDTO { export interface RulerGrafanaRuleDTO {