Commit d9dca72e by Torkel Ödegaard

dashboard_folders: refactoring picker and folder selection in dashboard settings & save as menu

parent 5b35a21d
...@@ -40,8 +40,8 @@ func GetDashboard(c *middleware.Context) Response { ...@@ -40,8 +40,8 @@ func GetDashboard(c *middleware.Context) Response {
slug := strings.ToLower(c.Params(":slug")) slug := strings.ToLower(c.Params(":slug"))
query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId} query := m.GetDashboardQuery{Slug: slug, OrgId: c.OrgId}
err := bus.Dispatch(&query)
if err != nil { if err := bus.Dispatch(&query); err != nil {
return ApiError(404, "Dashboard not found", err) return ApiError(404, "Dashboard not found", err)
} }
...@@ -70,27 +70,39 @@ func GetDashboard(c *middleware.Context) Response { ...@@ -70,27 +70,39 @@ func GetDashboard(c *middleware.Context) Response {
creator = getUserLogin(dash.CreatedBy) creator = getUserLogin(dash.CreatedBy)
} }
meta := dtos.DashboardMeta{
IsStarred: isStarred,
Slug: slug,
Type: m.DashTypeDB,
CanStar: c.IsSignedIn,
CanSave: canSave,
CanEdit: canEdit,
Created: dash.Created,
Updated: dash.Updated,
UpdatedBy: updater,
CreatedBy: creator,
Version: dash.Version,
HasAcl: dash.HasAcl,
IsFolder: dash.IsFolder,
FolderId: dash.ParentId,
FolderTitle: "Root",
}
// lookup folder title
if dash.ParentId > 0 {
query := m.GetDashboardQuery{Id: dash.ParentId, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Dashboard folder could not be read", err)
}
meta.FolderTitle = query.Result.Title
}
// make sure db version is in sync with json model version // make sure db version is in sync with json model version
dash.Data.Set("version", dash.Version) dash.Data.Set("version", dash.Version)
dto := dtos.DashboardFullWithMeta{ dto := dtos.DashboardFullWithMeta{
Dashboard: dash.Data, Dashboard: dash.Data,
Meta: dtos.DashboardMeta{ Meta: meta,
IsStarred: isStarred,
Slug: slug,
Type: m.DashTypeDB,
CanStar: c.IsSignedIn,
CanSave: canSave,
CanEdit: canEdit,
Created: dash.Created,
Updated: dash.Updated,
UpdatedBy: updater,
CreatedBy: creator,
Version: dash.Version,
HasAcl: dash.HasAcl,
IsFolder: dash.IsFolder,
ParentId: dash.ParentId,
},
} }
c.TimeRequest(metrics.M_Api_Dashboard_Get) c.TimeRequest(metrics.M_Api_Dashboard_Get)
......
...@@ -7,23 +7,24 @@ import ( ...@@ -7,23 +7,24 @@ import (
) )
type DashboardMeta struct { type DashboardMeta struct {
IsStarred bool `json:"isStarred,omitempty"` IsStarred bool `json:"isStarred,omitempty"`
IsHome bool `json:"isHome,omitempty"` IsHome bool `json:"isHome,omitempty"`
IsSnapshot bool `json:"isSnapshot,omitempty"` IsSnapshot bool `json:"isSnapshot,omitempty"`
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
CanSave bool `json:"canSave"` CanSave bool `json:"canSave"`
CanEdit bool `json:"canEdit"` CanEdit bool `json:"canEdit"`
CanStar bool `json:"canStar"` CanStar bool `json:"canStar"`
Slug string `json:"slug"` Slug string `json:"slug"`
Expires time.Time `json:"expires"` Expires time.Time `json:"expires"`
Created time.Time `json:"created"` Created time.Time `json:"created"`
Updated time.Time `json:"updated"` Updated time.Time `json:"updated"`
UpdatedBy string `json:"updatedBy"` UpdatedBy string `json:"updatedBy"`
CreatedBy string `json:"createdBy"` CreatedBy string `json:"createdBy"`
Version int `json:"version"` Version int `json:"version"`
HasAcl bool `json:"hasAcl"` HasAcl bool `json:"hasAcl"`
IsFolder bool `json:"isFolder"` IsFolder bool `json:"isFolder"`
ParentId int64 `json:"parentId"` FolderId int64 `json:"folderId"`
FolderTitle string `json:"folderTitle"`
} }
type DashboardFullWithMeta struct { type DashboardFullWithMeta struct {
......
...@@ -197,7 +197,7 @@ export class NavModelSrv { ...@@ -197,7 +197,7 @@ export class NavModelSrv {
clickHandler: () => dashNavCtrl.showHelpModal() clickHandler: () => dashNavCtrl.showHelpModal()
}); });
if (this.contextSrv.isEditor) { if (this.contextSrv.isEditor && !dashboard.meta.isFolder) {
menu.push({ menu.push({
title: 'Save As ...', title: 'Save As ...',
icon: 'fa fa-fw fa-save', icon: 'fa fa-fw fa-save',
......
...@@ -128,8 +128,10 @@ export class DashboardCtrl { ...@@ -128,8 +128,10 @@ export class DashboardCtrl {
$rootScope.$broadcast("refresh"); $rootScope.$broadcast("refresh");
}; };
$scope.onFolderChange = function(parentId) { $scope.onFolderChange = function(folder) {
$scope.dashboard.parentId = parentId; $scope.dashboard.parentId = folder.id;
$scope.dashboard.meta.folderId = folder.id;
$scope.dashboard.meta.folderTitle= folder.title;
}; };
} }
......
...@@ -113,17 +113,8 @@ export class DashboardSrv { ...@@ -113,17 +113,8 @@ export class DashboardSrv {
} }
showSaveAsModal() { showSaveAsModal() {
var newScope = this.$rootScope.$new();
newScope.clone = this.dash.getSaveModelClone();
newScope.clone.editable = true;
newScope.clone.hideControls = false;
newScope.clone.meta = {};
newScope.clone.meta.parentId = this.dash.meta.parentId;
newScope.clone.meta.isFolder = this.dash.meta.isFolder;
this.$rootScope.appEvent('show-modal', { this.$rootScope.appEvent('show-modal', {
templateHtml: '<save-dashboard-as-modal dismiss="dismiss()"></save-dashboard-as-modal>', templateHtml: '<save-dashboard-as-modal dismiss="dismiss()"></save-dashboard-as-modal>',
scope: newScope,
modalClass: 'modal--narrow' modalClass: 'modal--narrow'
}); });
} }
...@@ -131,7 +122,6 @@ export class DashboardSrv { ...@@ -131,7 +122,6 @@ export class DashboardSrv {
showSaveModal() { showSaveModal() {
this.$rootScope.appEvent('show-modal', { this.$rootScope.appEvent('show-modal', {
templateHtml: '<save-dashboard-modal dismiss="dismiss()"></save-dashboard-modal>', templateHtml: '<save-dashboard-modal dismiss="dismiss()"></save-dashboard-modal>',
scope: this.$rootScope.$new(),
modalClass: 'modal--narrow' modalClass: 'modal--narrow'
}); });
} }
......
<div class="gf-form">
<label class="gf-form-label width-7">Folder</label>
<div class="dropdown">
<metric-segment segment="ctrl.selectedFolderSegment"
get-options="ctrl.getOptions()"
on-change="ctrl.folderChanged()"></metric-segment>
</div>
</div>
...@@ -6,47 +6,38 @@ import _ from 'lodash'; ...@@ -6,47 +6,38 @@ import _ from 'lodash';
export class FolderPickerCtrl { export class FolderPickerCtrl {
folders: Folder[]; folders: Folder[];
selectedFolder: number; selectedOption: any;
selectedFolderSegment: any; initialTitle: string;
onChange: any; onChange: any;
rootFolderName: string; labelClass: string;
/** @ngInject */ /** @ngInject */
constructor(private backendSrv, private $scope, private $sce, private uiSegmentSrv) { constructor(private backendSrv, private $scope, private $sce) {
this.selectedFolderSegment = this.uiSegmentSrv.newSegment({value: this.rootFolderName || 'Root', selectMode: true}); if (!this.labelClass) {
this.get(); this.labelClass = "width-7";
}
this.selectedOption = {text: this.initialTitle, value: null};
} }
get() { getOptions(query) {
var params = { var params = {
query: query,
type: 'dash-folder', type: 'dash-folder',
}; };
return this.backendSrv.search(params).then(result => { return this.backendSrv.search(params).then(result => {
this.folders = [{id: 0, title: this.rootFolderName || 'Root', type: 'dash-folder'}]; if (query === "") {
this.folders.push(...result); result.unshift({title: "Root", value: 0});
if (this.selectedFolder) {
const selected = _.find(this.folders, {id: this.selectedFolder});
this.selectedFolderSegment.value = selected.title;
this.selectedFolderSegment.text = selected.title;
this.selectedFolderSegment.html = this.$sce.trustAsHtml(selected.title);
} }
return _.map(result, item => {
return {text: item.title, value: item.id};
});
}); });
} }
getOptions() { folderChanged(option) {
return Promise.resolve(this.folders.map(folder => { this.onChange({$folder: {id: option.value, title: option.text}});
return this.uiSegmentSrv.newSegment(folder.title);
}));
}
folderChanged() {
const selected = _.find(this.folders, {title: this.selectedFolderSegment.value});
if (selected) {
this.onChange({$folderId: selected.id});
}
} }
} }
...@@ -61,17 +52,29 @@ export interface Folder { ...@@ -61,17 +52,29 @@ export interface Folder {
dashboards?: any; dashboards?: any;
} }
const template = `
<div class="gf-form">
<label class="gf-form-label {{ctrl.labelClass}}">Folder</label>
<div class="dropdown">
<gf-form-dropdown model="ctrl.selectedOption"
get-options="ctrl.getOptions($query)"
on-change="ctrl.folderChanged($option)">
</gf-form-dropdown>
</div>
</div>
`;
export function folderPicker() { export function folderPicker() {
return { return {
restrict: 'E', restrict: 'E',
templateUrl: 'public/app/features/dashboard/folder_picker/picker.html', template: template,
controller: FolderPickerCtrl, controller: FolderPickerCtrl,
bindToController: true, bindToController: true,
controllerAs: 'ctrl', controllerAs: 'ctrl',
scope: { scope: {
selectedFolder: "<", initialTitle: "<",
onChange: "&", onChange: "&",
rootFolderName: "@" labelClass: "@",
} }
}; };
} }
......
...@@ -45,7 +45,11 @@ ...@@ -45,7 +45,11 @@
</div> </div>
</div> </div>
<folder-picker ng-if="!dashboardMeta.isFolder" selected-folder="dashboardMeta.parentId" on-change="onFolderChange($folderId)"></folder-picker> <folder-picker ng-if="!dashboardMeta.isFolder"
initial-title="dashboardMeta.folderTitle"
on-change="onFolderChange($folder)"
label-class="width-7">
</folder-picker>
</div> </div>
<div class="section"> <div class="section">
......
...@@ -18,11 +18,13 @@ const template = ` ...@@ -18,11 +18,13 @@ const template = `
<form name="ctrl.saveForm" ng-submit="ctrl.save()" class="modal-content" novalidate> <form name="ctrl.saveForm" ng-submit="ctrl.save()" class="modal-content" novalidate>
<div class="p-t-2"> <div class="p-t-2">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label">New name</label> <label class="gf-form-label width-7">New name</label>
<input type="text" class="gf-form-input" ng-model="ctrl.clone.title" give-focus="true" required> <input type="text" class="gf-form-input" ng-model="ctrl.clone.title" give-focus="true" required>
</div> </div>
<div class="gf-form"> <div class="gf-form">
<folder-picker ng-if="!ctrl.clone.meta.isFolder" selected-folder="ctrl.clone.meta.parentId" on-change="ctrl.onFolderChange($folderId)"> <folder-picker initial-title="ctrl.folderTitle"
on-change="ctrl.onFolderChange($folder)"
label-class="width-7">
</folder-picker> </folder-picker>
</div> </div>
</div> </div>
...@@ -37,6 +39,7 @@ const template = ` ...@@ -37,6 +39,7 @@ const template = `
export class SaveDashboardAsModalCtrl { export class SaveDashboardAsModalCtrl {
clone: any; clone: any;
folderTitle: any;
dismiss: () => void; dismiss: () => void;
/** @ngInject */ /** @ngInject */
...@@ -47,6 +50,7 @@ export class SaveDashboardAsModalCtrl { ...@@ -47,6 +50,7 @@ export class SaveDashboardAsModalCtrl {
this.clone.title += ' Copy'; this.clone.title += ' Copy';
this.clone.editable = true; this.clone.editable = true;
this.clone.hideControls = false; this.clone.hideControls = false;
this.folderTitle = dashboard.meta.folderTitle || 'Root';
// remove alerts // remove alerts
this.clone.rows.forEach(row => { this.clone.rows.forEach(row => {
...@@ -68,8 +72,8 @@ export class SaveDashboardAsModalCtrl { ...@@ -68,8 +72,8 @@ export class SaveDashboardAsModalCtrl {
} }
} }
onFolderChange(parentId) { onFolderChange(folder) {
this.clone.parentId = parentId; this.clone.parentId = folder.id;
} }
} }
......
...@@ -23,8 +23,10 @@ ...@@ -23,8 +23,10 @@
</div> </div>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-6">Folder</span> <folder-picker initial-text="ctrl.folderTitle"
<folder-picker selected-folder="ctrl.panel.folderId" on-change="ctrl.onFolderChange" root-folder-name="All"></folder-picker> on-change="ctrl.onFolderChange($folder)"
label-class="width-6">
</folder-picker>
</div> </div>
<div class="gf-form"> <div class="gf-form">
......
...@@ -10,6 +10,7 @@ class DashListCtrl extends PanelCtrl { ...@@ -10,6 +10,7 @@ class DashListCtrl extends PanelCtrl {
groups: any[]; groups: any[];
modes: any[]; modes: any[];
folderTitle: any;
panelDefaults = { panelDefaults = {
query: '', query: '',
...@@ -19,7 +20,7 @@ class DashListCtrl extends PanelCtrl { ...@@ -19,7 +20,7 @@ class DashListCtrl extends PanelCtrl {
search: false, search: false,
starred: true, starred: true,
headings: true, headings: true,
folderId: 0 folderId: 0,
}; };
/** @ngInject */ /** @ngInject */
...@@ -65,6 +66,10 @@ class DashListCtrl extends PanelCtrl { ...@@ -65,6 +66,10 @@ class DashListCtrl extends PanelCtrl {
this.editorTabIndex = 1; this.editorTabIndex = 1;
this.modes = ['starred', 'search', 'recently viewed']; this.modes = ['starred', 'search', 'recently viewed'];
this.addEditorTab('Options', 'public/app/plugins/panel/dashlist/editor.html'); this.addEditorTab('Options', 'public/app/plugins/panel/dashlist/editor.html');
if (!this.panel.folderId) {
this.folderTitle = "All";
}
} }
onRefresh() { onRefresh() {
...@@ -126,9 +131,10 @@ class DashListCtrl extends PanelCtrl { ...@@ -126,9 +131,10 @@ class DashListCtrl extends PanelCtrl {
}); });
} }
onFolderChange(parentId) { onFolderChange(folder) {
this.$scope.$parent.ctrl.panel.folderId = parentId; this.panel.folderId = folder.id;
this.$scope.$parent.ctrl.refresh(); this.panel.folderTitle = folder.title;
this.refresh();
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment