Commit 2288e017 by Marcus Efraimsson Committed by GitHub

Merge pull request #12169 from alexanderzobnin/feat-10796

Import dashboard to folder
parents c39979a5 25504e84
......@@ -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"`
}
......@@ -99,9 +99,10 @@ func setIndexViewData(c *m.ReqContext) (*dtos.IndexViewData, error) {
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",
......
......@@ -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,
}
......
......@@ -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),
......
......@@ -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"`
......
......@@ -13,6 +13,10 @@
<i class="fa fa-plus"></i>
Folder
</a>
<a class="btn btn-success" href="{{ctrl.importDashboardUrl()}}" ng-if="ctrl.hasEditPermissionInFolders || ctrl.canSave">
<i class="fa fa-plus"></i>
Import
</a>
</div>
<div class="page-action-bar page-action-bar--narrow" ng-show="ctrl.hasFilters">
......
......@@ -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() {
......
......@@ -52,11 +52,11 @@
<a href="dashboards/folder/new" class="search-filter-box-link" ng-if="ctrl.isEditor">
<i class="gicon gicon-folder-new"></i> New folder
</a>
<a href="dashboard/import" class="search-filter-box-link" ng-if="ctrl.isEditor">
<a href="dashboard/import" class="search-filter-box-link" ng-if="ctrl.isEditor || ctrl.hasEditPermissionInFolders">
<i class="gicon gicon-dashboard-import"></i> Import dashboard
</a>
<a class="search-filter-box-link" target="_blank" href="https://grafana.com/dashboards?utm_source=grafana_search">
<img src="public/img/icn-dashboard-tiny.svg" width="20" /> Find dashboards on Grafana.com
<img src="public/img/icn-dashboard-tiny.svg" width="20" /> Find dashboards on Grafana.com
</a>
</div>
</div>
......
......@@ -21,6 +21,9 @@ export class DashboardImportCtrl {
uidValidationError: any;
autoGenerateUid: boolean;
autoGenerateUidValue: string;
folderId: number;
initialFolderTitle: string;
isValidFolderSelection: boolean;
/** @ngInject */
constructor(private backendSrv, private validationSrv, navModelSrv, private $location, $routeParams) {
......@@ -31,6 +34,8 @@ export class DashboardImportCtrl {
this.uidExists = false;
this.autoGenerateUid = true;
this.autoGenerateUidValue = 'auto-generated';
this.folderId = $routeParams.folderId ? Number($routeParams.folderId) || 0 : null;
this.initialFolderTitle = 'Select a folder';
// check gnetId in url
if ($routeParams.gnetId) {
......@@ -102,8 +107,9 @@ export class DashboardImportCtrl {
this.nameExists = false;
this.validationSrv
.validateNewDashboardName(0, this.dash.title)
.validateNewDashboardName(this.folderId, this.dash.title)
.then(() => {
this.nameExists = false;
this.hasNameValidationError = false;
})
.catch(err => {
......@@ -138,6 +144,23 @@ export class DashboardImportCtrl {
});
}
onFolderChange(folder) {
this.folderId = folder.id;
this.titleChanged();
}
onEnterFolderCreation() {
this.inputsValid = false;
}
onExitFolderCreation() {
this.inputValueChanged();
}
isValid() {
return this.inputsValid && this.folderId !== null;
}
saveDashboard() {
var inputs = this.inputs.map(input => {
return {
......@@ -153,6 +176,7 @@ export class DashboardImportCtrl {
dashboard: this.dash,
overwrite: true,
inputs: inputs,
folderId: this.folderId,
})
.then(res => {
this.$location.url(res.importedUrl);
......
......@@ -132,23 +132,26 @@ export class FolderPickerCtrl {
}
private loadInitialValue() {
if (this.initialFolderId && this.initialFolderId > 0) {
this.getOptions('').then(result => {
this.folder = _.find(result, { value: this.initialFolderId });
if (!this.folder) {
this.folder = { text: this.initialTitle, value: this.initialFolderId };
}
this.onFolderLoad();
});
} else {
if (this.initialTitle && this.initialFolderId === null) {
this.folder = { text: this.initialTitle, value: null };
} else {
this.folder = { text: this.rootName, value: 0 };
const resetFolder = { text: this.initialTitle, value: null };
const rootFolder = { text: this.rootName, value: 0 };
this.getOptions('').then(result => {
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) {
folder = rootFolder;
} else {
folder = result.length > 0 ? result[0] : resetFolder;
}
}
this.folder = folder;
this.onFolderLoad();
}
});
}
private onFolderLoad() {
......
......@@ -82,6 +82,20 @@
<div class="gf-form-inline">
<div class="gf-form gf-form--grow">
<folder-picker label-class="width-15"
initial-folder-id="ctrl.folderId"
initial-title="ctrl.initialFolderTitle"
on-change="ctrl.onFolderChange($folder)"
on-load="ctrl.onFolderChange($folder)"
enter-folder-creation="ctrl.onEnterFolderCreation()"
exit-folder-creation="ctrl.onExitFolderCreation()"
enable-create-new="true">
</folder-picker>
</div>
</div>
<div class="gf-form-inline">
<div class="gf-form gf-form--grow">
<span class="gf-form-label width-15">
Unique identifier (uid)
<info-popover mode="right-normal">
......@@ -132,10 +146,10 @@
</div>
<div class="gf-form-button-row">
<button type="button" class="btn btn-success width-12" ng-click="ctrl.saveDashboard()" ng-hide="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.inputsValid">
<button type="button" class="btn btn-success width-12" ng-click="ctrl.saveDashboard()" ng-hide="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.isValid()">
<i class="fa fa-save"></i> Import
</button>
<button type="button" class="btn btn-danger width-12" ng-click="ctrl.saveDashboard()" ng-show="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.inputsValid">
<button type="button" class="btn btn-danger width-12" ng-click="ctrl.saveDashboard()" ng-show="ctrl.nameExists || ctrl.uidExists" ng-disabled="!ctrl.isValid()">
<i class="fa fa-save"></i> Import (Overwrite)
</button>
<a class="btn btn-link" ng-click="ctrl.back()">Cancel</a>
......
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