SaveProvisionedDashboard: Provisioned dashboard "Save as copy" flow (#114435)

* SaveProvisionedDashboard: Save as copy flow

* add copy tags toggle
This commit is contained in:
Yunwen Zheng
2025-12-10 14:01:51 -05:00
committed by GitHub
parent f368139802
commit b3f98d4cc3
7 changed files with 53 additions and 15 deletions
@@ -102,7 +102,14 @@ function SaveDashboardDrawerComponent({ model }: SceneComponentProps<SaveDashboa
}
if (isProvisionedNG) {
return <SaveProvisionedDashboard dashboard={dashboard} changeInfo={changeInfo} drawer={model} />;
return (
<SaveProvisionedDashboard
dashboard={dashboard}
changeInfo={changeInfo}
drawer={model}
saveAsCopy={saveAsCopy}
/>
);
}
if (saveAsCopy || changeInfo.isNew) {
@@ -835,7 +835,7 @@ export class DashboardScene extends SceneObjectBase<DashboardSceneState> impleme
kind: 'Dashboard',
metadata: {
...meta.k8s,
name: meta.uid ?? meta.k8s?.name,
name: options.isNew ? undefined : (meta.uid ?? meta.k8s?.name),
generateName: options.isNew ? 'd' : undefined,
},
spec,
@@ -178,7 +178,7 @@ export class V1DashboardSerializer
uid: '',
title: options.title || '',
description: options.description || undefined,
tags: options.isNew || options.copyTags ? saveModel.tags : [],
tags: options.copyTags === false ? [] : saveModel.tags,
};
}
@@ -390,7 +390,7 @@ export class V2DashboardSerializer
...saveModel,
title: options.title || '',
description: options.description || '',
tags: options.isNew || options.copyTags ? saveModel.tags : [],
tags: options.copyTags === false ? [] : saveModel.tags,
};
}
@@ -10,10 +10,14 @@ export interface SaveProvisionedDashboardProps {
dashboard: DashboardScene;
drawer: SaveDashboardDrawer;
changeInfo: DashboardChangeInfo;
saveAsCopy?: boolean;
}
export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: SaveProvisionedDashboardProps) {
const { isNew, defaultValues, workflowOptions, readOnly, repository } = useProvisionedDashboardData(dashboard);
export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard, saveAsCopy }: SaveProvisionedDashboardProps) {
const { isNew, defaultValues, workflowOptions, readOnly, repository } = useProvisionedDashboardData(
dashboard,
saveAsCopy
);
if (!defaultValues) {
return null;
@@ -24,11 +28,12 @@ export function SaveProvisionedDashboard({ drawer, changeInfo, dashboard }: Save
dashboard={dashboard}
drawer={drawer}
changeInfo={changeInfo}
isNew={isNew}
isNew={isNew || !!saveAsCopy}
defaultValues={defaultValues}
repository={repository}
workflowOptions={workflowOptions}
readOnly={readOnly}
saveAsCopy={saveAsCopy}
/>
);
}
@@ -6,7 +6,7 @@ import { AppEvents, locationUtil } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import { getAppEvents, locationService, reportInteraction } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema';
import { Button, Field, Input, Stack, TextArea } from '@grafana/ui';
import { Button, Field, Input, Stack, TextArea, Switch } from '@grafana/ui';
import { RepositoryView, Unstructured } from 'app/api/clients/provisioning/v0alpha1';
import kbn from 'app/core/utils/kbn';
import { Resource } from 'app/features/apiserver/types';
@@ -47,6 +47,7 @@ export function SaveProvisionedDashboardForm({
workflowOptions,
readOnly,
repository,
saveAsCopy,
}: Props) {
const navigate = useNavigate();
const appEvents = getAppEvents();
@@ -166,7 +167,15 @@ export function SaveProvisionedDashboardForm({
});
// Submit handler for saving the form data
const handleFormSubmit = async ({ title, description, repo, path, comment, ref }: ProvisionedDashboardFormData) => {
const handleFormSubmit = async ({
title,
description,
repo,
path,
comment,
ref,
copyTags,
}: ProvisionedDashboardFormData) => {
// Validate required fields
if (!repo || !path) {
console.error('Missing required fields for saving:', { repo, path });
@@ -185,7 +194,8 @@ export function SaveProvisionedDashboardForm({
isNew,
title,
description,
copyTags: true,
copyTags,
saveAsCopy,
});
reportInteraction('grafana_provisioning_dashboard_save_submitted', {
@@ -287,6 +297,12 @@ export function SaveProvisionedDashboardForm({
isNew={isNew}
/>
{saveAsCopy && (
<Field noMargin label={t('dashboard-scene.save-dashboard-as-form.label-copy-tags', 'Copy tags')}>
<Switch {...register('copyTags')} />
</Field>
)}
<Stack gap={2}>
<Button variant="secondary" onClick={drawer.onClose} fill="outline">
<Trans i18nKey="dashboard-scene.save-provisioned-dashboard-form.cancel">Cancel</Trans>
@@ -18,9 +18,16 @@ interface UseDefaultValuesParams {
defaultTitle: string;
defaultDescription?: string;
loadedFromRef?: string;
saveAsCopy?: boolean;
}
export function useDefaultValues({ meta, defaultTitle, defaultDescription, loadedFromRef }: UseDefaultValuesParams) {
export function useDefaultValues({
meta,
defaultTitle,
defaultDescription,
loadedFromRef,
saveAsCopy,
}: UseDefaultValuesParams) {
const annotations = meta.k8s?.annotations;
const managerKind = annotations?.[AnnoKeyManagerKind];
const managerIdentity = annotations?.[AnnoKeyManagerIdentity];
@@ -35,8 +42,8 @@ export function useDefaultValues({ meta, defaultTitle, defaultDescription, loade
const dashboardPath = generatePath({
timestamp,
pathFromAnnotation: sourcePath,
slug: meta.slug,
pathFromAnnotation: saveAsCopy ? undefined : sourcePath,
slug: saveAsCopy ? undefined : meta.slug,
folderPath,
});
@@ -56,9 +63,10 @@ export function useDefaultValues({ meta, defaultTitle, defaultDescription, loade
uid: meta.folderUid,
title: '',
},
title: defaultTitle,
title: saveAsCopy ? `${defaultTitle} Copy` : defaultTitle,
description: defaultDescription ?? '',
workflow: getDefaultWorkflow(repository, loadedFromRef),
copyTags: saveAsCopy ? false : true,
},
isNew: !meta.k8s?.name,
repository,
@@ -82,7 +90,7 @@ export interface ProvisionedDashboardData {
* It retrieves default values, repository information, and workflow options based on the current dashboard state.
*/
export function useProvisionedDashboardData(dashboard: DashboardScene): ProvisionedDashboardData {
export function useProvisionedDashboardData(dashboard: DashboardScene, saveAsCopy?: boolean): ProvisionedDashboardData {
const { meta, title: defaultTitle, description: defaultDescription } = dashboard.useState();
const [params] = useUrlParams();
const [isLoading, setIsLoading] = useState(false);
@@ -93,6 +101,7 @@ export function useProvisionedDashboardData(dashboard: DashboardScene): Provisio
defaultTitle,
defaultDescription,
loadedFromRef,
saveAsCopy,
});
if (!defaultValuesResult) {
@@ -15,4 +15,5 @@ export interface ProvisionedDashboardFormData extends BaseProvisionedFormData {
uid?: string;
title?: string;
};
copyTags?: boolean;
}