From d21b9561e4ac6fcaaa9f88ba5e6d8e6c2e885522 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Tue, 28 Jan 2025 15:12:22 +0200 Subject: [PATCH] POC/Provisioning: Provisioned dashboard save refactor (#99505) * refactor defaults * Only close the modal for direct workflow * Store prOpened state * Update betterer --- .betterer.results | 38 +++---- .../saving/SaveDashboardDrawer.tsx | 2 +- .../SaveProvisionedDashboard.tsx | 99 +++++++------------ .../saving/provisioned/defaults.ts | 13 +++ .../saving/provisioned/hooks.ts | 49 +++++++++ .../scene/NavToolbarActions.tsx | 2 +- 6 files changed, 118 insertions(+), 85 deletions(-) rename public/app/features/dashboard-scene/saving/{ => provisioned}/SaveProvisionedDashboard.tsx (74%) create mode 100644 public/app/features/dashboard-scene/saving/provisioned/defaults.ts create mode 100644 public/app/features/dashboard-scene/saving/provisioned/hooks.ts diff --git a/.betterer.results b/.betterer.results index 2a7e8579a04..3ccaf72d1af 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3387,7 +3387,25 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "16"], [0, 0, 0, "No untranslated strings. Wrap text with ", "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 ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "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 or use t()", "0"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "1"], [0, 0, 0, "No untranslated strings in text props. Wrap text with or use t()", "2"], @@ -3411,24 +3429,6 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "20"], [0, 0, 0, "No untranslated strings. Wrap text with ", "21"] ], - "public/app/features/dashboard-scene/saving/SaveProvisionedDashboardForm.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "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 or use t()", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] diff --git a/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx b/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx index 9a51e5394e2..1fb1e8b3876 100644 --- a/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx +++ b/public/app/features/dashboard-scene/saving/SaveDashboardDrawer.tsx @@ -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; diff --git a/public/app/features/dashboard-scene/saving/SaveProvisionedDashboard.tsx b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx similarity index 74% rename from public/app/features/dashboard-scene/saving/SaveProvisionedDashboard.tsx rename to public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx index 0acc5f398da..2d25c667ec2 100644 --- a/public/app/features/dashboard-scene/saving/SaveProvisionedDashboard.tsx +++ b/public/app/features/dashboard-scene/saving/provisioned/SaveProvisionedDashboard.tsx @@ -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({ - defaultValues: { - ...defaultValues, - title: defaultTitle, - description: defaultDescription, - workflow: WorkflowOption.PullRequest, - }, - }); + } = useForm({ 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 (
- {isGitHub && workflow === WorkflowOption.PullRequest && !isDirty && ( + {prOpened && href && ( { 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 ; + return ; }} /> @@ -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'; diff --git a/public/app/features/dashboard-scene/saving/provisioned/defaults.ts b/public/app/features/dashboard-scene/saving/provisioned/defaults.ts new file mode 100644 index 00000000000..7014ffbdcd1 --- /dev/null +++ b/public/app/features/dashboard-scene/saving/provisioned/defaults.ts @@ -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 }, + ]; +} diff --git a/public/app/features/dashboard-scene/saving/provisioned/hooks.ts b/public/app/features/dashboard-scene/saving/provisioned/hooks.ts new file mode 100644 index 00000000000..f2f061c8610 --- /dev/null +++ b/public/app/features/dashboard-scene/saving/provisioned/hooks.ts @@ -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', + }; +} diff --git a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx index 430e39bb43b..e59cef6fe30 100644 --- a/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx +++ b/public/app/features/dashboard-scene/scene/NavToolbarActions.tsx @@ -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 (