From 4063ae37a4842dd3ca9da0deaf98cbd1b89f3e85 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Mon, 4 Jun 2018 21:29:14 +0300 Subject: [PATCH 1/6] dashboard: import to folder --- pkg/api/dtos/plugins.go | 1 + pkg/api/plugins.go | 1 + pkg/plugins/dashboard_importer.go | 4 +++- pkg/plugins/dashboards.go | 1 + .../dashboard/dashboard_import_ctrl.ts | 19 ++++++++++++++++++- .../dashboard/partials/dashboard_import.html | 12 ++++++++++++ 6 files changed, 36 insertions(+), 2 deletions(-) diff --git a/pkg/api/dtos/plugins.go b/pkg/api/dtos/plugins.go index f4281f877b3..78a611c5eeb 100644 --- a/pkg/api/dtos/plugins.go +++ b/pkg/api/dtos/plugins.go @@ -57,4 +57,5 @@ type ImportDashboardCommand struct { Overwrite bool `json:"overwrite"` Dashboard *simplejson.Json `json:"dashboard"` Inputs []plugins.ImportDashboardInput `json:"inputs"` + FolderId int64 `json:"folderId"` } diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index f757f2b9adc..4b44009ab8c 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -174,6 +174,7 @@ func ImportDashboard(c *m.ReqContext, apiCmd dtos.ImportDashboardCommand) Respon Path: apiCmd.Path, Inputs: apiCmd.Inputs, Overwrite: apiCmd.Overwrite, + FolderId: apiCmd.FolderId, Dashboard: apiCmd.Dashboard, } diff --git a/pkg/plugins/dashboard_importer.go b/pkg/plugins/dashboard_importer.go index 1364fded987..9b319358780 100644 --- a/pkg/plugins/dashboard_importer.go +++ b/pkg/plugins/dashboard_importer.go @@ -16,6 +16,7 @@ type ImportDashboardCommand struct { Path string Inputs []ImportDashboardInput Overwrite bool + FolderId int64 OrgId int64 User *m.SignedInUser @@ -70,7 +71,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error { UserId: cmd.User.UserId, Overwrite: cmd.Overwrite, PluginId: cmd.PluginId, - FolderId: dashboard.FolderId, + FolderId: cmd.FolderId, } dto := &dashboards.SaveDashboardDTO{ @@ -91,6 +92,7 @@ func ImportDashboard(cmd *ImportDashboardCommand) error { Title: savedDash.Title, Path: cmd.Path, Revision: savedDash.Data.Get("revision").MustInt64(1), + FolderId: savedDash.FolderId, ImportedUri: "db/" + savedDash.Slug, ImportedUrl: savedDash.GetUrl(), ImportedRevision: dashboard.Data.Get("revision").MustInt64(1), diff --git a/pkg/plugins/dashboards.go b/pkg/plugins/dashboards.go index d15bcdd6db5..500d97e38ca 100644 --- a/pkg/plugins/dashboards.go +++ b/pkg/plugins/dashboards.go @@ -17,6 +17,7 @@ type PluginDashboardInfoDTO struct { ImportedUrl string `json:"importedUrl"` Slug string `json:"slug"` DashboardId int64 `json:"dashboardId"` + FolderId int64 `json:"folderId"` ImportedRevision int64 `json:"importedRevision"` Revision int64 `json:"revision"` Description string `json:"description"` diff --git a/public/app/features/dashboard/dashboard_import_ctrl.ts b/public/app/features/dashboard/dashboard_import_ctrl.ts index fe61d3f7a55..7764bb4815f 100644 --- a/public/app/features/dashboard/dashboard_import_ctrl.ts +++ b/public/app/features/dashboard/dashboard_import_ctrl.ts @@ -21,6 +21,8 @@ export class DashboardImportCtrl { uidValidationError: any; autoGenerateUid: boolean; autoGenerateUidValue: string; + folderId: number; + isValidFolderSelection: boolean; /** @ngInject */ constructor(private backendSrv, private validationSrv, navModelSrv, private $location, $routeParams) { @@ -31,6 +33,7 @@ export class DashboardImportCtrl { this.uidExists = false; this.autoGenerateUid = true; this.autoGenerateUidValue = 'auto-generated'; + this.folderId = 0; // check gnetId in url if ($routeParams.gnetId) { @@ -102,7 +105,7 @@ export class DashboardImportCtrl { this.nameExists = false; this.validationSrv - .validateNewDashboardName(0, this.dash.title) + .validateNewDashboardName(this.folderId, this.dash.title) .then(() => { this.hasNameValidationError = false; }) @@ -138,6 +141,19 @@ export class DashboardImportCtrl { }); } + onFolderChange(folder) { + this.folderId = folder.id; + this.titleChanged(); + } + + onEnterFolderCreation() { + this.inputsValid = false; + } + + onExitFolderCreation() { + this.inputsValid = true; + } + saveDashboard() { var inputs = this.inputs.map(input => { return { @@ -153,6 +169,7 @@ export class DashboardImportCtrl { dashboard: this.dash, overwrite: true, inputs: inputs, + folderId: this.folderId, }) .then(res => { this.$location.url(res.importedUrl); diff --git a/public/app/features/dashboard/partials/dashboard_import.html b/public/app/features/dashboard/partials/dashboard_import.html index 51011ae2c3d..b0f3568148d 100644 --- a/public/app/features/dashboard/partials/dashboard_import.html +++ b/public/app/features/dashboard/partials/dashboard_import.html @@ -80,6 +80,18 @@ +
+
+ + +
+
+
From 393f41cd148a6eabc32131e88d670b89f4278b3d Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 5 Jun 2018 19:37:47 +0300 Subject: [PATCH 2/6] dashboard: add Import button to manage page --- .../manage_dashboards/manage_dashboards.html | 4 ++++ .../components/manage_dashboards/manage_dashboards.ts | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.html b/public/app/core/components/manage_dashboards/manage_dashboards.html index aac30d2ce02..92946b866a1 100644 --- a/public/app/core/components/manage_dashboards/manage_dashboards.html +++ b/public/app/core/components/manage_dashboards/manage_dashboards.html @@ -13,6 +13,10 @@ Folder + + + Import +
diff --git a/public/app/core/components/manage_dashboards/manage_dashboards.ts b/public/app/core/components/manage_dashboards/manage_dashboards.ts index db73d84fd58..86cd3066c48 100644 --- a/public/app/core/components/manage_dashboards/manage_dashboards.ts +++ b/public/app/core/components/manage_dashboards/manage_dashboards.ts @@ -294,6 +294,16 @@ export class ManageDashboardsCtrl { return url; } + + importDashboardUrl() { + let url = 'dashboard/import'; + + if (this.folderId) { + url += `?folderId=${this.folderId}`; + } + + return url; + } } export function manageDashboardsDirective() { From 4ff4ac1d5f7a7c2d42998305b047155056564671 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 5 Jun 2018 20:01:48 +0300 Subject: [PATCH 3/6] dashboard: import into current folder from manage folder page --- public/app/features/dashboard/dashboard_import_ctrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard/dashboard_import_ctrl.ts b/public/app/features/dashboard/dashboard_import_ctrl.ts index 7764bb4815f..444bf642fcc 100644 --- a/public/app/features/dashboard/dashboard_import_ctrl.ts +++ b/public/app/features/dashboard/dashboard_import_ctrl.ts @@ -33,7 +33,7 @@ export class DashboardImportCtrl { this.uidExists = false; this.autoGenerateUid = true; this.autoGenerateUidValue = 'auto-generated'; - this.folderId = 0; + this.folderId = Number($routeParams.folderId) || 0; // check gnetId in url if ($routeParams.gnetId) { From 9460063ab5d11f0a23e584cd4031f870b85922e6 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 6 Jun 2018 13:55:00 +0300 Subject: [PATCH 4/6] show import menu in sidenav, dashboard search and manage dashboards page if user has editor permissions for at least one folder --- pkg/api/index.go | 1 + public/app/core/components/search/search.html | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index acf0c30c907..57b0b50603c 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -95,6 +95,7 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { if hasEditPermissionInFoldersQuery.Result { children := []*dtos.NavLink{ {Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"}, + {Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"}, } if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR { diff --git a/public/app/core/components/search/search.html b/public/app/core/components/search/search.html index 561c752208e..8723d5d0584 100644 --- a/public/app/core/components/search/search.html +++ b/public/app/core/components/search/search.html @@ -52,11 +52,11 @@ New folder - + Import dashboard - Find dashboards on Grafana.com + Find dashboards on Grafana.com
From 8fd3015e5263e2547a9338f14cf7187b3067cd2b Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Wed, 6 Jun 2018 13:58:20 +0300 Subject: [PATCH 5/6] dashboard: improve import UX for non-editor users validate folderId, import only into available folders --- .../dashboard/dashboard_import_ctrl.ts | 9 ++++++- .../dashboard/folder_picker/folder_picker.ts | 25 +++++++++++-------- .../dashboard/partials/dashboard_import.html | 6 +++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/public/app/features/dashboard/dashboard_import_ctrl.ts b/public/app/features/dashboard/dashboard_import_ctrl.ts index 444bf642fcc..599019fb78e 100644 --- a/public/app/features/dashboard/dashboard_import_ctrl.ts +++ b/public/app/features/dashboard/dashboard_import_ctrl.ts @@ -22,6 +22,7 @@ export class DashboardImportCtrl { autoGenerateUid: boolean; autoGenerateUidValue: string; folderId: number; + initialFolderTitle: string; isValidFolderSelection: boolean; /** @ngInject */ @@ -33,7 +34,8 @@ export class DashboardImportCtrl { this.uidExists = false; this.autoGenerateUid = true; this.autoGenerateUidValue = 'auto-generated'; - this.folderId = Number($routeParams.folderId) || 0; + this.folderId = $routeParams.folderId ? Number($routeParams.folderId) || 0 : null; + this.initialFolderTitle = 'Select a folder'; // check gnetId in url if ($routeParams.gnetId) { @@ -107,6 +109,7 @@ export class DashboardImportCtrl { this.validationSrv .validateNewDashboardName(this.folderId, this.dash.title) .then(() => { + this.nameExists = false; this.hasNameValidationError = false; }) .catch(err => { @@ -154,6 +157,10 @@ export class DashboardImportCtrl { this.inputsValid = true; } + isValid() { + return this.inputsValid && !this.hasNameValidationError && this.folderId !== null; + } + saveDashboard() { var inputs = this.inputs.map(input => { return { diff --git a/public/app/features/dashboard/folder_picker/folder_picker.ts b/public/app/features/dashboard/folder_picker/folder_picker.ts index 69a09455c4d..e04fcb9042f 100644 --- a/public/app/features/dashboard/folder_picker/folder_picker.ts +++ b/public/app/features/dashboard/folder_picker/folder_picker.ts @@ -132,23 +132,26 @@ export class FolderPickerCtrl { } private loadInitialValue() { - if (this.initialFolderId && this.initialFolderId > 0) { - this.getOptions('').then(result => { + this.getOptions('').then(result => { + if (!_.isNil(this.initialFolderId)) { + // If initialFolderId is set, try to find it in result or return null this.folder = _.find(result, { value: this.initialFolderId }); if (!this.folder) { - this.folder = { text: this.initialTitle, value: this.initialFolderId }; + this.folder = { text: this.initialTitle, value: null }; } - this.onFolderLoad(); - }); - } else { - if (this.initialTitle && this.initialFolderId === null) { - this.folder = { text: this.initialTitle, value: null }; } else { - this.folder = { text: this.rootName, value: 0 }; + // If initialFolderId isn't set, return General folder for Editor + // or first available for user, otherwise return null + if (this.isEditor) { + this.folder = { text: this.rootName, value: 0 }; + } else if (result.length > 0) { + this.folder = result[0]; + } else { + this.folder = { text: this.initialTitle, value: null }; + } } - this.onFolderLoad(); - } + }); } private onFolderLoad() { diff --git a/public/app/features/dashboard/partials/dashboard_import.html b/public/app/features/dashboard/partials/dashboard_import.html index b0f3568148d..1cb8cdccd4f 100644 --- a/public/app/features/dashboard/partials/dashboard_import.html +++ b/public/app/features/dashboard/partials/dashboard_import.html @@ -83,7 +83,9 @@
- - Cancel From 25504e84eda2ca685104375e7eca68b1c91163ce Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 7 Jun 2018 17:05:56 +0300 Subject: [PATCH 6/6] dashboard import to folder: minor fixes --- pkg/api/index.go | 4 +-- .../dashboard/dashboard_import_ctrl.ts | 4 +-- .../dashboard/folder_picker/folder_picker.ts | 26 +++++++++---------- .../dashboard/partials/dashboard_import.html | 16 ++++++------ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index 57b0b50603c..f5f20f24bc7 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -95,14 +95,14 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) { if hasEditPermissionInFoldersQuery.Result { children := []*dtos.NavLink{ {Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"}, - {Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"}, } if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR { children = append(children, &dtos.NavLink{Text: "Folder", SubTitle: "Create a new folder to organize your dashboards", Id: "folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboards/folder/new"}) - children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"}) } + children = append(children, &dtos.NavLink{Text: "Import", SubTitle: "Import dashboard from file or Grafana.com", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"}) + data.NavTree = append(data.NavTree, &dtos.NavLink{ Text: "Create", Id: "create", diff --git a/public/app/features/dashboard/dashboard_import_ctrl.ts b/public/app/features/dashboard/dashboard_import_ctrl.ts index 599019fb78e..73e9e316b4e 100644 --- a/public/app/features/dashboard/dashboard_import_ctrl.ts +++ b/public/app/features/dashboard/dashboard_import_ctrl.ts @@ -154,11 +154,11 @@ export class DashboardImportCtrl { } onExitFolderCreation() { - this.inputsValid = true; + this.inputValueChanged(); } isValid() { - return this.inputsValid && !this.hasNameValidationError && this.folderId !== null; + return this.inputsValid && this.folderId !== null; } saveDashboard() { diff --git a/public/app/features/dashboard/folder_picker/folder_picker.ts b/public/app/features/dashboard/folder_picker/folder_picker.ts index e04fcb9042f..28338c29d33 100644 --- a/public/app/features/dashboard/folder_picker/folder_picker.ts +++ b/public/app/features/dashboard/folder_picker/folder_picker.ts @@ -132,24 +132,24 @@ export class FolderPickerCtrl { } private loadInitialValue() { + const resetFolder = { text: this.initialTitle, value: null }; + const rootFolder = { text: this.rootName, value: 0 }; this.getOptions('').then(result => { - if (!_.isNil(this.initialFolderId)) { - // If initialFolderId is set, try to find it in result or return null - this.folder = _.find(result, { value: this.initialFolderId }); - if (!this.folder) { - this.folder = { text: this.initialTitle, value: null }; - } - } else { - // If initialFolderId isn't set, return General folder for Editor - // or first available for user, otherwise return null + let folder; + if (this.initialFolderId) { + folder = _.find(result, { value: this.initialFolderId }); + } else if (this.enableReset && this.initialTitle && this.initialFolderId === null) { + folder = resetFolder; + } + + if (!folder) { if (this.isEditor) { - this.folder = { text: this.rootName, value: 0 }; - } else if (result.length > 0) { - this.folder = result[0]; + folder = rootFolder; } else { - this.folder = { text: this.initialTitle, value: null }; + folder = result.length > 0 ? result[0] : resetFolder; } } + this.folder = folder; this.onFolderLoad(); }); } diff --git a/public/app/features/dashboard/partials/dashboard_import.html b/public/app/features/dashboard/partials/dashboard_import.html index 1cb8cdccd4f..b5358dfd0f8 100644 --- a/public/app/features/dashboard/partials/dashboard_import.html +++ b/public/app/features/dashboard/partials/dashboard_import.html @@ -82,14 +82,14 @@
- +