Alerting: Import UI - Add folder creation & add to new list view (#103417)
* Update styling of import warning * Update logic for picking recording rule target datasource, and default to chosen DS * Add CreateNewFolder component * Reuse folder creation logic inside alert rule form * Update betterer and tweak jsdoc * Add translation to folder selector and tidy up imports * Allow specifying data source UID in query param * Add import button to new list view on appropriate data sources * Fix CI failures * Update translation keys
This commit is contained in:
@@ -1817,11 +1817,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/FolderSelector.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "2"]
|
||||
],
|
||||
"public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "0"],
|
||||
[0, 0, 0, "No untranslated strings in text props. Wrap text with <Trans /> or use t()", "1"],
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Button, Field, Input, Label, Modal, Stack, useStyles2 } from '@grafana/ui';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
import { useNewFolderMutation } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
|
||||
import { Folder } from '../../types/rule-form';
|
||||
|
||||
/**
|
||||
* Provides a button and associated modal for creating a new folder
|
||||
*/
|
||||
export const CreateNewFolder = ({ onCreate }: { onCreate: (folder: Folder) => void }) => {
|
||||
const [isCreatingFolder, setIsCreatingFolder] = useState(false);
|
||||
const handleCreate = (folder: Folder) => {
|
||||
onCreate(folder);
|
||||
setIsCreatingFolder(false);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => setIsCreatingFolder(true)}
|
||||
type="button"
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
variant="secondary"
|
||||
disabled={!contextSrv.hasPermission(AccessControlAction.FoldersCreate)}
|
||||
>
|
||||
<Trans i18nKey="alerting.create-new-folder.new-folder">New folder</Trans>
|
||||
</Button>
|
||||
{isCreatingFolder && <FolderCreationModal onCreate={handleCreate} onClose={() => setIsCreatingFolder(false)} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
function FolderCreationModal({
|
||||
onClose,
|
||||
onCreate,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onCreate: (folder: Folder) => void;
|
||||
}): React.ReactElement {
|
||||
const styles = useStyles2(getStyles);
|
||||
const notifyApp = useAppNotification();
|
||||
const [title, setTitle] = useState('');
|
||||
const [createFolder] = useNewFolderMutation();
|
||||
|
||||
const onSubmit = async () => {
|
||||
const { data, error } = await createFolder({ title });
|
||||
|
||||
if (error) {
|
||||
notifyApp.error('Failed to create folder');
|
||||
} else if (data) {
|
||||
onCreate({ title: data.title, uid: data.uid });
|
||||
notifyApp.success('Folder created');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
className={styles.modal}
|
||||
isOpen
|
||||
title={t('alerting.create-new-folder.title-new-folder', 'New folder')}
|
||||
onDismiss={onClose}
|
||||
onClickBackdrop={onClose}
|
||||
>
|
||||
<Stack direction="column" gap={2}>
|
||||
<Field
|
||||
label={
|
||||
<Label htmlFor="folder">
|
||||
<Trans i18nKey="alerting.create-new-folder.folder.name">Folder name</Trans>
|
||||
</Label>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
data-testid={selectors.components.AlertRules.newFolderNameField}
|
||||
autoFocus={true}
|
||||
id="folderName"
|
||||
placeholder={t('alerting.create-new-folder.placeholder-enter-a-name', 'Enter a name')}
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.currentTarget.value)}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="secondary" type="button" onClick={onClose}>
|
||||
<Trans i18nKey="alerting.create-new-folder.folder.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
disabled={!title}
|
||||
data-testid={selectors.components.AlertRules.newFolderNameCreateButton}
|
||||
>
|
||||
<Trans i18nKey="alerting.create-new-folder.folder.create">Create</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</Stack>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
modal: css({
|
||||
width: `${theme.breakpoints.values.sm}px`,
|
||||
}),
|
||||
});
|
||||
+8
-9
@@ -4,7 +4,7 @@ import { ComponentProps } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { CodeEditor, ConfirmModal, Icon, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { Alert, CodeEditor, ConfirmModal, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
import { stringifyErrorLike } from 'app/features/alerting/unified/utils/misc';
|
||||
@@ -90,15 +90,14 @@ export const ConfirmConversionModal = ({ isOpen, onDismiss }: ModalProps) => {
|
||||
confirmButtonVariant="primary"
|
||||
body={
|
||||
<Stack direction="column" gap={2}>
|
||||
<Stack direction="row" gap={1} alignItems={'self-start'}>
|
||||
<Text color="warning">
|
||||
<Icon name="exclamation-triangle" />
|
||||
<Alert title={t('alerting.to-gma.confirm-modal.title-warning', 'Warning')} severity="warning">
|
||||
<Text variant="body">
|
||||
<Trans i18nKey="alerting.to-gma.confirm-modal.body">
|
||||
If the target folder is not empty, some rules may be overwritten or removed. Are you sure you want to
|
||||
import these alert rules to Grafana-managed rules?
|
||||
</Trans>
|
||||
</Text>
|
||||
<Trans i18nKey="alerting.to-gma.confirm-modal.body">
|
||||
If the target folder is not empty, some rules may be overwritten or removed. Are you sure you want to
|
||||
import these alert rules to Grafana-managed rules?
|
||||
</Trans>
|
||||
</Stack>
|
||||
</Alert>
|
||||
<Text variant="h6">
|
||||
<Trans i18nKey="alerting.to-gma.confirm-modal.summary">
|
||||
These are the list of rules that will be imported:
|
||||
|
||||
+56
-28
@@ -16,12 +16,16 @@ import {
|
||||
Text,
|
||||
} from '@grafana/ui';
|
||||
import { NestedFolderPicker } from 'app/core/components/NestedFolderPicker/NestedFolderPicker';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
import { Trans, t } from 'app/core/internationalization';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
import { useDatasource } from 'app/features/datasources/hooks';
|
||||
|
||||
import { Folder } from '../../types/rule-form';
|
||||
import { DataSourceType } from '../../utils/datasource';
|
||||
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
|
||||
import { AlertingPageWrapper } from '../AlertingPageWrapper';
|
||||
import { CreateNewFolder } from '../create-folder/CreateNewFolder';
|
||||
import { CloudRulesSourcePicker } from '../rule-editor/CloudRulesSourcePicker';
|
||||
|
||||
import { ConfirmConversionModal } from './ConfirmConvertModal';
|
||||
@@ -38,11 +42,22 @@ export interface ImportFormValues {
|
||||
targetDatasourceUID?: string;
|
||||
}
|
||||
|
||||
export const supportedImportTypes: string[] = [DataSourceType.Prometheus, DataSourceType.Loki];
|
||||
|
||||
const ImportFromDSRules = () => {
|
||||
const [queryParams] = useQueryParams();
|
||||
const queryParamSelectedDatasourceUID: string = String(queryParams.datasourceUid) || '';
|
||||
const defaultDataSourceSettings = useDatasource(queryParamSelectedDatasourceUID);
|
||||
// useDatasource gets the default data source as a fallback, so we need to check if it's the right type
|
||||
// before trying to use it
|
||||
const defaultDataSource = supportedImportTypes.includes(defaultDataSourceSettings?.type || '')
|
||||
? defaultDataSourceSettings
|
||||
: undefined;
|
||||
|
||||
const formAPI = useForm<ImportFormValues>({
|
||||
defaultValues: {
|
||||
selectedDatasourceUID: undefined,
|
||||
selectedDatasourceName: '',
|
||||
selectedDatasourceUID: defaultDataSource?.uid,
|
||||
selectedDatasourceName: defaultDataSource?.name,
|
||||
pauseAlertingRules: true,
|
||||
pauseRecordingRules: true,
|
||||
targetFolder: undefined,
|
||||
@@ -58,7 +73,7 @@ const ImportFromDSRules = () => {
|
||||
formState: { errors, isSubmitting },
|
||||
} = formAPI;
|
||||
|
||||
const [optionsShowing, toggleOptions] = useToggle(false);
|
||||
const [optionsShowing, toggleOptions] = useToggle(true);
|
||||
const [targetFolder, selectedDatasourceName] = watch(['targetFolder', 'selectedDatasourceName']);
|
||||
const [showConfirmModal, setShowConfirmModal] = useToggle(false);
|
||||
|
||||
@@ -70,7 +85,7 @@ const ImportFromDSRules = () => {
|
||||
<AlertingPageWrapper
|
||||
navId="alert-list"
|
||||
pageNav={{
|
||||
text: t('alerting.import-to-gma.pageTitle', 'Import alert rules from a datasource to Grafana-managed rules'),
|
||||
text: t('alerting.import-to-gma.pageTitle', 'Import alert rules from a data source to Grafana-managed rules'),
|
||||
}}
|
||||
>
|
||||
<Stack gap={2} direction={'column'}>
|
||||
@@ -92,6 +107,9 @@ const ImportFromDSRules = () => {
|
||||
onChange={(ds: DataSourceInstanceSettings) => {
|
||||
setValue('selectedDatasourceUID', ds.uid);
|
||||
setValue('selectedDatasourceName', ds.name);
|
||||
// If we've chosen a Prometheus data source, we can set the recording rules target data source to the same as the source
|
||||
const targetDataSourceUID = ds.type === DataSourceType.Prometheus ? ds.uid : undefined;
|
||||
setValue('targetDatasourceUID', targetDataSourceUID);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@@ -99,7 +117,7 @@ const ImportFromDSRules = () => {
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: t('alerting.import-to-gma.datasource.required-message', 'Please select a datasource'),
|
||||
message: t('alerting.import-to-gma.datasource.required-message', 'Please select a data source'),
|
||||
},
|
||||
}}
|
||||
control={control}
|
||||
@@ -107,7 +125,7 @@ const ImportFromDSRules = () => {
|
||||
</Field>
|
||||
|
||||
<Collapse
|
||||
label={t('alerting.import-to-gma.optional-settings', 'Optional settings')}
|
||||
label={t('alerting.import-to-gma.additional-settings', 'Additional settings')}
|
||||
isOpen={optionsShowing}
|
||||
onToggle={toggleOptions}
|
||||
collapsible={true}
|
||||
@@ -129,27 +147,34 @@ const ImportFromDSRules = () => {
|
||||
error={errors.selectedDatasourceName?.message}
|
||||
htmlFor="folder-picker"
|
||||
>
|
||||
<Controller
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<Stack width={50}>
|
||||
<NestedFolderPicker
|
||||
showRootFolder={false}
|
||||
invalid={!!errors.targetFolder?.message}
|
||||
{...field}
|
||||
value={targetFolder?.uid}
|
||||
onChange={(uid, title) => {
|
||||
if (uid && title) {
|
||||
setValue('targetFolder', { title, uid });
|
||||
} else {
|
||||
setValue('targetFolder', undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
name="targetFolder"
|
||||
control={control}
|
||||
/>
|
||||
<Stack gap={2}>
|
||||
<Controller
|
||||
render={({ field: { onChange, ref, ...field } }) => (
|
||||
<Stack width={42}>
|
||||
<NestedFolderPicker
|
||||
showRootFolder={false}
|
||||
invalid={!!errors.targetFolder?.message}
|
||||
{...field}
|
||||
value={targetFolder?.uid}
|
||||
onChange={(uid, title) => {
|
||||
if (uid && title) {
|
||||
setValue('targetFolder', { title, uid });
|
||||
} else {
|
||||
setValue('targetFolder', undefined);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
)}
|
||||
name="targetFolder"
|
||||
control={control}
|
||||
/>
|
||||
<CreateNewFolder
|
||||
onCreate={(folder) => {
|
||||
setValue('targetFolder', folder);
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</Field>
|
||||
<NamespaceAndGroupFilter rulesSourceName={selectedDatasourceName || undefined} />
|
||||
</Box>
|
||||
@@ -189,7 +214,7 @@ const ImportFromDSRules = () => {
|
||||
|
||||
<Box marginLeft={1} width={50}>
|
||||
<Field
|
||||
required={false}
|
||||
required
|
||||
id="target-data-source"
|
||||
label={t('alerting.recording-rules.label-target-data-source', 'Target data source')}
|
||||
description={t(
|
||||
@@ -214,6 +239,9 @@ const ImportFromDSRules = () => {
|
||||
)}
|
||||
name="targetDatasourceUID"
|
||||
control={control}
|
||||
rules={{
|
||||
required: { value: true, message: 'Please select a target data source' },
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</Box>
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
import { css } from '@emotion/css';
|
||||
import * as React from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Button, Field, Input, Label, Modal, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { Field, Label, Stack } from '@grafana/ui';
|
||||
import { NestedFolderPicker } from 'app/core/components/NestedFolderPicker/NestedFolderPicker';
|
||||
import { useAppNotification } from 'app/core/copy/appNotification';
|
||||
import { contextSrv } from 'app/core/services/context_srv';
|
||||
import { useNewFolderMutation } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
|
||||
import { AccessControlAction } from 'app/types';
|
||||
import { t } from 'app/core/internationalization';
|
||||
|
||||
import { Trans } from '../../../../../core/internationalization/index';
|
||||
import { Folder, RuleFormValues } from '../../types/rule-form';
|
||||
import { CreateNewFolder } from '../create-folder/CreateNewFolder';
|
||||
|
||||
export function FolderSelector() {
|
||||
const {
|
||||
@@ -26,161 +20,61 @@ export function FolderSelector() {
|
||||
setValue('group', '');
|
||||
}, [setValue]);
|
||||
|
||||
const [isCreatingFolder, setIsCreatingFolder] = useState(false);
|
||||
const folder = watch('folder');
|
||||
|
||||
const onOpenFolderCreationModal = () => setIsCreatingFolder(true);
|
||||
|
||||
const handleFolderCreation = (folder: Folder) => {
|
||||
resetGroup();
|
||||
setValue('folder', folder);
|
||||
setIsCreatingFolder(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack alignItems="center">
|
||||
{
|
||||
<Field
|
||||
label={
|
||||
<Label htmlFor="folder" description={'Select a folder to store your rule in.'}>
|
||||
<Trans i18nKey="alerting.rule-form.folder.label">Folder</Trans>
|
||||
</Label>
|
||||
}
|
||||
error={errors.folder?.message}
|
||||
data-testid="folder-picker"
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
{(!isCreatingFolder && (
|
||||
<>
|
||||
<Controller
|
||||
render={({ field: { ref, ...field } }) => (
|
||||
<div style={{ width: 420 }}>
|
||||
<NestedFolderPicker
|
||||
showRootFolder={false}
|
||||
invalid={!!errors.folder?.message}
|
||||
{...field}
|
||||
value={folder?.uid}
|
||||
onChange={(uid, title) => {
|
||||
if (uid && title) {
|
||||
setValue('folder', { title, uid });
|
||||
} else {
|
||||
setValue('folder', undefined);
|
||||
}
|
||||
<Stack alignItems="center">
|
||||
{
|
||||
<Field
|
||||
label={
|
||||
<Label
|
||||
htmlFor="folder"
|
||||
description={t(
|
||||
'alerting.folder-selector.description-select-folder',
|
||||
'Select a folder to store your rule in.'
|
||||
)}
|
||||
>
|
||||
<Trans i18nKey="alerting.rule-form.folder.label">Folder</Trans>
|
||||
</Label>
|
||||
}
|
||||
error={errors.folder?.message}
|
||||
data-testid="folder-picker"
|
||||
>
|
||||
<Stack direction="row" alignItems="center">
|
||||
<Controller
|
||||
render={({ field: { ref, ...field } }) => (
|
||||
<div style={{ width: 420 }}>
|
||||
<NestedFolderPicker
|
||||
showRootFolder={false}
|
||||
invalid={!!errors.folder?.message}
|
||||
{...field}
|
||||
value={folder?.uid}
|
||||
onChange={(uid, title) => {
|
||||
if (uid && title) {
|
||||
setValue('folder', { title, uid });
|
||||
} else {
|
||||
setValue('folder', undefined);
|
||||
}
|
||||
|
||||
resetGroup();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
name="folder"
|
||||
rules={{
|
||||
required: { value: true, message: 'Select a folder' },
|
||||
resetGroup();
|
||||
}}
|
||||
/>
|
||||
<Text color="secondary">
|
||||
<Trans i18nKey="alerting.rule-form.folder.new-folder-or">or</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
onClick={onOpenFolderCreationModal}
|
||||
type="button"
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
variant="secondary"
|
||||
disabled={!contextSrv.hasPermission(AccessControlAction.FoldersCreate)}
|
||||
data-testid={selectors.components.AlertRules.newFolderButton}
|
||||
>
|
||||
<Trans i18nKey="alerting.rule-form.folder.new-folder">New folder</Trans>
|
||||
</Button>
|
||||
</>
|
||||
)) || (
|
||||
<div>
|
||||
<Trans i18nKey="alerting.rule-form.folder.creating-new-folder">Creating new folder</Trans>
|
||||
{'...'}
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
</Field>
|
||||
}
|
||||
</Stack>
|
||||
|
||||
{isCreatingFolder && (
|
||||
<FolderCreationModal onCreate={handleFolderCreation} onClose={() => setIsCreatingFolder(false)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function FolderCreationModal({
|
||||
onClose,
|
||||
onCreate,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
onCreate: (folder: Folder) => void;
|
||||
}): React.ReactElement {
|
||||
const styles = useStyles2(getStyles);
|
||||
const notifyApp = useAppNotification();
|
||||
const [title, setTitle] = useState('');
|
||||
const [createFolder] = useNewFolderMutation();
|
||||
|
||||
const onSubmit = async () => {
|
||||
const { data, error } = await createFolder({ title });
|
||||
|
||||
if (error) {
|
||||
notifyApp.error('Failed to create folder');
|
||||
} else if (data) {
|
||||
onCreate({ title: data.title, uid: data.uid });
|
||||
notifyApp.success('Folder created');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal className={styles.modal} isOpen={true} title={'New folder'} onDismiss={onClose} onClickBackdrop={onClose}>
|
||||
<Stack direction="column" gap={2}>
|
||||
<Text color="secondary">
|
||||
<Trans i18nKey="alerting.rule-form.folder.create-folder">
|
||||
Create a new folder to store your alert rule in.
|
||||
</Trans>
|
||||
</Text>
|
||||
|
||||
<form onSubmit={onSubmit}>
|
||||
<Field
|
||||
label={
|
||||
<Label htmlFor="folder">
|
||||
<Trans i18nKey="alerting.rule-form.folder.name">Folder name</Trans>
|
||||
</Label>
|
||||
}
|
||||
>
|
||||
<Input
|
||||
data-testid={selectors.components.AlertRules.newFolderNameField}
|
||||
autoFocus={true}
|
||||
id="folderName"
|
||||
placeholder="Enter a name"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.currentTarget.value)}
|
||||
name="folder"
|
||||
rules={{
|
||||
required: { value: true, message: 'Select a folder' },
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
|
||||
<Modal.ButtonRow>
|
||||
<Button variant="secondary" type="button" onClick={onClose}>
|
||||
<Trans i18nKey="alerting.rule-form.folder.cancel">Cancel</Trans>
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!title}
|
||||
data-testid={selectors.components.AlertRules.newFolderNameCreateButton}
|
||||
>
|
||||
<Trans i18nKey="alerting.rule-form.folder.create">Create</Trans>
|
||||
</Button>
|
||||
</Modal.ButtonRow>
|
||||
</form>
|
||||
</Stack>
|
||||
</Modal>
|
||||
<CreateNewFolder onCreate={handleFolderCreation} />
|
||||
</Stack>
|
||||
</Field>
|
||||
}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
modal: css({
|
||||
width: `${theme.breakpoints.values.sm}px`,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -10,6 +10,8 @@ import { RulesSourceApplication } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { Spacer } from '../../components/Spacer';
|
||||
import { WithReturnButton } from '../../components/WithReturnButton';
|
||||
import { supportedImportTypes } from '../../components/import-to-gma/ImportFromDSRules';
|
||||
import { useRulesSourcesWithRuler } from '../../hooks/useRuleSourcesWithRuler';
|
||||
import { isAdmin } from '../../utils/misc';
|
||||
|
||||
import { DataSourceIcon } from './Namespace';
|
||||
@@ -34,6 +36,12 @@ export const DataSourceSection = ({
|
||||
description = null,
|
||||
}: DataSourceSectionProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const { rulesSourcesWithRuler } = useRulesSourcesWithRuler();
|
||||
|
||||
const showImportLink =
|
||||
uid !== GrafanaRulesSourceSymbol &&
|
||||
rulesSourcesWithRuler.some(({ uid: dsUid, type }) => dsUid === uid && supportedImportTypes.includes(type));
|
||||
|
||||
const [isCollapsed, toggleCollapsed] = useToggle(false);
|
||||
const configureLink = (() => {
|
||||
if (uid === GrafanaRulesSourceSymbol) {
|
||||
@@ -70,6 +78,16 @@ export const DataSourceSection = ({
|
||||
</>
|
||||
)}
|
||||
<Spacer />
|
||||
{showImportLink && (
|
||||
<LinkButton
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
href={`/alerting/import-datasource-managed-rules?datasourceUid=${String(uid)}`}
|
||||
icon="arrow-up"
|
||||
>
|
||||
<Trans i18nKey="alerting.data-source-section.import-to-grafana">Import to Grafana rules</Trans>
|
||||
</LinkButton>
|
||||
)}
|
||||
{configureLink && (
|
||||
<WithReturnButton
|
||||
title={t('alerting.rule-list.return-button.title', 'Alert rules')}
|
||||
|
||||
@@ -468,10 +468,23 @@
|
||||
"label": "Contact point"
|
||||
},
|
||||
"copy-to-clipboard": "Copy \"{{label}}\" to clipboard",
|
||||
"create-new-folder": {
|
||||
"folder": {
|
||||
"cancel": "Cancel",
|
||||
"create": "Create",
|
||||
"name": "Folder name"
|
||||
},
|
||||
"new-folder": "New folder",
|
||||
"placeholder-enter-a-name": "Enter a name",
|
||||
"title-new-folder": "New folder"
|
||||
},
|
||||
"dag": {
|
||||
"missing-reference": "Expression \"{{source}}\" failed to run because \"{{target}}\" is missing or also failed.",
|
||||
"self-reference": "You can't link an expression to itself"
|
||||
},
|
||||
"data-source-section": {
|
||||
"import-to-grafana": "Import to Grafana rules"
|
||||
},
|
||||
"delete-rule-modal": {
|
||||
"title": "Delete rule",
|
||||
"with-soft-delete": "Are you sure you want to delete this rule? This rule will be recoverable from the Recently deleted page by a user with an admin role.",
|
||||
@@ -521,6 +534,9 @@
|
||||
"one-format": "Download the file or copy the contents to clipboard"
|
||||
}
|
||||
},
|
||||
"folder-selector": {
|
||||
"description-select-folder": "Select a folder to store your rule in."
|
||||
},
|
||||
"folderAndGroup": {
|
||||
"evaluation": {
|
||||
"modal": {
|
||||
@@ -587,6 +603,7 @@
|
||||
},
|
||||
"import-to-gma": {
|
||||
"action-button": "Import",
|
||||
"additional-settings": "Additional settings",
|
||||
"alert-rules": "Alert rules",
|
||||
"confirm-modal": {
|
||||
"confirm": "Yes, import",
|
||||
@@ -594,7 +611,7 @@
|
||||
},
|
||||
"datasource": {
|
||||
"label": "Data source",
|
||||
"required-message": "Please select a datasource"
|
||||
"required-message": "Please select a data source"
|
||||
},
|
||||
"error": "Failed to import alert rules: {{error}}",
|
||||
"group": {
|
||||
@@ -606,8 +623,7 @@
|
||||
"description": "Type to search for an existing namespace",
|
||||
"label": "Namespace"
|
||||
},
|
||||
"optional-settings": "Optional settings",
|
||||
"pageTitle": "Import alert rules from a datasource to Grafana-managed rules",
|
||||
"pageTitle": "Import alert rules from a data source to Grafana-managed rules",
|
||||
"pause": {
|
||||
"label": "Pause imported alerting rules"
|
||||
},
|
||||
@@ -818,14 +834,7 @@
|
||||
"validation": "Pending period must be greater than or equal to the evaluation interval."
|
||||
},
|
||||
"folder": {
|
||||
"cancel": "Cancel",
|
||||
"create": "Create",
|
||||
"create-folder": "Create a new folder to store your alert rule in.",
|
||||
"creating-new-folder": "Creating new folder",
|
||||
"label": "Folder",
|
||||
"name": "Folder name",
|
||||
"new-folder": "New folder",
|
||||
"new-folder-or": "or"
|
||||
"label": "Folder"
|
||||
},
|
||||
"folder-and-labels": "Organize your alert rule with a folder and set of labels.",
|
||||
"folders": {
|
||||
@@ -988,7 +997,8 @@
|
||||
"to-gma": {
|
||||
"confirm-modal": {
|
||||
"body": "If the target folder is not empty, some rules may be overwritten or removed. Are you sure you want to import these alert rules to Grafana-managed rules?",
|
||||
"summary": "These are the list of rules that will be imported:"
|
||||
"summary": "These are the list of rules that will be imported:",
|
||||
"title-warning": "Warning"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user