Dashboards: Allow editing provisioned dashboards if AllowUIUpdates is set (#115804)
This commit is contained in:
@@ -294,6 +294,9 @@ type DashboardProvisioning struct {
|
||||
ExternalID string `xorm:"external_id"`
|
||||
CheckSum string
|
||||
Updated int64
|
||||
|
||||
// note: only used when writing metadata to unified storage resources - not saved in legacy table.
|
||||
AllowUIUpdates bool `xorm:"-"`
|
||||
}
|
||||
|
||||
type DeleteDashboardCommand struct {
|
||||
|
||||
@@ -1942,6 +1942,7 @@ func (dr *DashboardServiceImpl) saveProvisionedDashboardThroughK8s(ctx context.C
|
||||
// HOWEVER, maybe OK to leave this for now and "fix" it by using file provisioning for mode 4
|
||||
m.Kind = utils.ManagerKindClassicFP // nolint:staticcheck
|
||||
m.Identity = provisioning.Name
|
||||
m.AllowsEdits = provisioning.AllowUIUpdates
|
||||
s.Path = provisioning.ExternalID
|
||||
s.Checksum = provisioning.CheckSum
|
||||
s.TimestampMillis = time.Unix(provisioning.Updated, 0).UnixMilli()
|
||||
|
||||
@@ -358,6 +358,8 @@ func (fr *FileReader) saveDashboard(ctx context.Context, path string, folderID i
|
||||
Name: fr.Cfg.Name,
|
||||
Updated: resolvedFileInfo.ModTime().Unix(),
|
||||
CheckSum: jsonFile.checkSum,
|
||||
// adds `grafana.app/managerAllowsEdits` to the provisioned dashboards in unified storage. not used if in legacy.
|
||||
AllowUIUpdates: fr.Cfg.AllowUIUpdates,
|
||||
}
|
||||
_, err := fr.dashboardProvisioningService.SaveProvisionedDashboard(ctx, dash, dp)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { GrafanaConfig, locationUtil } from '@grafana/data';
|
||||
import * as folderHooks from 'app/api/clients/folder/v1beta1/hooks';
|
||||
import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { AnnoKeyFolder, AnnoKeyMessage, AnnoReloadOnParamsChange } from 'app/features/apiserver/types';
|
||||
import {
|
||||
AnnoKeyFolder,
|
||||
AnnoKeyManagerAllowsEdits,
|
||||
AnnoKeyManagerKind,
|
||||
AnnoKeyMessage,
|
||||
AnnoKeySourcePath,
|
||||
AnnoReloadOnParamsChange,
|
||||
ManagerKind,
|
||||
} from 'app/features/apiserver/types';
|
||||
import { DashboardDataDTO } from 'app/types/dashboard';
|
||||
|
||||
import { DashboardWithAccessInfo } from './types';
|
||||
@@ -215,6 +223,63 @@ describe('v1 dashboard API', () => {
|
||||
expect(result.meta.reloadOnParamsChange).toBe(true);
|
||||
});
|
||||
|
||||
describe('managed/provisioned dashboards', () => {
|
||||
it('should not mark dashboard as provisioned when manager allows UI edits', async () => {
|
||||
mockGet.mockResolvedValueOnce({
|
||||
...mockDashboardDto,
|
||||
metadata: {
|
||||
...mockDashboardDto.metadata,
|
||||
annotations: {
|
||||
[AnnoKeyManagerKind]: ManagerKind.Terraform,
|
||||
[AnnoKeyManagerAllowsEdits]: 'true',
|
||||
[AnnoKeySourcePath]: 'dashboards/test.json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const api = new K8sDashboardAPI();
|
||||
const result = await api.getDashboardDTO('test');
|
||||
expect(result.meta.provisioned).toBe(false);
|
||||
expect(result.meta.provisionedExternalId).toBe('dashboards/test.json');
|
||||
});
|
||||
|
||||
it('should mark dashboard as provisioned when manager does not allow UI edits', async () => {
|
||||
mockGet.mockResolvedValueOnce({
|
||||
...mockDashboardDto,
|
||||
metadata: {
|
||||
...mockDashboardDto.metadata,
|
||||
annotations: {
|
||||
[AnnoKeyManagerKind]: ManagerKind.Terraform,
|
||||
[AnnoKeySourcePath]: 'dashboards/test.json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const api = new K8sDashboardAPI();
|
||||
const result = await api.getDashboardDTO('test');
|
||||
expect(result.meta.provisioned).toBe(true);
|
||||
expect(result.meta.provisionedExternalId).toBe('dashboards/test.json');
|
||||
});
|
||||
|
||||
it('should not mark repository-managed dashboard as provisioned (locked)', async () => {
|
||||
mockGet.mockResolvedValueOnce({
|
||||
...mockDashboardDto,
|
||||
metadata: {
|
||||
...mockDashboardDto.metadata,
|
||||
annotations: {
|
||||
[AnnoKeyManagerKind]: ManagerKind.Repo,
|
||||
[AnnoKeySourcePath]: 'dashboards/test.json',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const api = new K8sDashboardAPI();
|
||||
const result = await api.getDashboardDTO('test');
|
||||
expect(result.meta.provisioned).toBe(false);
|
||||
expect(result.meta.provisionedExternalId).toBe('dashboards/test.json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('saveDashboard', () => {
|
||||
beforeEach(() => {
|
||||
locationUtil.initialize({
|
||||
|
||||
@@ -164,7 +164,11 @@ export class K8sDashboardAPI implements DashboardAPI<DashboardDTO, Dashboard> {
|
||||
const managerKind = annotations[AnnoKeyManagerKind];
|
||||
|
||||
if (managerKind) {
|
||||
result.meta.provisioned = annotations[AnnoKeyManagerAllowsEdits] === 'true' || managerKind === ManagerKind.Repo;
|
||||
// `meta.provisioned` is used by the save/delete UI to decide if a dashboard is locked
|
||||
// (i.e. it can't be saved from the UI). This should match the legacy behavior where
|
||||
// `allowUiUpdates: true` keeps the dashboard editable/savable.
|
||||
const allowsEdits = annotations[AnnoKeyManagerAllowsEdits] === 'true';
|
||||
result.meta.provisioned = !allowsEdits && managerKind !== ManagerKind.Repo;
|
||||
result.meta.provisionedExternalId = annotations[AnnoKeySourcePath];
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user