import { css } from '@emotion/css'; import classNames from 'classnames'; import React, { useCallback } from 'react'; import { useFormContext } from 'react-hook-form'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2, Field, Input, InputControl, Label, Tooltip, Icon, Stack } from '@grafana/ui'; import { FolderPickerFilter } from 'app/core/components/Select/FolderPicker'; import { contextSrv } from 'app/core/services/context_srv'; import { DashboardSearchHit } from 'app/features/search/types'; import { AccessControlAction } from 'app/types'; import { RuleForm, RuleFormType, RuleFormValues } from '../../types/rule-form'; import AnnotationsField from './AnnotationsField'; import { GroupAndNamespaceFields } from './GroupAndNamespaceFields'; import { RuleEditorSection } from './RuleEditorSection'; import { RuleFolderPicker, Folder } from './RuleFolderPicker'; import { checkForPathSeparator } from './util'; const recordingRuleNameValidationPattern = { message: 'Recording rule name must be valid metric name. It may only contain letters, numbers, and colons. It may not contain whitespace.', value: /^[a-zA-Z_:][a-zA-Z0-9_:]*$/, }; interface DetailsStepProps { initialFolder: RuleForm | null; } export const DetailsStep = ({ initialFolder }: DetailsStepProps) => { const { register, watch, formState: { errors }, } = useFormContext(); const styles = useStyles2(getStyles); const ruleFormType = watch('type'); const dataSourceName = watch('dataSourceName'); const type = watch('type'); const folderFilter = useRuleFolderFilter(initialFolder); return ( { // we use the alert rule name as the "groupname" for Grafana managed alerts, so we can't allow path separators if (ruleFormType === RuleFormType.grafana) { return checkForPathSeparator(value); } return true; }, }, })} /> {(ruleFormType === RuleFormType.cloudRecording || ruleFormType === RuleFormType.cloudAlerting) && dataSourceName && } {ruleFormType === RuleFormType.grafana && (
Folder Each folder has unique folder permission. When you store multiple rules in a folder, the folder access permissions get assigned to the rules.
} > } className={styles.formInput} error={errors.folder?.message} invalid={!!errors.folder?.message} data-testid="folder-picker" > ( )} name="folder" rules={{ required: { value: true, message: 'Please select a folder' }, validate: { pathSeparator: (folder: Folder) => checkForPathSeparator(folder.title), }, }} /> )} {type !== RuleFormType.cloudRecording && }
); }; const useRuleFolderFilter = (existingRuleForm: RuleForm | null) => { const isSearchHitAvailable = useCallback( (hit: DashboardSearchHit) => { const rbacDisabledFallback = contextSrv.hasEditPermissionInFolders; const canCreateRuleInFolder = contextSrv.hasAccessInMetadata( AccessControlAction.AlertingRuleCreate, hit, rbacDisabledFallback ); const canUpdateInCurrentFolder = existingRuleForm && hit.folderId === existingRuleForm.id && contextSrv.hasAccessInMetadata(AccessControlAction.AlertingRuleUpdate, hit, rbacDisabledFallback); return canCreateRuleInFolder || canUpdateInCurrentFolder; }, [existingRuleForm] ); return useCallback( (folderHits) => folderHits.filter(isSearchHitAvailable), [isSearchHitAvailable] ); }; const getStyles = (theme: GrafanaTheme2) => ({ alignBaseline: css` align-items: baseline; margin-bottom: ${theme.spacing(3)}; `, formInput: css` width: 275px; & + & { margin-left: ${theme.spacing(3)}; } `, flexRow: css` display: flex; flex-direction: row; justify-content: flex-start; align-items: flex-end; `, });