Dashboards: Allow dashboards with same name in different folders (#70378)

* Dashboards: Allow dashboards with same name in different folders

Co-authored-by: Tobias Skarhed <tobias.skarhed@gmail.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
Co-authored-by: joshhunt <josh@trtr.co>

* fix

---------

Co-authored-by: Tobias Skarhed <tobias.skarhed@gmail.com>
Co-authored-by: Ashley Harrison <ashley.harrison@grafana.com>
This commit is contained in:
Josh Hunt 2023-06-26 12:49:50 +01:00 committed by GitHub
parent 9e0f6ceb57
commit e50cf55649
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 20 additions and 32 deletions

View File

@ -2710,11 +2710,6 @@ exports[`better eslint`] = {
"public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx:5381": [ "public/app/features/manage-dashboards/components/ImportDashboardLibraryPanelsList.tsx:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"] [0, 0, 0, "Do not use any type assertions.", "0"]
], ],
"public/app/features/manage-dashboards/services/ValidationSrv.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"]
],
"public/app/features/manage-dashboards/state/actions.test.ts:5381": [ "public/app/features/manage-dashboards/state/actions.test.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"] [0, 0, 0, "Unexpected any. Specify a different type.", "0"]
], ],

View File

@ -52,8 +52,9 @@ export const SaveDashboardAsForm = ({ dashboard, isNew, onSubmit, onCancel, onSu
if (dashboardName && dashboardName === getFormValues().$folder.title?.trim()) { if (dashboardName && dashboardName === getFormValues().$folder.title?.trim()) {
return 'Dashboard name cannot be the same as folder name'; return 'Dashboard name cannot be the same as folder name';
} }
try { try {
await validationSrv.validateNewDashboardName(getFormValues().$folder.uid, dashboardName); await validationSrv.validateNewDashboardName(getFormValues().$folder.uid ?? 'general', dashboardName);
return true; return true;
} catch (e) { } catch (e) {
return e instanceof Error ? e.message : 'Dashboard name is invalid'; return e instanceof Error ? e.message : 'Dashboard name is invalid';

View File

@ -1,9 +1,4 @@
import { backendSrv } from 'app/core/services/backend_srv'; import { getGrafanaSearcher } from 'app/features/search/service';
const hitTypes = {
FOLDER: 'dash-folder',
DASHBOARD: 'dash-db',
};
class ValidationError extends Error { class ValidationError extends Error {
type: string; type: string;
@ -17,15 +12,19 @@ class ValidationError extends Error {
export class ValidationSrv { export class ValidationSrv {
rootName = 'general'; rootName = 'general';
validateNewDashboardName(folderUid: any, name: string) { validateNewDashboardName(folderUID: string, name: string) {
return this.validate(folderUid, name, 'A dashboard or a folder with the same name already exists'); return this.validate(folderUID, name, 'A dashboard or a folder with the same name already exists');
} }
validateNewFolderName(name?: string) { validateNewFolderName(name?: string) {
return this.validate(0, name, 'A folder or dashboard in the general folder with the same name already exists'); return this.validate(
this.rootName,
name,
'A folder or dashboard in the general folder with the same name already exists'
);
} }
private async validate(folderId: any, name: string | undefined, existingErrorMessage: string) { private async validate(folderUID: string, name: string | undefined, existingErrorMessage: string) {
name = (name || '').trim(); name = (name || '').trim();
const nameLowerCased = name.toLowerCase(); const nameLowerCased = name.toLowerCase();
@ -33,27 +32,20 @@ export class ValidationSrv {
throw new ValidationError('REQUIRED', 'Name is required'); throw new ValidationError('REQUIRED', 'Name is required');
} }
if (folderId === 0 && nameLowerCased === this.rootName) { if (nameLowerCased === this.rootName) {
throw new ValidationError('EXISTING', 'This is a reserved name and cannot be used for a folder.'); throw new ValidationError('EXISTING', 'This is a reserved name and cannot be used for a folder.');
} }
const promises = []; const searcher = getGrafanaSearcher();
promises.push(backendSrv.search({ type: hitTypes.FOLDER, folderIds: [folderId], query: name }));
promises.push(backendSrv.search({ type: hitTypes.DASHBOARD, folderIds: [folderId], query: name }));
const res = await Promise.all(promises); const dashboardResults = await searcher.search({
let hits: any[] = []; kind: ['dashboard'],
query: name,
location: folderUID || 'general',
});
if (res.length > 0 && res[0].length > 0) { for (const result of dashboardResults.view) {
hits = res[0]; if (nameLowerCased === result.name.toLowerCase()) {
}
if (res.length > 1 && res[1].length > 0) {
hits = hits.concat(res[1]);
}
for (const hit of hits) {
if (nameLowerCased === hit.title.toLowerCase()) {
throw new ValidationError('EXISTING', existingErrorMessage); throw new ValidationError('EXISTING', existingErrorMessage);
} }
} }