浏览代码

provisioning: improve UX when saving provisioned dashboards

bergquist 7 年之前
父节点
当前提交
d6faa3d06f

+ 7 - 0
pkg/api/dashboard.go

@@ -102,6 +102,13 @@ func GetDashboard(c *m.ReqContext) Response {
 		meta.FolderUrl = query.Result.GetUrl()
 	}
 
+	dpQuery := &m.GetProvisionedDashboardByDashboardId{DashboardId: dash.Id}
+	err = bus.Dispatch(dpQuery)
+	if dpQuery.Result != nil {
+		meta.CanEdit = true
+		meta.Provisioned = true
+	}
+
 	// make sure db version is in sync with json model version
 	dash.Data.Set("version", dash.Version)
 

+ 1 - 0
pkg/api/dtos/dashboard.go

@@ -28,6 +28,7 @@ type DashboardMeta struct {
 	FolderId    int64     `json:"folderId"`
 	FolderTitle string    `json:"folderTitle"`
 	FolderUrl   string    `json:"folderUrl"`
+	Provisioned bool      `json:"provisioned"`
 }
 
 type DashboardFullWithMeta struct {

+ 6 - 0
pkg/models/dashboards.go

@@ -317,6 +317,12 @@ type GetDashboardSlugByIdQuery struct {
 	Result string
 }
 
+type GetProvisionedDashboardByDashboardId struct {
+	DashboardId int64
+
+	Result *DashboardProvisioning
+}
+
 type GetProvisionedDashboardDataQuery struct {
 	Name string
 

+ 0 - 3
pkg/services/provisioning/dashboards/types.go

@@ -55,9 +55,6 @@ func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *Das
 	dash.OrgId = cfg.OrgId
 	dash.Dashboard.OrgId = cfg.OrgId
 	dash.Dashboard.FolderId = folderId
-	if !cfg.Editable {
-		dash.Dashboard.Data.Set("editable", cfg.Editable)
-	}
 
 	if dash.Dashboard.Title == "" {
 		return nil, models.ErrDashboardTitleEmpty

+ 13 - 0
pkg/services/sqlstore/dashboard_provisioning.go

@@ -8,6 +8,7 @@ import (
 func init() {
 	bus.AddHandler("sql", GetProvisionedDashboardDataQuery)
 	bus.AddHandler("sql", SaveProvisionedDashboard)
+	bus.AddHandler("sql", GetProvisionedDataByDashboardId)
 }
 
 type DashboardExtras struct {
@@ -17,6 +18,18 @@ type DashboardExtras struct {
 	Value       string
 }
 
+func GetProvisionedDataByDashboardId(cmd *models.GetProvisionedDashboardByDashboardId) error {
+	result := &models.DashboardProvisioning{}
+
+	_, err := x.Where("dashboard_id = ?", cmd.DashboardId).Get(result)
+	if err != nil {
+		return err
+	}
+
+	cmd.Result = result
+	return nil
+}
+
 func SaveProvisionedDashboard(cmd *models.SaveProvisionedDashboardCommand) error {
 	return inTransaction(func(sess *DBSession) error {
 		err := saveDashboard(sess, cmd.DashboardCmd)

+ 10 - 0
pkg/services/sqlstore/dashboard_provisioning_test.go

@@ -50,6 +50,16 @@ func TestDashboardProvisioningTest(t *testing.T) {
 				So(query.Result[0].DashboardId, ShouldEqual, dashId)
 				So(query.Result[0].Updated, ShouldEqual, now.Unix())
 			})
+
+			Convey("Can query for one provisioned dashboard", func() {
+				query := &models.GetProvisionedDashboardByDashboardId{DashboardId: cmd.Result.Id}
+
+				err := GetProvisionedDataByDashboardId(query)
+				So(err, ShouldBeNil)
+
+				So(query.Result.DashboardId, ShouldEqual, cmd.Result.Id)
+				So(query.Result.Updated, ShouldEqual, now.Unix())
+			})
 		})
 	})
 }

+ 1 - 0
public/app/features/dashboard/all.ts

@@ -6,6 +6,7 @@ import './dashnav/dashnav';
 import './submenu/submenu';
 import './save_as_modal';
 import './save_modal';
+import './save_provisioned_modal';
 import './shareModalCtrl';
 import './share_snapshot_ctrl';
 import './dashboard_srv';

+ 11 - 0
public/app/features/dashboard/dashboard_srv.ts

@@ -105,6 +105,10 @@ export class DashboardSrv {
       this.setCurrent(this.create(clone, this.dash.meta));
     }
 
+    if (this.dash.meta.provisioned) {
+      return this.showDashboardProvisionedModal();
+    }
+
     if (!this.dash.meta.canSave && options.makeEditable !== true) {
       return Promise.resolve();
     }
@@ -120,6 +124,13 @@ export class DashboardSrv {
     return this.save(this.dash.getSaveModelClone(), options);
   }
 
+  showDashboardProvisionedModal() {
+    this.$rootScope.appEvent('show-modal', {
+      templateHtml: '<save-provisioned-dashboard-modal dismiss="dismiss()"></save-provisioned-dashboard-modal>',
+      modalClass: 'modal--narrow',
+    });
+  }
+
   showSaveAsModal() {
     this.$rootScope.appEvent('show-modal', {
       templateHtml: '<save-dashboard-as-modal dismiss="dismiss()"></save-dashboard-as-modal>',

+ 1 - 1
public/app/features/dashboard/dashnav/dashnav.html

@@ -17,7 +17,7 @@
 	<div class="navbar__spacer"></div>
 
 	<div class="navbar-buttons navbar-buttons--actions">
-		<button class="btn navbar-button navbar-button--add-panel" ng-show="::ctrl.dashboard.meta.canSave" bs-tooltip="'Add panel'" data-placement="bottom" ng-click="ctrl.addPanel()">
+		<button class="btn navbar-button navbar-button--add-panel" ng-show="::ctrl.dashboard.meta.canEdit" bs-tooltip="'Add panel'" data-placement="bottom" ng-click="ctrl.addPanel()">
 			<i class="gicon gicon-add-panel"></i>
 		</button>
 

+ 74 - 0
public/app/features/dashboard/save_provisioned_modal.ts

@@ -0,0 +1,74 @@
+import coreModule from 'app/core/core_module';
+
+const template = `
+<div class="modal-body">
+  <div class="modal-header">
+    <h2 class="modal-header-title">
+      <i class="fa fa-save"></i>
+      <span class="p-l-1">Cannot save provisioned dashboards</span>
+    </h2>
+
+    <a class="modal-header-close" ng-click="ctrl.dismiss();">
+      <i class="fa fa-remove"></i>
+    </a>
+  </div>
+
+  <form name="ctrl.saveForm" class="modal-content" novalidate>
+    <h6 class="text-center">
+      This dashboard cannot be saved from Grafana's UI since it have been
+      <a href="http://docs.grafana.org/administration/provisioning/#dashboards">provisioned</a> from
+      another source. Please ask your Administrator for more info.
+    </h6>
+    <div class="p-t-2">
+      <div class="gf-form">
+        <label class="gf-form-hint">
+          <textarea
+            type="text"
+            name="dashboardJson"
+            class="gf-form-input"
+            ng-model="ctrl.dashboardJson"
+            ng-model-options="{allowInvalid: true}"
+            autocomplete="off"
+            rows="3" /></textarea>
+        </label>
+      </div>
+    </div>
+
+    <div class="gf-form-button-row text-center">
+      <button type="submit" class="btn btn-success" clipboard-button="ctrl.getJsonForClipboard()" >
+        <i class="fa fa-clipboard"></i>&nbsp;Copy json
+      </button>
+      <button class="btn btn-inverse" ng-click="ctrl.dismiss();">Close</button>
+    </div>
+  </form>
+</div>
+`;
+
+export class SaveProvisionedDashboardModalCtrl {
+  dashboardJson: string;
+  dismiss: () => void;
+
+  /** @ngInject */
+  constructor(dashboardSrv) {
+    var dashboard = dashboardSrv.getCurrent().getSaveModelClone();
+    delete dashboard.id;
+    this.dashboardJson = JSON.stringify(dashboard);
+  }
+
+  getJsonForClipboard() {
+    return this.dashboardJson;
+  }
+}
+
+export function saveProvisionedDashboardModalDirective() {
+  return {
+    restrict: 'E',
+    template: template,
+    controller: SaveProvisionedDashboardModalCtrl,
+    bindToController: true,
+    controllerAs: 'ctrl',
+    scope: { dismiss: '&' },
+  };
+}
+
+coreModule.directive('saveProvisionedDashboardModal', saveProvisionedDashboardModalDirective);

+ 28 - 0
public/app/features/dashboard/specs/save_provisioned_modal.jest.ts

@@ -0,0 +1,28 @@
+import { SaveProvisionedDashboardModalCtrl } from '../save_provisioned_modal';
+import { describe, it, expect } from 'test/lib/common';
+
+describe('SaveProvisionedDashboardModalCtrl', () => {
+  var json = {
+    title: 'name',
+    id: 5,
+  };
+
+  var mockDashboardSrv = {
+    getCurrent: function() {
+      return {
+        id: 5,
+        meta: {},
+        getSaveModelClone: function() {
+          return json;
+        },
+      };
+    },
+  };
+
+  var ctrl = new SaveProvisionedDashboardModalCtrl(mockDashboardSrv);
+
+  it('verify that the id have been removed', () => {
+    var copy = ctrl.getJsonForClipboard();
+    expect(copy).toBe(`{"title":"name"}`);
+  });
+});