Commit c3708b30 by Torkel Ödegaard

feat(import): import directly from grafana.net id/url now works

parent 22a7eaf2
...@@ -5,8 +5,10 @@ import ( ...@@ -5,8 +5,10 @@ import (
"net" "net"
"net/http" "net/http"
"net/http/httputil" "net/http/httputil"
"net/url"
"time" "time"
"github.com/Unknwon/log"
"github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
...@@ -23,12 +25,15 @@ var gNetProxyTransport = &http.Transport{ ...@@ -23,12 +25,15 @@ var gNetProxyTransport = &http.Transport{
} }
func ReverseProxyGnetReq(proxyPath string) *httputil.ReverseProxy { func ReverseProxyGnetReq(proxyPath string) *httputil.ReverseProxy {
url, _ := url.Parse(setting.GrafanaNetUrl)
director := func(req *http.Request) { director := func(req *http.Request) {
req.URL.Scheme = "https" req.URL.Scheme = url.Scheme
req.URL.Host = "grafana.net" req.URL.Host = url.Host
req.Host = "grafana.net" req.Host = url.Host
req.URL.Path = util.JoinUrlFragments(setting.GrafanaNetUrl+"/api", proxyPath) req.URL.Path = util.JoinUrlFragments(url.Path+"/api", proxyPath)
log.Info("Url: %v", req.URL.Path)
// clear cookie headers // clear cookie headers
req.Header.Del("Cookie") req.Header.Del("Cookie")
......
...@@ -17,7 +17,7 @@ define([ ...@@ -17,7 +17,7 @@ define([
'./importCtrl', './importCtrl',
'./impression_store', './impression_store',
'./upload', './upload',
'./import/import', './import/dash_import',
'./export/export_modal', './export/export_modal',
'./dash_list_ctrl', './dash_list_ctrl',
], function () {}); ], function () {});
...@@ -15,16 +15,6 @@ ...@@ -15,16 +15,6 @@
You can share dashboards on <a class="external-link" href="https://grafana.net">Grafana.net</a> You can share dashboards on <a class="external-link" href="https://grafana.net">Grafana.net</a>
</p> </p>
<div class="gf-form-group">
<div class="gf-form">
<label class="gf-form-label width-8">Title</label>
<input type="text" class="gf-form-input" ng-model="ctrl.dash.title" ng-change="ctrl.titleChanged()">
<label class="gf-form-label text-success" ng-show="ctrl.dash.title">
<i class="fa fa-check"></i>
</label>
</div>
</div>
<div class="gf-form-button-row"> <div class="gf-form-button-row">
<button type="button" class="btn gf-form-btn width-10 btn-success" ng-click="ctrl.save()"> <button type="button" class="btn gf-form-btn width-10 btn-success" ng-click="ctrl.save()">
<i class="fa fa-save"></i> Save to file <i class="fa fa-save"></i> Save to file
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<div class="gf-form-group"> <div class="gf-form-group">
<div class="gf-form"> <div class="gf-form">
<input type="text" class="gf-form-input" ng-ctrl="ctrl.grafanaNetUrl" placeholder="Paste Grafana.net dashboard url or id" ng-change="ctrl.checkGnetDashboard()"></textarea> <input type="text" class="gf-form-input" ng-model="ctrl.gnetUrl" placeholder="Paste Grafana.net dashboard url or id" ng-blur="ctrl.checkGnetDashboard()"></textarea>
</div> </div>
<div class="gf-form" ng-if="ctrl.gnetError"> <div class="gf-form" ng-if="ctrl.gnetError">
<label class="gf-form-label text-warning"> <label class="gf-form-label text-warning">
...@@ -50,6 +50,21 @@ ...@@ -50,6 +50,21 @@
</div> </div>
<div ng-if="ctrl.step === 2"> <div ng-if="ctrl.step === 2">
<h3 class="section-heading" ng-if="ctrl.dash.gnetId">
Importing Dashboard from
<a href="https://grafana.net/dashboards/{{ctrl.dash.gnetId}}" class="external-link" target="_blank">Grafana.net</a>
</h3>
<div class="gf-form-group">
<div class="gf-form">
<label class="gf-form-label width-15">Published by</label>
<label class="gf-form-label width-15">{{ctrl.gnetInfo.orgName}}</label>
</div>
<div class="gf-form">
<label class="gf-form-label width-15">Updated on</label>
<label class="gf-form-label width-15">{{ctrl.gnetInfo.updatedAt | date : 'yyyy-MM-dd HH:mm:ss'}}</label>
</div>
</div>
<h3 class="section-heading"> <h3 class="section-heading">
Options Options
</h3> </h3>
...@@ -57,7 +72,7 @@ ...@@ -57,7 +72,7 @@
<div class="gf-form-group"> <div class="gf-form-group">
<div class="gf-form-inline"> <div class="gf-form-inline">
<div class="gf-form gf-form--grow"> <div class="gf-form gf-form--grow">
<label class="gf-form-label width-15">Title</label> <label class="gf-form-label width-15">Name</label>
<input type="text" class="gf-form-input" ng-model="ctrl.dash.title" give-focus="true" ng-change="ctrl.titleChanged()" ng-class="{'validation-error': ctrl.nameExists}"> <input type="text" class="gf-form-input" ng-model="ctrl.dash.title" give-focus="true" ng-change="ctrl.titleChanged()" ng-class="{'validation-error': ctrl.nameExists}">
<label class="gf-form-label text-success" ng-if="!ctrl.nameExists"> <label class="gf-form-label text-success" ng-if="!ctrl.nameExists">
<i class="fa fa-check"></i> <i class="fa fa-check"></i>
...@@ -76,32 +91,33 @@ ...@@ -76,32 +91,33 @@
<div ng-repeat="input in ctrl.inputs"> <div ng-repeat="input in ctrl.inputs">
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-15">{{input.label}}</label> <label class="gf-form-label width-15">
{{input.label}}
<info-popover mode="right-normal">
{{input.info}}
</info-popover>
</label>
<div class="gf-form-select-wrapper" style="width: 100%"> <div class="gf-form-select-wrapper" style="width: 100%">
<select class="gf-form-input" ng-model="input.value" ng-options="v.value as v.text for v in input.options" ng-change="ctrl.inputValueChanged()"></select> <select class="gf-form-input" ng-model="input.value" ng-options="v.value as v.text for v in input.options" ng-change="ctrl.inputValueChanged()">
<option value="" ng-hide="input.value">{{input.info}}</option>
</select>
</div> </div>
<label class="gf-form-label text-success" ng-show="input.value"> <label class="gf-form-label text-success" ng-show="input.value">
<i class="fa fa-check"></i> <i class="fa fa-check"></i>
</label> </label>
</div> </div>
<div class="gf-form offset-width-15 gf-form--grow">
<label class="gf-form-label gf-form-label--grow" ng-show="input.info">
<i class="fa fa-info-circle"></i>
{{input.info}}
</label>
<label class="gf-form-label gf-form-label--grow" ng-show="input.error">
<i class="fa fa-info-circle"></i>
{{input.info}}
</label>
</div>
</div> </div>
</div> </div>
<div class="gf-form-button-row"> <div class="gf-form-button-row">
<button type="button" class="btn gf-form-btn width-10" ng-click="ctrl.saveDashboard()" ng-class="{'btn-danger': ctrl.nameExists, 'btn-success': !ctrl.nameExists}" ng-disabled="!ctrl.inputsValid"> <button type="button" class="btn gf-form-btn btn-success width-10" ng-click="ctrl.saveDashboard()" ng-hide="ctrl.nameExists" ng-disabled="!ctrl.inputsValid">
<i class="fa fa-save"></i> Save &amp; Open <i class="fa fa-save"></i> Save &amp; Open
</button> </button>
<button type="button" class="btn gf-form-btn btn-danger width-10" ng-click="ctrl.saveDashboard()" ng-show="ctrl.nameExists" ng-disabled="!ctrl.inputsValid">
<i class="fa fa-save"></i> Overwrite &amp; Open
</button>
<a class="btn btn-link" ng-click="dismiss()">Cancel</a> <a class="btn btn-link" ng-click="dismiss()">Cancel</a>
<a class="btn btn-link" ng-click="ctrl.back()">Back</a>
</div> </div>
</div> </div>
......
...@@ -14,11 +14,20 @@ export class DashImportCtrl { ...@@ -14,11 +14,20 @@ export class DashImportCtrl {
dash: any; dash: any;
inputs: any[]; inputs: any[];
inputsValid: boolean; inputsValid: boolean;
gnetUrl: string;
gnetError: string;
gnetInfo: any;
/** @ngInject */ /** @ngInject */
constructor(private backendSrv, private $location, private $scope) { constructor(private backendSrv, private $location, private $scope, private $routeParams) {
this.step = 1; this.step = 1;
this.nameExists = false; this.nameExists = false;
// check gnetId in url
if ($routeParams.gnetId) {
this.gnetUrl = $routeParams.gnetId ;
this.checkGnetDashboard();
}
} }
onUpload(dash) { onUpload(dash) {
...@@ -121,6 +130,37 @@ export class DashImportCtrl { ...@@ -121,6 +130,37 @@ export class DashImportCtrl {
} }
} }
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.net
res.json.gnetId = dashboardId;
this.onUpload(res.json);
}).catch(err => {
this.gnetError = err.message || err;
});
}
back() {
this.gnetUrl = '';
this.step = 1;
this.gnetError = '';
this.gnetInfo = '';
}
} }
export function dashImportDirective() { export function dashImportDirective() {
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<div class="gf-form-group section"> <div class="gf-form-group section">
<h5 class="section-heading">Details</h5> <h5 class="section-heading">Details</h5>
<div class="gf-form"> <div class="gf-form">
<label class="gf-form-label width-7">Title</label> <label class="gf-form-label width-7">Name</label>
<input type="text" class="gf-form-input width-30" ng-model='dashboard.title'></input> <input type="text" class="gf-form-input width-30" ng-model='dashboard.title'></input>
</div> </div>
<div class="gf-form"> <div class="gf-form">
......
...@@ -7,6 +7,7 @@ describe('DashImportCtrl', function() { ...@@ -7,6 +7,7 @@ describe('DashImportCtrl', function() {
var ctx: any = {}; var ctx: any = {};
var backendSrv = { var backendSrv = {
search: sinon.stub().returns(Promise.resolve([])), search: sinon.stub().returns(Promise.resolve([])),
get: sinon.stub()
}; };
beforeEach(angularMocks.module('grafana.core')); beforeEach(angularMocks.module('grafana.core'));
...@@ -20,7 +21,7 @@ describe('DashImportCtrl', function() { ...@@ -20,7 +21,7 @@ describe('DashImportCtrl', function() {
}); });
})); }));
describe('when upload json', function() { describe('when uploading json', function() {
beforeEach(function() { beforeEach(function() {
config.datasources = { config.datasources = {
ds: { ds: {
...@@ -37,14 +38,47 @@ describe('DashImportCtrl', function() { ...@@ -37,14 +38,47 @@ describe('DashImportCtrl', function() {
it('should build input model', function() { it('should build input model', function() {
expect(ctx.ctrl.inputs.length).to.eql(1); expect(ctx.ctrl.inputs.length).to.eql(1);
expect(ctx.ctrl.inputs[0].label).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');
}); });
it('should set inputValid to false', function() { it('should set inputValid to false', function() {
expect(ctx.ctrl.inputsValid).to.eql(false); expect(ctx.ctrl.inputsValid).to.eql(false);
}); });
});
describe('when specifing grafana.net url', function() {
beforeEach(function() {
ctx.ctrl.gnetUrl = 'http://grafana.net/dashboards/123';
// setup api mock
backendSrv.get = sinon.spy(() => {
return Promise.resolve({
});
});
ctx.ctrl.checkGnetDashboard();
});
it('should call gnet api with correct dashboard id', function() {
expect(backendSrv.get.getCall(0).args[0]).to.eql('api/gnet/dashboards/123');
});
});
describe('when specifing dashbord id', function() {
beforeEach(function() {
ctx.ctrl.gnetUrl = '2342';
// setup api mock
backendSrv.get = sinon.spy(() => {
return Promise.resolve({
});
});
ctx.ctrl.checkGnetDashboard();
});
it('should call gnet api with correct dashboard id', function() {
expect(backendSrv.get.getCall(0).args[0]).to.eql('api/gnet/dashboards/2342');
}); });
});
}); });
...@@ -158,6 +158,10 @@ $gf-form-margin: 0.25rem; ...@@ -158,6 +158,10 @@ $gf-form-margin: 0.25rem;
color: transparent; color: transparent;
text-shadow: 0 0 0 $text-color; text-shadow: 0 0 0 $text-color;
} }
&.ng-empty {
color: $text-color-weak;
}
} }
&:after { &:after {
......
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