POC/Provisioning: Provisioned dashboard save refactor (#99505)

* refactor defaults

* Only close the modal for direct workflow

* Store prOpened state

* Update betterer
This commit is contained in:
Alex Khomenko authored and GitHub committed 2025-01-28 15:12:22 +02:00
1 parent 78555be89d
commit d21b9561e4
6 files changed
+118 -85

No files matched your search

+19 -19
View File
@@ -3387,7 +3387,25 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "16"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "17"]
],
"public/app/features/dashboard-scene/saving/SaveProvisionedDashboard.tsx:5381": [
"public/app/features/dashboard-scene/saving/SaveProvisionedDashboardForm.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
],
"public/app/features/dashboard-scene/saving/getDashboardChanges.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
],
"public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.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"],
@@ -3411,24 +3429,6 @@ exports[`better eslint`] = {
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "20"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "21"]
],
"public/app/features/dashboard-scene/saving/SaveProvisionedDashboardForm.tsx:5381": [
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "7"]
],
"public/app/features/dashboard-scene/saving/getDashboardChanges.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"],
[0, 0, 0, "Do not use any type assertions.", "4"],
[0, 0, 0, "Unexpected any. Specify a different type.", "5"]
],
"public/app/features/dashboard-scene/saving/shared.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. Wrap text with <Trans />", "1"]
@@ -9,8 +9,8 @@ import { DashboardScene } from '../scene/DashboardScene';
import { SaveDashboardAsForm } from './SaveDashboardAsForm';
import { SaveDashboardForm } from './SaveDashboardForm';
import { SaveProvisionedDashboard } from './SaveProvisionedDashboard';
import { SaveProvisionedDashboardForm } from './SaveProvisionedDashboardForm';
import { SaveProvisionedDashboard } from './provisioned/SaveProvisionedDashboard';
interface SaveDashboardDrawerState extends SceneObjectState {
dashboardRef: SceneObjectRef<DashboardScene>;
@@ -1,23 +1,24 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { AppEvents } from '@grafana/data';
import { getAppEvents } from '@grafana/runtime';
import { Alert, Button, Field, Icon, Input, RadioButtonGroup, Stack, TextArea, TextLink } from '@grafana/ui';
import { FolderPicker } from 'app/core/components/Select/FolderPicker';
import { AnnoKeyRepoName, AnnoKeyRepoPath } from 'app/features/apiserver/types';
import { DashboardMeta } from 'app/types';
import { AnnoKeyRepoName } from 'app/features/apiserver/types';
import { validationSrv } from 'app/features/manage-dashboards/services/ValidationSrv';
import { Repository } from 'app/features/provisioning/api';
import { useCreateOrUpdateRepositoryFile, usePullRequestParam } from 'app/features/provisioning/hooks';
import { WorkflowOption } from 'app/features/provisioning/types';
import { createPRLink, validateBranchName } from 'app/features/provisioning/utils/git';
import { validationSrv } from '../../manage-dashboards/services/ValidationSrv';
import { Repository, RepositorySpec } from '../../provisioning/api';
import { useCreateOrUpdateRepositoryFile, useFolderRepository, usePullRequestParam } from '../../provisioning/hooks';
import { WorkflowOption } from '../../provisioning/types';
import { createPRLink, validateBranchName } from '../../provisioning/utils/git';
import { DashboardScene } from '../scene/DashboardScene';
import { DashboardScene } from '../../scene/DashboardScene';
import { SaveDashboardDrawer } from '../SaveDashboardDrawer';
import { SaveDashboardFormCommonOptions } from '../SaveDashboardForm';
import { DashboardChangeInfo } from '../shared';
import { SaveDashboardDrawer } from './SaveDashboardDrawer';
import { SaveDashboardFormCommonOptions } from './SaveDashboardForm';
import { DashboardChangeInfo } from './shared';
import { getDefaultWorkflow, getWorkflowOptions } from './defaults';
import { useDefaultValues } from './hooks';
type FormData = {
ref?: string;
@@ -27,41 +28,12 @@ type FormData = {
workflow?: WorkflowOption;
title: string;
description: string;
folder: { uid?: string; title?: string };
};
function getDefaultValues(meta: DashboardMeta, repositoryName = '') {
const anno = meta.k8s?.annotations;
const timestamp = Date.now();
const ref = `dashboard/${timestamp}`;
const pathName = meta.slug || `new-dashboard-${timestamp}`;
let path = anno?.[AnnoKeyRepoPath] ?? `${pathName}.json`;
const repo = anno?.[AnnoKeyRepoName] ?? repositoryName;
const idx = path.indexOf('#');
if (idx > 0) {
path = path.substring(0, idx);
}
return {
ref,
path,
repo,
comment: '',
folder: { uid: meta.folderUid, title: '' },
folder: {
uid?: string;
title?: string;
};
}
const getDefaultWorkflow = (config?: RepositorySpec) => {
return config?.github?.branchWorkflow ? WorkflowOption.PullRequest : WorkflowOption.Direct;
};
function getWorkflowOptions(branch = 'main') {
return [
{ label: `Commit to ${branch}`, value: WorkflowOption.Direct },
{ label: 'Create pull request', value: WorkflowOption.PullRequest },
];
}
export interface Props {
dashboard: DashboardScene;
drawer: SaveDashboardDrawer;
@@ -69,15 +41,16 @@ export interface Props {
}
export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Props) {
// Saving as a new provisioned dashboard
const { saveProvisioned } = drawer.useState();
const { meta, title: defaultTitle, description: defaultDescription } = dashboard.useState();
const prURL = usePullRequestParam();
const isNew = !meta.k8s?.annotations?.[AnnoKeyRepoPath];
const folderRepository = useFolderRepository(meta.folderUid);
const repositoryConfig = folderRepository?.spec;
const repo = folderRepository?.metadata?.name;
const defaultValues = getDefaultValues(meta, repo);
const [prOpened, setPrOpened] = useState(false);
const {
values: defaultValues,
isNew,
isGitHub,
repositoryConfig,
} = useDefaultValues({ meta, defaultTitle, defaultDescription });
const [action, request] = useCreateOrUpdateRepositoryFile(saveProvisioned || isNew ? undefined : defaultValues.path);
const {
register,
@@ -86,18 +59,11 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
formState: { errors },
control,
setValue,
} = useForm<FormData>({
defaultValues: {
...defaultValues,
title: defaultTitle,
description: defaultDescription,
workflow: WorkflowOption.PullRequest,
},
});
} = useForm<FormData>({ defaultValues });
const [title, ref, workflow, comment] = watch(['title', 'ref', 'workflow', 'comment']);
const isGitHub = repositoryConfig?.type === 'github';
const href = createPRLink(repositoryConfig, title, ref, comment);
const { isDirty } = dashboard.state;
const href = createPRLink(repositoryConfig, title, ref, comment);
useEffect(() => {
const appEvents = getAppEvents();
@@ -107,13 +73,18 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
payload: ['Dashboard saved'],
});
dashboard.setState({ isDirty: false });
if (workflow === WorkflowOption.Direct) {
dashboard.closeModal();
} else {
setPrOpened(true);
}
} else if (request.isError) {
appEvents.publish({
type: AppEvents.alertError.name,
payload: ['Error saving dashboard', request.error],
});
}
}, [request.isSuccess, request.isError, request.error, dashboard]);
}, [request.isSuccess, request.isError, request.error, dashboard, workflow]);
useEffect(() => {
setValue('workflow', getDefaultWorkflow(repositoryConfig));
@@ -141,7 +112,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
return (
<form onSubmit={handleSubmit(doSave)}>
<Stack direction="column" gap={2}>
{isGitHub && workflow === WorkflowOption.PullRequest && !isDirty && (
{prOpened && href && (
<Alert
severity="success"
title="Branch successfully created"
@@ -153,6 +124,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
}
onRemove={() => {
window.open(href, '_blank');
dashboard.closeModal();
}}
>
You can now open a pull request in Github.
@@ -222,7 +194,7 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Prop
control={control}
name={'workflow'}
render={({ field: { ref, ...field } }) => {
return <RadioButtonGroup {...field} options={getWorkflowOptions(repositoryConfig.github?.branch)} />;
return <RadioButtonGroup {...field} options={getWorkflowOptions(repositoryConfig?.github?.branch)} />;
}}
/>
</Field>
@@ -272,7 +244,6 @@ const BranchValidationError = () => {
</>
);
};
async function validateDashboardName(title: string, formValues: FormData) {
if (title === formValues.folder.title?.trim()) {
return 'Dashboard name cannot be the same as folder name';
@@ -0,0 +1,13 @@
import { RepositorySpec } from 'app/features/provisioning/api';
import { WorkflowOption } from 'app/features/provisioning/types';
export function getDefaultWorkflow(config?: RepositorySpec) {
return config?.github?.branchWorkflow ? WorkflowOption.PullRequest : WorkflowOption.Direct;
}
export function getWorkflowOptions(branch = 'main') {
return [
{ label: `Commit to ${branch}`, value: WorkflowOption.Direct },
{ label: 'Create pull request', value: WorkflowOption.PullRequest },
];
}
@@ -0,0 +1,49 @@
import { AnnoKeyRepoName, AnnoKeyRepoPath } from 'app/features/apiserver/types';
import { useFolderRepository } from 'app/features/provisioning/hooks';
import { DashboardMeta } from 'app/types';
import { getDefaultWorkflow } from './defaults';
interface UseDefaultValuesParams {
meta: DashboardMeta;
defaultTitle: string;
defaultDescription?: string;
}
function generatePath(timestamp: number, pathFromAnnotation?: string, slug?: string) {
if (pathFromAnnotation) {
const hashIndex = pathFromAnnotation.indexOf('#');
return hashIndex > 0 ? pathFromAnnotation.substring(0, hashIndex) : pathFromAnnotation;
}
const pathSlug = slug || `new-dashboard-${timestamp}`;
return `${pathSlug}.json`;
}
export function useDefaultValues({ meta, defaultTitle, defaultDescription }: UseDefaultValuesParams) {
const folderRepository = useFolderRepository(meta.folderUid);
const timestamp = Date.now();
const annotations = meta.k8s?.annotations;
const annoPath = annotations?.[AnnoKeyRepoPath];
const repositoryConfig = folderRepository?.spec;
const repositoryName = folderRepository?.metadata?.name ?? '';
return {
values: {
ref: `dashboard/${timestamp}`,
path: generatePath(timestamp, annoPath, meta.slug),
repo: annotations?.[AnnoKeyRepoName] ?? repositoryName,
comment: '',
folder: {
uid: meta.folderUid,
title: '',
},
title: defaultTitle,
description: defaultDescription ?? '',
workflow: getDefaultWorkflow(repositoryConfig),
},
isNew: !annoPath,
repositoryConfig,
isGitHub: repositoryConfig?.type === 'github',
};
}
@@ -614,7 +614,7 @@ export function ToolbarActions({ dashboard }: Props) {
}
// If we only can save as copy
if (canSaveAs && !meta.canSave && !meta.canMakeEditable) {
if (canSaveAs && !meta.canSave && !meta.canMakeEditable && !isProvisionedNG) {
return (
<Button
onClick={() => {