diff --git a/pkg/api/index.go b/pkg/api/index.go index eb9e70bb404..c1945906624 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -90,12 +90,13 @@ func setIndexViewData(c *middleware.Context) (*dtos.IndexViewData, error) { if c.OrgRole == m.ROLE_ADMIN || c.OrgRole == m.ROLE_EDITOR { data.NavTree = append(data.NavTree, &dtos.NavLink{ Text: "Create", + Id: "create", Icon: "fa fa-fw fa-plus", Url: "#", Children: []*dtos.NavLink{ {Text: "Dashboard", Icon: "gicon gicon-dashboard-new", Url: setting.AppSubUrl + "/dashboard/new"}, {Text: "Folder", Icon: "gicon gicon-folder-new", Url: setting.AppSubUrl + "/dashboard/new/?editview=new-folder"}, - {Text: "Import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/new/?editview=import"}, + {Text: "Import", Id: "import", Icon: "gicon gicon-dashboard-import", Url: setting.AppSubUrl + "/dashboard/import"}, }, }) } diff --git a/public/app/core/routes/routes.ts b/public/app/core/routes/routes.ts index 68595da4296..d3ef3d51477 100644 --- a/public/app/core/routes/routes.ts +++ b/public/app/core/routes/routes.ts @@ -48,6 +48,11 @@ function setupAngularRoutes($routeProvider, $locationProvider) { reloadOnSearch: false, pageClass: 'page-dashboard', }) + .when('/dashboard/import', { + templateUrl: 'public/app/features/dashboard/partials/dashboardImport.html', + controller : 'DashboardImportCtrl', + controllerAs: 'ctrl', + }) .when('/datasources', { templateUrl: 'public/app/features/plugins/partials/ds_list.html', controller : 'DataSourcesCtrl', diff --git a/public/app/features/dashboard/all.ts b/public/app/features/dashboard/all.ts index b4f1f4e77f0..f2f2087a24e 100644 --- a/public/app/features/dashboard/all.ts +++ b/public/app/features/dashboard/all.ts @@ -15,7 +15,6 @@ import './unsavedChangesSrv'; import './unsaved_changes_modal'; import './timepicker/timepicker'; import './upload'; -import './import/dash_import'; import './export/export_modal'; import './export_data/export_data_modal'; import './ad_hoc_filters'; @@ -30,5 +29,7 @@ import './move_to_folder_modal/move_to_folder'; import coreModule from 'app/core/core_module'; import {DashboardListCtrl} from './dashboard_list_ctrl'; +import {DashboardImportCtrl} from './dashboard_import_ctrl'; coreModule.controller('DashboardListCtrl', DashboardListCtrl); +coreModule.controller('DashboardImportCtrl', DashboardImportCtrl); diff --git a/public/app/features/dashboard/dashboard_import_ctrl.ts b/public/app/features/dashboard/dashboard_import_ctrl.ts new file mode 100644 index 00000000000..9a630681145 --- /dev/null +++ b/public/app/features/dashboard/dashboard_import_ctrl.ts @@ -0,0 +1,163 @@ +import _ from 'lodash'; +import config from 'app/core/config'; + +export class DashboardImportCtrl { + navModel: any; + step: number; + jsonText: string; + parseError: string; + nameExists: boolean; + dash: any; + inputs: any[]; + inputsValid: boolean; + gnetUrl: string; + gnetError: string; + gnetInfo: any; + + /** @ngInject */ + constructor(private backendSrv, navModelSrv, private $location, private $scope, $routeParams) { + this.navModel = navModelSrv.getNav('create', 'import'); + + this.step = 1; + this.nameExists = false; + + // check gnetId in url + if ($routeParams.gnetId) { + this.gnetUrl = $routeParams.gnetId ; + this.checkGnetDashboard(); + } + } + + onUpload(dash) { + this.dash = dash; + this.dash.id = null; + this.step = 2; + this.inputs = []; + + if (this.dash.__inputs) { + for (let input of this.dash.__inputs) { + var inputModel = { + name: input.name, + label: input.label, + info: input.description, + value: input.value, + type: input.type, + pluginId: input.pluginId, + options: [] + }; + + if (input.type === 'datasource') { + this.setDatasourceOptions(input, inputModel); + } else if (!inputModel.info) { + inputModel.info = 'Specify a string constant'; + } + + this.inputs.push(inputModel); + } + } + + this.inputsValid = this.inputs.length === 0; + this.titleChanged(); + } + + setDatasourceOptions(input, inputModel) { + var sources = _.filter(config.datasources, val => { + return val.type === input.pluginId; + }); + + if (sources.length === 0) { + inputModel.info = "No data sources of type " + input.pluginName + " found"; + } else if (!inputModel.info) { + inputModel.info = "Select a " + input.pluginName + " data source"; + } + + inputModel.options = sources.map(val => { + return {text: val.name, value: val.name}; + }); + } + + inputValueChanged() { + this.inputsValid = true; + for (let input of this.inputs) { + if (!input.value) { + this.inputsValid = false; + } + } + } + + titleChanged() { + this.backendSrv.search({query: this.dash.title}).then(res => { + this.nameExists = false; + for (let hit of res) { + if (this.dash.title === hit.title) { + this.nameExists = true; + break; + } + } + }); + } + + saveDashboard() { + var inputs = this.inputs.map(input => { + return { + name: input.name, + type: input.type, + pluginId: input.pluginId, + value: input.value + }; + }); + + return this.backendSrv.post('api/dashboards/import', { + dashboard: this.dash, + overwrite: true, + inputs: inputs + }).then(res => { + this.$location.url('dashboard/' + res.importedUri); + this.$scope.dismiss(); + }); + } + + loadJsonText() { + try { + this.parseError = ''; + var dash = JSON.parse(this.jsonText); + this.onUpload(dash); + } catch (err) { + console.log(err); + this.parseError = err.message; + return; + } + } + + checkGnetDashboard() { + this.gnetError = ''; + + var match = /(^\d+$)|dashboards\/(\d+)/.exec(this.gnetUrl); + var dashboardId; + + if (match && match[1]) { + dashboardId = match[1]; + } else if (match && match[2]) { + dashboardId = match[2]; + } else { + this.gnetError = 'Could not find dashboard'; + } + + return this.backendSrv.get('api/gnet/dashboards/' + dashboardId).then(res => { + this.gnetInfo = res; + // store reference to grafana.com + res.json.gnetId = res.id; + this.onUpload(res.json); + }).catch(err => { + err.isHandled = true; + this.gnetError = err.data.message || err; + }); + } + + back() { + this.gnetUrl = ''; + this.step = 1; + this.gnetError = ''; + this.gnetInfo = ''; + } +} diff --git a/public/app/features/dashboard/partials/dashboardImport.html b/public/app/features/dashboard/partials/dashboardImport.html new file mode 100644 index 00000000000..be43078d32a --- /dev/null +++ b/public/app/features/dashboard/partials/dashboardImport.html @@ -0,0 +1,125 @@ + + +
+
+ +
+ +
+ +
Grafana.com Dashboard
+ +
+
+ +
+
+ +
+
+ +
Or paste JSON
+ +
+
+ +
+ + + + {{ctrl.parseError}} + +
+
+ +
+
+

+ Importing Dashboard from + Grafana.com +

+ +
+ + +
+
+ + +
+
+ +

+ Options +

+ +
+
+
+ + + +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ + +
+ +
+ + + +
+
+
+ +
+ + + Cancel +
+ +
+
diff --git a/public/app/features/dashboard/specs/dash_import_ctrl_specs.ts b/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts similarity index 52% rename from public/app/features/dashboard/specs/dash_import_ctrl_specs.ts rename to public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts index c541aca34b2..3270bb5a732 100644 --- a/public/app/features/dashboard/specs/dash_import_ctrl_specs.ts +++ b/public/app/features/dashboard/specs/dashboard_import_ctrl.jest.ts @@ -1,25 +1,24 @@ -import {describe, beforeEach, it, sinon, expect, angularMocks} from 'test/lib/common'; +import {DashboardImportCtrl} from '../dashboard_import_ctrl'; +import config from '../../../core/config'; -import {DashImportCtrl} from 'app/features/dashboard/import/dash_import'; -import config from 'app/core/config'; - -describe('DashImportCtrl', function() { +describe('DashboardImportCtrl', function() { var ctx: any = {}; - var backendSrv = { - search: sinon.stub().returns(Promise.resolve([])), - get: sinon.stub() - }; - beforeEach(angularMocks.module('grafana.core')); + let navModelSrv; + let backendSrv; - beforeEach(angularMocks.inject(($rootScope, $controller, $q) => { - ctx.$q = $q; - ctx.scope = $rootScope.$new(); - ctx.ctrl = $controller(DashImportCtrl, { - $scope: ctx.scope, - backendSrv: backendSrv, - }); - })); + beforeEach(() => { + navModelSrv = { + getNav: () => {} + }; + + backendSrv = { + search: jest.fn().mockReturnValue(Promise.resolve([])), + get: jest.fn() + }; + + ctx.ctrl = new DashboardImportCtrl(backendSrv, navModelSrv, {}, {}, {}); + }); describe('when uploading json', function() { beforeEach(function() { @@ -37,13 +36,13 @@ describe('DashImportCtrl', function() { }); it('should build input model', function() { - expect(ctx.ctrl.inputs.length).to.eql(1); - expect(ctx.ctrl.inputs[0].name).to.eql('ds'); - expect(ctx.ctrl.inputs[0].info).to.eql('Select a Test DB data source'); + expect(ctx.ctrl.inputs.length).toBe(1); + expect(ctx.ctrl.inputs[0].name).toBe('ds'); + expect(ctx.ctrl.inputs[0].info).toBe('Select a Test DB data source'); }); it('should set inputValid to false', function() { - expect(ctx.ctrl.inputsValid).to.eql(false); + expect(ctx.ctrl.inputsValid).toBe(false); }); }); @@ -51,7 +50,7 @@ describe('DashImportCtrl', function() { beforeEach(function() { ctx.ctrl.gnetUrl = 'http://grafana.com/dashboards/123'; // setup api mock - backendSrv.get = sinon.spy(() => { + backendSrv.get = jest.fn(() => { return Promise.resolve({ json: {} }); @@ -60,7 +59,7 @@ describe('DashImportCtrl', function() { }); it('should call gnet api with correct dashboard id', function() { - expect(backendSrv.get.getCall(0).args[0]).to.eql('api/gnet/dashboards/123'); + expect(backendSrv.get.mock.calls[0][0]).toBe('api/gnet/dashboards/123'); }); }); @@ -68,7 +67,7 @@ describe('DashImportCtrl', function() { beforeEach(function() { ctx.ctrl.gnetUrl = '2342'; // setup api mock - backendSrv.get = sinon.spy(() => { + backendSrv.get = jest.fn(() => { return Promise.resolve({ json: {} }); @@ -77,10 +76,8 @@ describe('DashImportCtrl', function() { }); it('should call gnet api with correct dashboard id', function() { - expect(backendSrv.get.getCall(0).args[0]).to.eql('api/gnet/dashboards/2342'); + expect(backendSrv.get.mock.calls[0][0]).toBe('api/gnet/dashboards/2342'); }); }); }); - -