Alerting: Adds support for editing group details for Grafana managed rules (#53120)

This commit is contained in:
Gilles De Mey
2022-08-04 17:39:28 +02:00
committed by GitHub
parent c65b4c732f
commit ca2b97b095
10 changed files with 104 additions and 39 deletions

View File

@@ -60,6 +60,7 @@ export const CloudRules: FC<Props> = ({ namespaces, expandAll }) => {
key={`${getRulesSourceUid(namespace.rulesSource)}-${namespace.name}-${group.name}`}
namespace={namespace}
expandAll={expandAll}
viewMode={'grouped'}
/>
);
})}

View File

@@ -2,15 +2,18 @@ import { css } from '@emotion/css';
import React, { useEffect, useMemo } from 'react';
import { useDispatch } from 'react-redux';
import { isValidGoDuration } from '@grafana/data';
import { Modal, Button, Form, Field, Input, useStyles2 } from '@grafana/ui';
import { useCleanup } from 'app/core/hooks/useCleanup';
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import { updateLotexNamespaceAndGroupAction } from '../../state/actions';
import { checkEvaluationIntervalGlobalLimit } from '../../utils/config';
import { getRulesSourceName } from '../../utils/datasource';
import { initialAsyncRequestState } from '../../utils/redux';
import { durationValidationPattern } from '../../utils/time';
import { EvaluationIntervalLimitExceeded } from '../InvalidIntervalWarning';
interface Props {
namespace: CombinedRuleNamespace;
@@ -71,7 +74,7 @@ export function EditCloudGroupModal(props: Props): React.ReactElement {
onClickBackdrop={onClose}
>
<Form defaultValues={defaultValues} onSubmit={onSubmit} key={JSON.stringify(defaultValues)}>
{({ register, errors, formState: { isDirty } }) => (
{({ register, errors, formState: { isDirty }, watch }) => (
<>
<Field label="Namespace" invalid={!!errors.namespaceName} error={errors.namespaceName?.message}>
<Input
@@ -99,9 +102,25 @@ export function EditCloudGroupModal(props: Props): React.ReactElement {
placeholder="1m"
{...register('groupInterval', {
pattern: durationValidationPattern,
validate: (input) => {
const validDuration = isValidGoDuration(input);
if (!validDuration) {
return 'Invalid duration. Valid example: 1m (Available units: h, m, s)';
}
const limitExceeded = !checkEvaluationIntervalGlobalLimit(input).exceedsLimit;
if (limitExceeded) {
return true;
}
return false;
},
})}
/>
</Field>
{checkEvaluationIntervalGlobalLimit(watch('groupInterval')).exceedsLimit && (
<EvaluationIntervalLimitExceeded />
)}
<Modal.ButtonRow>
<Button variant="secondary" type="button" disabled={loading} onClick={onClose} fill="outline">

View File

@@ -49,7 +49,13 @@ export const GrafanaRules: FC<Props> = ({ namespaces, expandAll }) => {
</div>
{pageItems.map(({ group, namespace }) => (
<RulesGroup group={group} key={`${namespace.name}-${group.name}`} namespace={namespace} expandAll={expandAll} />
<RulesGroup
group={group}
key={`${namespace.name}-${group.name}`}
namespace={namespace}
expandAll={expandAll}
viewMode={wantsGroupedView ? 'grouped' : 'list'}
/>
))}
{namespacesFormat?.length === 0 && <p>No rules found.</p>}
<Pagination

View File

@@ -45,7 +45,7 @@ describe('Rules group tests', () => {
function renderRulesGroup(namespace: CombinedRuleNamespace, group: CombinedRuleGroup) {
return render(
<Provider store={store}>
<RulesGroup group={group} namespace={namespace} expandAll={false} />
<RulesGroup group={group} namespace={namespace} expandAll={false} viewMode={'grouped'} />
</Provider>
);
}

View File

@@ -4,9 +4,7 @@ import React, { FC, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime';
import { Badge, ConfirmModal, HorizontalGroup, Icon, Spinner, Tooltip, useStyles2 } from '@grafana/ui';
import kbn from 'app/core/utils/kbn';
import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting';
import { useFolder } from '../../hooks/useFolder';
@@ -14,22 +12,26 @@ import { useHasRuler } from '../../hooks/useHasRuler';
import { deleteRulesGroupAction } from '../../state/actions';
import { useRulesAccess } from '../../utils/accessControlHooks';
import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource';
import { makeFolderLink } from '../../utils/misc';
import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules';
import { CollapseToggle } from '../CollapseToggle';
import { RuleLocation } from '../RuleLocation';
import { ActionIcon } from './ActionIcon';
import { EditCloudGroupModal } from './EditCloudGroupModal';
import { EditCloudGroupModal } from './EditRuleGroupModal';
import { RuleStats } from './RuleStats';
import { RulesTable } from './RulesTable';
type ViewMode = 'grouped' | 'list';
interface Props {
namespace: CombinedRuleNamespace;
group: CombinedRuleGroup;
expandAll: boolean;
viewMode: ViewMode;
}
export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }) => {
export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll, viewMode }) => {
const { rulesSource } = namespace;
const dispatch = useDispatch();
const styles = useStyles2(getStyles);
@@ -54,6 +56,15 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
hasRuler(rulesSource) && rulerRulesLoaded(rulesSource) && !group.rules.find((rule) => !!rule.rulerRule);
const isFederated = isFederatedRuleGroup(group);
// check if group has provisioned items
const isProvisioned = group.rules.some((rule) => {
return isGrafanaRulerRule(rule.rulerRule) && rule.rulerRule.grafana_alert.provenance;
});
// check what view mode we are in
const isListView = viewMode === 'list';
const isGroupView = viewMode === 'grouped';
const deleteGroup = () => {
dispatch(deleteRulesGroupAction(namespace, group));
setIsDeletingGroup(false);
@@ -71,20 +82,34 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
);
} else if (rulesSource === GRAFANA_RULES_SOURCE_NAME) {
if (folderUID) {
const baseUrl = `${config.appSubUrl}/dashboards/f/${folderUID}/${kbn.slugifyForUrl(namespace.name)}`;
const baseUrl = makeFolderLink(folderUID);
if (folder?.canSave) {
actionIcons.push(
<ActionIcon
aria-label="edit folder"
key="edit"
icon="pen"
tooltip="edit folder"
to={baseUrl + '/settings'}
target="__blank"
/>
);
if (isGroupView && !isProvisioned) {
actionIcons.push(
<ActionIcon
aria-label="edit rule group"
data-testid="edit-group"
key="edit"
icon="pen"
tooltip="edit rule group"
onClick={() => setIsEditingGroup(true)}
/>
);
}
if (isListView) {
actionIcons.push(
<ActionIcon
aria-label="go to folder"
key="goto"
icon="folder-open"
tooltip="go to folder"
to={baseUrl}
target="__blank"
/>
);
}
}
if (folder?.canAdmin) {
if (folder?.canAdmin && isListView) {
actionIcons.push(
<ActionIcon
aria-label="manage permissions"
@@ -124,8 +149,7 @@ export const RulesGroup: FC<Props> = React.memo(({ group, namespace, expandAll }
}
// ungrouped rules are rules that are in the "default" group name
const isUngrouped = group.name === 'default';
const groupName = isUngrouped ? (
const groupName = isListView ? (
<RuleLocation namespace={namespace.name} />
) : (
<RuleLocation namespace={namespace.name} group={group.name} />