From 60d7e4308c03b3a7d825e000975231ed5fb311e6 Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Tue, 19 Dec 2017 13:33:34 +0100 Subject: [PATCH] dashfolders: support creating new folder from the folder picker. #10197 --- public/app/core/services/backend_srv.ts | 2 +- public/app/features/dashboard/all.ts | 57 ++++--- .../folder_picker/folder_picker.html | 46 +++++ .../dashboard/folder_picker/folder_picker.ts | 161 ++++++++++++++++++ .../dashboard/folder_picker/picker.ts | 103 ----------- .../app/features/dashboard/validation_srv.ts | 48 ++++++ public/sass/components/_gf-form.scss | 4 + 7 files changed, 289 insertions(+), 132 deletions(-) create mode 100644 public/app/features/dashboard/folder_picker/folder_picker.html create mode 100644 public/app/features/dashboard/folder_picker/folder_picker.ts delete mode 100644 public/app/features/dashboard/folder_picker/picker.ts create mode 100644 public/app/features/dashboard/validation_srv.ts diff --git a/public/app/core/services/backend_srv.ts b/public/app/core/services/backend_srv.ts index 5b276a53e4f..bc34d9d37ab 100644 --- a/public/app/core/services/backend_srv.ts +++ b/public/app/core/services/backend_srv.ts @@ -251,7 +251,7 @@ export class BackendSrv { createDashboardFolder(name) { const dash = { schemaVersion: 16, - title: name, + title: name.trim(), editable: true, panels: [] }; diff --git a/public/app/features/dashboard/all.ts b/public/app/features/dashboard/all.ts index 7bf0a980205..14517bea7bb 100644 --- a/public/app/features/dashboard/all.ts +++ b/public/app/features/dashboard/all.ts @@ -1,31 +1,32 @@ -import "./dashboard_ctrl"; -import "./alerting_srv"; -import "./history/history"; -import "./dashboardLoaderSrv"; -import "./dashnav/dashnav"; -import "./submenu/submenu"; -import "./save_as_modal"; -import "./save_modal"; -import "./shareModalCtrl"; -import "./shareSnapshotCtrl"; -import "./dashboard_srv"; -import "./view_state_srv"; -import "./time_srv"; -import "./unsavedChangesSrv"; -import "./unsaved_changes_modal"; -import "./timepicker/timepicker"; -import "./upload"; -import "./export/export_modal"; -import "./export_data/export_data_modal"; -import "./ad_hoc_filters"; -import "./repeat_option/repeat_option"; -import "./dashgrid/DashboardGridDirective"; -import "./dashgrid/PanelLoader"; -import "./dashgrid/RowOptions"; -import "./acl/acl"; -import "./folder_picker/picker"; -import "./move_to_folder_modal/move_to_folder"; -import "./settings/settings"; +import './dashboard_ctrl'; +import './alerting_srv'; +import './history/history'; +import './dashboardLoaderSrv'; +import './dashnav/dashnav'; +import './submenu/submenu'; +import './save_as_modal'; +import './save_modal'; +import './shareModalCtrl'; +import './shareSnapshotCtrl'; +import './dashboard_srv'; +import './view_state_srv'; +import './validation_srv'; +import './time_srv'; +import './unsavedChangesSrv'; +import './unsaved_changes_modal'; +import './timepicker/timepicker'; +import './upload'; +import './export/export_modal'; +import './export_data/export_data_modal'; +import './ad_hoc_filters'; +import './repeat_option/repeat_option'; +import './dashgrid/DashboardGridDirective'; +import './dashgrid/PanelLoader'; +import './dashgrid/RowOptions'; +import './acl/acl'; +import './folder_picker/folder_picker'; +import './move_to_folder_modal/move_to_folder'; +import './settings/settings'; import coreModule from "app/core/core_module"; import { DashboardListCtrl } from "./dashboard_list_ctrl"; diff --git a/public/app/features/dashboard/folder_picker/folder_picker.html b/public/app/features/dashboard/folder_picker/folder_picker.html new file mode 100644 index 00000000000..b830e65b7e6 --- /dev/null +++ b/public/app/features/dashboard/folder_picker/folder_picker.html @@ -0,0 +1,46 @@ +
+
+ + + +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
\ No newline at end of file diff --git a/public/app/features/dashboard/folder_picker/folder_picker.ts b/public/app/features/dashboard/folder_picker/folder_picker.ts new file mode 100644 index 00000000000..72d1984634c --- /dev/null +++ b/public/app/features/dashboard/folder_picker/folder_picker.ts @@ -0,0 +1,161 @@ +/// + +import _ from 'lodash'; +import coreModule from 'app/core/core_module'; +import appEvents from 'app/core/app_events'; + +export class FolderPickerCtrl { + initialTitle: string; + initialFolderId?: number; + labelClass: string; + onChange: any; + onLoad: any; + onCreateFolder: any; + enterFolderCreation: any; + exitFolderCreation: any; + enableCreateNew: boolean; + rootName = 'Root'; + folder: any; + createNewFolder: boolean; + newFolderName: string; + newFolderNameTouched: boolean; + hasValidationError: boolean; + validationError: any; + + /** @ngInject */ + constructor(private backendSrv, private validationSrv) { + if (!this.labelClass) { + this.labelClass = "width-7"; + } + + this.loadInitialValue(); + } + + getOptions(query) { + var params = { + query: query, + type: 'dash-folder', + }; + + return this.backendSrv.search(params).then(result => { + if (query === '' || + query.toLowerCase() === "r" || + query.toLowerCase() === "ro" || + query.toLowerCase() === "roo" || + query.toLowerCase() === "root") { + result.unshift({title: this.rootName, id: 0}); + } + + if (this.enableCreateNew && query === '') { + result.unshift({title: '-- New Folder --', id: -1}); + } + + return _.map(result, item => { + return {text: item.title, value: item.id}; + }); + }); + } + + onFolderChange(option) { + if (option.value === -1) { + this.createNewFolder = true; + this.enterFolderCreation(); + return; + } + this.onChange({$folder: {id: option.value, title: option.text}}); + } + + newFolderNameChanged() { + this.newFolderNameTouched = true; + + this.validationSrv.validateNewDashboardOrFolderName(this.newFolderName) + .then(() => { + this.hasValidationError = false; + }) + .catch(err => { + this.hasValidationError = true; + this.validationError = err.message; + }); + } + + createFolder(evt) { + if (evt) { + evt.stopPropagation(); + evt.preventDefault(); + } + + return this.backendSrv.createDashboardFolder(this.newFolderName).then(result => { + appEvents.emit('alert-success', ['Folder Created', 'OK']); + + this.closeCreateFolder(); + this.folder = {text: result.dashboard.title, value: result.dashboard.id}; + this.onFolderChange(this.folder); + }); + } + + cancelCreateFolder(evt) { + if (evt) { + evt.stopPropagation(); + evt.preventDefault(); + } + + this.closeCreateFolder(); + this.loadInitialValue(); + } + + private closeCreateFolder() { + this.exitFolderCreation(); + this.createNewFolder = false; + this.hasValidationError = false; + this.validationError = null; + this.newFolderName = ''; + this.newFolderNameTouched = false; + } + + private loadInitialValue() { + if (this.initialFolderId && this.initialFolderId > 0) { + this.getOptions('').then(result => { + this.folder = _.find(result, {value: this.initialFolderId}); + this.onFolderLoad(); + }); + } else { + if (this.initialTitle) { + this.folder = {text: this.initialTitle, value: null}; + } else { + this.folder = {text: this.rootName, value: 0}; + } + + this.onFolderLoad(); + } + } + + private onFolderLoad() { + if (this.onLoad) { + this.onLoad({$folder: {id: this.folder.value, title: this.folder.text}}); + } + } +} + +export function folderPicker() { + return { + restrict: 'E', + templateUrl: 'public/app/features/dashboard/folder_picker/folder_picker.html', + controller: FolderPickerCtrl, + bindToController: true, + controllerAs: 'ctrl', + scope: { + initialTitle: '<', + initialFolderId: '<', + labelClass: '@', + rootName: '@', + onChange: '&', + onLoad: '&', + onCreateFolder: '&', + enterFolderCreation: '&', + exitFolderCreation: '&', + enableCreateNew: '@' + } + }; +} + +coreModule.directive('folderPicker', folderPicker); diff --git a/public/app/features/dashboard/folder_picker/picker.ts b/public/app/features/dashboard/folder_picker/picker.ts deleted file mode 100644 index f04065b36f5..00000000000 --- a/public/app/features/dashboard/folder_picker/picker.ts +++ /dev/null @@ -1,103 +0,0 @@ -/// - -import coreModule from "app/core/core_module"; -import _ from "lodash"; - -export class FolderPickerCtrl { - initialTitle: string; - initialFolderId?: number; - labelClass: string; - onChange: any; - onLoad: any; - rootName = "Root"; - folder: any; - - /** @ngInject */ - constructor(private backendSrv) { - if (!this.labelClass) { - this.labelClass = "width-7"; - } - - if (this.initialFolderId && this.initialFolderId > 0) { - this.getOptions("").then(result => { - this.folder = _.find(result, { value: this.initialFolderId }); - this.onFolderLoad(); - }); - } else { - if (this.initialTitle) { - this.folder = { text: this.initialTitle, value: null }; - } else { - this.folder = { text: this.rootName, value: 0 }; - } - - this.onFolderLoad(); - } - } - - getOptions(query) { - var params = { - query: query, - type: "dash-folder" - }; - - return this.backendSrv.search(params).then(result => { - if ( - query === "" || - query.toLowerCase() === "r" || - query.toLowerCase() === "ro" || - query.toLowerCase() === "roo" || - query.toLowerCase() === "root" - ) { - result.unshift({ title: this.rootName, id: 0 }); - } - - return _.map(result, item => { - return { text: item.title, value: item.id }; - }); - }); - } - - onFolderLoad() { - if (this.onLoad) { - this.onLoad({ - $folder: { id: this.folder.value, title: this.folder.text } - }); - } - } - - onFolderChange(option) { - this.onChange({ $folder: { id: option.value, title: option.text } }); - } -} - -const template = ` -
- - -
-`; - -export function folderPicker() { - return { - restrict: "E", - template: template, - controller: FolderPickerCtrl, - bindToController: true, - controllerAs: "ctrl", - scope: { - initialTitle: "<", - initialFolderId: "<", - labelClass: "@", - rootName: "@", - onChange: "&", - onLoad: "&" - } - }; -} - -coreModule.directive("folderPicker", folderPicker); diff --git a/public/app/features/dashboard/validation_srv.ts b/public/app/features/dashboard/validation_srv.ts new file mode 100644 index 00000000000..4e02b8a678f --- /dev/null +++ b/public/app/features/dashboard/validation_srv.ts @@ -0,0 +1,48 @@ +/// + +import coreModule from 'app/core/core_module'; + +export class ValidationSrv { + rootName = 'root'; + + /** @ngInject */ + constructor(private $q, private backendSrv) {} + + validateNewDashboardOrFolderName(name) { + name = (name || '').trim(); + + if (name.length === 0) { + return this.$q.reject({ + type: 'REQUIRED', + message: 'Name is required' + }); + } + + if (name.toLowerCase() === this.rootName) { + return this.$q.reject({ + type: 'EXISTING', + message: 'A folder or dashboard with the same name already exists' + }); + } + + let deferred = this.$q.defer(); + + this.backendSrv.search({ query: name }).then(res => { + for (let hit of res) { + if (name.toLowerCase() === hit.title.toLowerCase()) { + deferred.reject({ + type: 'EXISTING', + message: 'A folder or dashboard with the same name already exists' + }); + break; + } + } + + deferred.resolve(); + }); + + return deferred.promise; + } +} + +coreModule.service('validationSrv', ValidationSrv); diff --git a/public/sass/components/_gf-form.scss b/public/sass/components/_gf-form.scss index 3a61246c494..10a900f82f9 100644 --- a/public/sass/components/_gf-form.scss +++ b/public/sass/components/_gf-form.scss @@ -109,6 +109,10 @@ $input-border: 1px solid $input-border-color; &--error { color: $critical; } + + &:disabled { + color: $text-color-weak + } } .gf-form-label + .gf-form-label {