SaveProvisionedDashboardForm: Reset dashboard dirty state after provisioned dashboard save (#110571)

* SaveProvisionedDashboardForm: Bugfix when changes are saved, save button still enabled
This commit is contained in:
Yunwen Zheng 2025-09-05 12:06:52 -04:00 committed by GitHub
parent d715bda8af
commit 24107abea3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 45 additions and 21 deletions

View File

@ -2040,21 +2040,6 @@
"count": 2 "count": 2
} }
}, },
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupCondition.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingGroupVisibility.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/dashboard-scene/conditional-rendering/ConditionalRenderingTimeRangeSize.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx": { "public/app/features/dashboard-scene/edit-pane/DashboardEditPaneRenderer.tsx": {
"@typescript-eslint/consistent-type-assertions": { "@typescript-eslint/consistent-type-assertions": {
"count": 1 "count": 1

View File

@ -1276,9 +1276,10 @@ describe('UnifiedDashboardScenePageStateManager', () => {
await loader.loadDashboard({ uid: 'blah-blah', route: DashboardRoutes.Provisioning }); await loader.loadDashboard({ uid: 'blah-blah', route: DashboardRoutes.Provisioning });
expect(loader.state.dashboard).toBeDefined(); expect(loader.state.dashboard).toBeDefined();
expect(loader.state.dashboard!.serializer.initialSaveModel).toEqual( expect(loader.state.dashboard!.serializer.initialSaveModel).toEqual({
v1ProvisionedDashboardResource.resource.dryRun.spec ...v1ProvisionedDashboardResource.resource.dryRun.spec,
); version: v1ProvisionedDashboardResource.resource.dryRun.metadata.generation || 0,
});
}); });
it('should load a provisioned v2 dashboard', async () => { it('should load a provisioned v2 dashboard', async () => {

View File

@ -230,6 +230,12 @@ abstract class DashboardScenePageStateManagerBase<T>
anno[AnnoKeyManagerIdentity] = repo; anno[AnnoKeyManagerIdentity] = repo;
anno[AnnoKeySourcePath] = provisioningPreview.ref ? path + '#' + provisioningPreview.ref : path; anno[AnnoKeySourcePath] = provisioningPreview.ref ? path + '#' + provisioningPreview.ref : path;
// Include version information to align with the current dashboard schema
const specWithVersion = {
...dryRun.spec,
version: dryRun.metadata.generation || 0,
};
return { return {
meta: { meta: {
canStar: false, canStar: false,
@ -247,7 +253,7 @@ abstract class DashboardScenePageStateManagerBase<T>
// lookup info // lookup info
provisioning: provisioningPreview, provisioning: provisioningPreview,
}, },
dashboard: dryRun.spec, dashboard: specWithVersion,
}; };
} }

View File

@ -7,7 +7,7 @@ import { Trans, t } from '@grafana/i18n';
import { getAppEvents, locationService } from '@grafana/runtime'; import { getAppEvents, locationService } from '@grafana/runtime';
import { Dashboard } from '@grafana/schema'; import { Dashboard } from '@grafana/schema';
import { Button, Field, Input, Stack, TextArea } from '@grafana/ui'; import { Button, Field, Input, Stack, TextArea } from '@grafana/ui';
import { RepositoryView } from 'app/api/clients/provisioning/v0alpha1'; import { RepositoryView, Unstructured } from 'app/api/clients/provisioning/v0alpha1';
import kbn from 'app/core/utils/kbn'; import kbn from 'app/core/utils/kbn';
import { Resource } from 'app/features/apiserver/types'; import { Resource } from 'app/features/apiserver/types';
import { SaveDashboardFormCommonOptions } from 'app/features/dashboard-scene/saving/SaveDashboardForm'; import { SaveDashboardFormCommonOptions } from 'app/features/dashboard-scene/saving/SaveDashboardForm';
@ -19,6 +19,7 @@ import {
ProvisionedOperationInfo, ProvisionedOperationInfo,
useProvisionedRequestHandler, useProvisionedRequestHandler,
} from 'app/features/provisioning/hooks/useProvisionedRequestHandler'; } from 'app/features/provisioning/hooks/useProvisionedRequestHandler';
import { SaveDashboardResponseDTO } from 'app/types/dashboard';
import { ProvisionedDashboardFormData } from '../../types/form'; import { ProvisionedDashboardFormData } from '../../types/form';
import { buildResourceBranchRedirectUrl } from '../../utils/redirect'; import { buildResourceBranchRedirectUrl } from '../../utils/redirect';
@ -111,8 +112,13 @@ export function SaveProvisionedDashboardForm({
}; };
const handleDismiss = () => { const handleDismiss = () => {
dashboard.setState({ isDirty: false });
panelEditor?.onDiscard(); panelEditor?.onDiscard();
const model = dashboard.getSaveModel();
const resourceData = request?.data?.resource.dryRun;
const saveResponse = createSaveResponseFromResource(resourceData);
dashboard.saveCompleted(model, saveResponse, defaultValues.folder?.uid);
drawer.onClose(); drawer.onClose();
}; };
@ -292,3 +298,29 @@ function updateURLParams(param: string, value?: string) {
url.searchParams.set(param, value); url.searchParams.set(param, value);
window.history.replaceState({}, '', url); window.history.replaceState({}, '', url);
} }
/**
* Creates a SaveDashboardResponseDTO from a provisioning resource response
* This allows us to use the standard dashboard save completion flow
*/
function createSaveResponseFromResource(resource?: Unstructured): SaveDashboardResponseDTO {
const uid = resource?.metadata?.name;
const title = resource?.spec?.title;
const slug = kbn.slugifyForUrl(title);
return {
uid,
// Use the current dashboard state version to maintain consistency
version: resource?.metadata?.generation,
id: resource?.spec?.id || 0,
status: 'success',
url: locationUtil.assureBaseUrl(
getDashboardUrl({
uid,
slug,
currentQueryParams: '',
})
),
slug,
};
}