Commit 9aa488c0 by bergquist

Merge remote-tracking branch 'origin/7883_frontend_step' into 7883_backend

* origin/7883_frontend_step:
  dashboards: make scripted dashboards work using the old legacy urls
  dashboards: redirect from old url used to load dashboard to new url
  dashboards: add new default frontend route for rendering a dashboard panel
  dashboards: fix links to recently viewed and starred dashboards
  dashboards: use new *url* prop from dashboard search for linking to dashboards
  dashboards: when saving dashboard redirect if url changes
  dashboards: add new default frontend route for loading a dashboard
  dashboards: return url in response to save dashboard. #7883
parents 16a16428 57edf890
......@@ -15,6 +15,8 @@ func (hs *HttpServer) registerRoutes() {
reqGrafanaAdmin := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true, ReqGrafanaAdmin: true})
reqEditorRole := middleware.RoleAuth(m.ROLE_EDITOR, m.ROLE_ADMIN)
reqOrgAdmin := middleware.RoleAuth(m.ROLE_ADMIN)
redirectFromLegacyDashboardUrl := middleware.RedirectFromLegacyDashboardUrl()
redirectFromLegacyDashboardSoloUrl := middleware.RedirectFromLegacyDashboardSoloUrl()
quota := middleware.Quota
bind := binding.Bind
......@@ -63,9 +65,13 @@ func (hs *HttpServer) registerRoutes() {
r.Get("/plugins/:id/edit", reqSignedIn, Index)
r.Get("/plugins/:id/page/:page", reqSignedIn, Index)
r.Get("/dashboard/*", reqSignedIn, Index)
r.Get("/d/:uid/:slug", reqSignedIn, Index)
r.Get("/dashboard/db/:slug", reqSignedIn, redirectFromLegacyDashboardUrl, Index)
r.Get("/dashboard/script/*", reqSignedIn, Index)
r.Get("/dashboard-solo/snapshot/*", Index)
r.Get("/dashboard-solo/*", reqSignedIn, Index)
r.Get("/d-solo/:uid/:slug", reqSignedIn, Index)
r.Get("/dashboard-solo/db/:slug", reqSignedIn, redirectFromLegacyDashboardSoloUrl, Index)
r.Get("/dashboard-solo/script/*", reqSignedIn, Index)
r.Get("/import/dashboard", reqSignedIn, Index)
r.Get("/dashboards/", reqSignedIn, Index)
r.Get("/dashboards/*", reqSignedIn, Index)
......
......@@ -238,8 +238,22 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
}
var url string
if dash.IsFolder {
url = m.GetFolderUrl(dashboard.Uid, dashboard.Slug)
} else {
url = m.GetDashboardUrl(dashboard.Uid, dashboard.Slug)
}
c.TimeRequest(metrics.M_Api_Dashboard_Save)
return Json(200, util.DynMap{"status": "success", "slug": dashboard.Slug, "version": dashboard.Version, "id": dashboard.Id, "uid": dashboard.Uid})
return Json(200, util.DynMap{
"status": "success",
"slug": dashboard.Slug,
"version": dashboard.Version,
"id": dashboard.Id,
"uid": dashboard.Uid,
"url": url,
})
}
func GetHomeDashboard(c *middleware.Context) Response {
......
......@@ -180,13 +180,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
CallPostDashboard(sc)
So(sc.resp.Code, ShouldEqual, 200)
result := sc.ToJson()
So(result.Get("status").MustString(), ShouldEqual, "success")
So(result.Get("id").MustInt64(), ShouldBeGreaterThan, 0)
So(result.Get("uid").MustString(), ShouldNotBeNil)
So(result.Get("slug").MustString(), ShouldNotBeNil)
CallPostDashboardShouldReturnSuccess(sc)
})
Convey("When saving a dashboard folder in another folder", func() {
......@@ -423,13 +417,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
CallPostDashboard(sc)
So(sc.resp.Code, ShouldEqual, 200)
result := sc.ToJson()
So(result.Get("status").MustString(), ShouldEqual, "success")
So(result.Get("id").MustInt64(), ShouldBeGreaterThan, 0)
So(result.Get("uid").MustString(), ShouldNotBeNil)
So(result.Get("slug").MustString(), ShouldNotBeNil)
CallPostDashboardShouldReturnSuccess(sc)
})
})
......@@ -544,13 +532,7 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
postDashboardScenario("When calling POST on", "/api/dashboards", "/api/dashboards", role, cmd, func(sc *scenarioContext) {
CallPostDashboard(sc)
So(sc.resp.Code, ShouldEqual, 200)
result := sc.ToJson()
So(result.Get("status").MustString(), ShouldEqual, "success")
So(result.Get("id").MustInt64(), ShouldBeGreaterThan, 0)
So(result.Get("uid").MustString(), ShouldNotBeNil)
So(result.Get("slug").MustString(), ShouldNotBeNil)
CallPostDashboardShouldReturnSuccess(sc)
})
})
......@@ -678,6 +660,18 @@ func CallPostDashboard(sc *scenarioContext) {
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
}
func CallPostDashboardShouldReturnSuccess(sc *scenarioContext) {
CallPostDashboard(sc)
So(sc.resp.Code, ShouldEqual, 200)
result := sc.ToJson()
So(result.Get("status").MustString(), ShouldEqual, "success")
So(result.Get("id").MustInt64(), ShouldBeGreaterThan, 0)
So(result.Get("uid").MustString(), ShouldNotBeNil)
So(result.Get("slug").MustString(), ShouldNotBeNil)
So(result.Get("url").MustString(), ShouldNotBeNil)
}
func postDashboardScenario(desc string, url string, routePattern string, role m.RoleType, cmd m.SaveDashboardCommand, fn scenarioFunc) {
Convey(desc+" "+url, func() {
defer bus.ClearBusHandlers()
......
package middleware
import (
"strings"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
"gopkg.in/macaron.v1"
)
func getDashboardUrlBySlug(orgId int64, slug string) (string, error) {
query := m.GetDashboardQuery{Slug: slug, OrgId: orgId}
if err := bus.Dispatch(&query); err != nil {
return "", m.ErrDashboardNotFound
}
return m.GetDashboardUrl(query.Result.Uid, query.Result.Slug), nil
}
func RedirectFromLegacyDashboardUrl() macaron.Handler {
return func(c *Context) {
slug := c.Params("slug")
if slug != "" {
if url, err := getDashboardUrlBySlug(c.OrgId, slug); err == nil {
c.Redirect(url, 301)
return
}
}
}
}
func RedirectFromLegacyDashboardSoloUrl() macaron.Handler {
return func(c *Context) {
slug := c.Params("slug")
if slug != "" {
if url, err := getDashboardUrlBySlug(c.OrgId, slug); err == nil {
url = strings.Replace(url, "/d/", "/d-solo/", 1)
c.Redirect(url, 301)
return
}
}
}
}
package middleware
import (
"strings"
"testing"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
. "github.com/smartystreets/goconvey/convey"
)
func TestMiddlewareDashboardRedirect(t *testing.T) {
Convey("Given the dashboard redirect middleware", t, func() {
bus.ClearBusHandlers()
redirectFromLegacyDashboardUrl := RedirectFromLegacyDashboardUrl()
redirectFromLegacyDashboardSoloUrl := RedirectFromLegacyDashboardSoloUrl()
fakeDash := m.NewDashboard("Child dash")
fakeDash.Id = 1
fakeDash.FolderId = 1
fakeDash.HasAcl = false
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
query.Result = fakeDash
return nil
})
middlewareScenario("GET dashboard by legacy url", func(sc *scenarioContext) {
sc.m.Get("/dashboard/db/:slug", redirectFromLegacyDashboardUrl, sc.defaultHandler)
sc.fakeReqWithParams("GET", "/dashboard/db/dash", map[string]string{}).exec()
Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() {
So(sc.resp.Code, ShouldEqual, 301)
redirectUrl, _ := sc.resp.Result().Location()
So(redirectUrl.Path, ShouldEqual, m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug))
})
})
middlewareScenario("GET dashboard solo by legacy url", func(sc *scenarioContext) {
sc.m.Get("/dashboard-solo/db/:slug", redirectFromLegacyDashboardSoloUrl, sc.defaultHandler)
sc.fakeReqWithParams("GET", "/dashboard-solo/db/dash", map[string]string{}).exec()
Convey("Should redirect to new dashboard url with a 301 Moved Permanently", func() {
So(sc.resp.Code, ShouldEqual, 301)
redirectUrl, _ := sc.resp.Result().Location()
expectedUrl := m.GetDashboardUrl(fakeDash.Uid, fakeDash.Slug)
expectedUrl = strings.Replace(expectedUrl, "/d/", "/d-solo/", 1)
So(redirectUrl.Path, ShouldEqual, expectedUrl)
})
})
})
}
......@@ -399,6 +399,20 @@ func (sc *scenarioContext) fakeReq(method, url string) *scenarioContext {
return sc
}
func (sc *scenarioContext) fakeReqWithParams(method, url string, queryParams map[string]string) *scenarioContext {
sc.resp = httptest.NewRecorder()
req, err := http.NewRequest(method, url, nil)
q := req.URL.Query()
for k, v := range queryParams {
q.Add(k, v)
}
req.URL.RawQuery = q.Encode()
So(err, ShouldBeNil)
sc.req = req
return sc
}
func (sc *scenarioContext) handler(fn handlerFunc) *scenarioContext {
sc.handlerFunc = fn
return sc
......
......@@ -225,6 +225,10 @@ export class BackendSrv {
return this.get('/api/dashboards/' + type + '/' + slug);
}
getDashboardByUid(uid: string) {
return this.get(`/api/dashboards/uid/${uid}`);
}
saveDashboard(dash, options) {
options = options || {};
......
......@@ -41,10 +41,7 @@ export class SearchSrv {
.map(orderId => {
return _.find(result, { id: orderId });
})
.filter(hit => hit && !hit.isStarred)
.map(hit => {
return this.transformToViewModel(hit);
});
.filter(hit => hit && !hit.isStarred);
});
}
......@@ -81,17 +78,12 @@ export class SearchSrv {
score: -2,
expanded: this.starredIsOpen,
toggle: this.toggleStarred.bind(this),
items: result.map(this.transformToViewModel),
items: result,
};
}
});
}
private transformToViewModel(hit) {
hit.url = 'dashboard/db/' + hit.slug;
return hit;
}
search(options) {
let sections: any = {};
let promises = [];
......@@ -181,7 +173,7 @@ export class SearchSrv {
}
section.expanded = true;
section.items.push(this.transformToViewModel(hit));
section.items.push(hit);
}
}
......@@ -198,7 +190,7 @@ export class SearchSrv {
};
return this.backendSrv.search(query).then(results => {
section.items = _.map(results, this.transformToViewModel);
section.items = results;
return Promise.resolve(section);
});
}
......
......@@ -35,18 +35,18 @@ export class DashboardLoaderSrv {
};
}
loadDashboard(type, slug) {
loadDashboard(type, slug, uid) {
var promise;
if (type === 'script') {
promise = this._loadScriptedDashboard(slug);
} else if (type === 'snapshot') {
promise = this.backendSrv.get('/api/snapshots/' + this.$routeParams.slug).catch(() => {
promise = this.backendSrv.get('/api/snapshots/' + slug).catch(() => {
return this._dashboardLoadFailed('Snapshot not found', true);
});
} else {
promise = this.backendSrv
.getDashboard(this.$routeParams.type, this.$routeParams.slug)
.getDashboardByUid(uid)
.then(result => {
if (result.meta.isFolder) {
this.$rootScope.appEvent('alert-error', ['Dashboard not found']);
......
......@@ -73,9 +73,8 @@ export class DashboardSrv {
postSave(clone, data) {
this.dash.version = data.version;
var dashboardUrl = '/dashboard/db/' + data.slug;
if (dashboardUrl !== this.$location.path()) {
this.$location.url(dashboardUrl);
if (data.url !== this.$location.path()) {
this.$location.url(data.url);
}
this.$rootScope.appEvent('dashboard-saved', this.dash);
......
......@@ -74,6 +74,7 @@ export class ShareModalCtrl {
$scope.shareUrl = linkSrv.addParamsToUrl(baseUrl, params);
var soloUrl = baseUrl.replace(config.appSubUrl + '/dashboard/', config.appSubUrl + '/dashboard-solo/');
soloUrl = soloUrl.replace(config.appSubUrl + '/d/', config.appSubUrl + '/d-solo/');
delete params.fullscreen;
delete params.edit;
soloUrl = linkSrv.addParamsToUrl(soloUrl, params);
......@@ -84,6 +85,7 @@ export class ShareModalCtrl {
config.appSubUrl + '/dashboard-solo/',
config.appSubUrl + '/render/dashboard-solo/'
);
$scope.imageUrl = $scope.imageUrl.replace(config.appSubUrl + '/d-solo/', config.appSubUrl + '/render/d-solo/');
$scope.imageUrl += '&width=1000';
$scope.imageUrl += '&height=500';
$scope.imageUrl += '&tz=UTC' + encodeURIComponent(moment().format('Z'));
......
......@@ -43,12 +43,23 @@ describe('ShareModalCtrl', function() {
});
it('should generate render url', function() {
ctx.$location.$$absUrl = 'http://dashboards.grafana.com/dashboard/db/my-dash';
ctx.$location.$$absUrl = 'http://dashboards.grafana.com/d/abcdefghi/my-dash';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/dashboard-solo/db/my-dash';
var base = 'http://dashboards.grafana.com/render/d-solo/abcdefghi/my-dash';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).to.contain(base + params);
});
it('should generate render url for scripted dashboard', function() {
ctx.$location.$$absUrl = 'http://dashboards.grafana.com/dashboard/script/my-dash.js';
ctx.scope.panel = { id: 22 };
ctx.scope.init();
var base = 'http://dashboards.grafana.com/render/dashboard-solo/script/my-dash.js';
var params = '?from=1000&to=2000&orgId=1&panelId=22&width=1000&height=500&tz=UTC';
expect(ctx.scope.imageUrl).to.contain(base + params);
});
......
......@@ -2,7 +2,7 @@ import angular from 'angular';
export class SoloPanelCtrl {
/** @ngInject */
constructor($scope, $routeParams, $location, dashboardLoaderSrv, contextSrv) {
constructor($scope, $routeParams, $location, dashboardLoaderSrv, contextSrv, backendSrv) {
var panelId;
$scope.init = function() {
......@@ -13,7 +13,17 @@ export class SoloPanelCtrl {
$scope.onAppEvent('dashboard-initialized', $scope.initPanelScope);
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug).then(function(result) {
// if no uid, redirect to new route based on slug
if (!($routeParams.type === 'script' || $routeParams.type === 'snapshot') && !$routeParams.uid) {
backendSrv.get(`/api/dashboards/db/${$routeParams.slug}`).then(res => {
if (res) {
$location.path(res.meta.url.replace('/d/', '/d-solo/'));
}
});
return;
}
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug, $routeParams.uid).then(function(result) {
result.meta.soloMode = true;
$scope.initDashboard(result, $scope);
});
......
......@@ -4,7 +4,7 @@
{{group.header}}
</h6>
<div class="dashlist-item" ng-repeat="dash in group.list">
<a class="dashlist-link dashlist-link-{{dash.type}}" href="dashboard/{{dash.uri}}">
<a class="dashlist-link dashlist-link-{{dash.type}}" href="{{dash.url}}">
<span class="dashlist-title">
{{dash.title}}
</span>
......
......@@ -5,7 +5,7 @@ export class LoadDashboardCtrl {
constructor($scope, $routeParams, dashboardLoaderSrv, backendSrv, $location) {
$scope.appEvent('dashboard-fetch-start');
if (!$routeParams.slug) {
if (!$routeParams.uid && !$routeParams.slug) {
backendSrv.get('/api/dashboards/home').then(function(homeDash) {
if (homeDash.redirectUri) {
$location.path('dashboard/' + homeDash.redirectUri);
......@@ -18,7 +18,17 @@ export class LoadDashboardCtrl {
return;
}
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug).then(function(result) {
// if no uid, redirect to new route based on slug
if (!($routeParams.type === 'script' || $routeParams.type === 'snapshot') && !$routeParams.uid) {
backendSrv.get(`/api/dashboards/db/${$routeParams.slug}`).then(res => {
if (res) {
$location.path(res.meta.url);
}
});
return;
}
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug, $routeParams.uid).then(function(result) {
if ($routeParams.keepRows) {
result.meta.keepRows = true;
}
......
......@@ -14,12 +14,24 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
reloadOnSearch: false,
pageClass: 'page-dashboard',
})
.when('/d/:uid/:slug', {
templateUrl: 'public/app/partials/dashboard.html',
controller: 'LoadDashboardCtrl',
reloadOnSearch: false,
pageClass: 'page-dashboard',
})
.when('/dashboard/:type/:slug', {
templateUrl: 'public/app/partials/dashboard.html',
controller: 'LoadDashboardCtrl',
reloadOnSearch: false,
pageClass: 'page-dashboard',
})
.when('/d-solo/:uid/:slug', {
templateUrl: 'public/app/features/panel/partials/soloPanel.html',
controller: 'SoloPanelCtrl',
reloadOnSearch: false,
pageClass: 'page-dashboard',
})
.when('/dashboard-solo/:type/:slug', {
templateUrl: 'public/app/features/panel/partials/soloPanel.html',
controller: 'SoloPanelCtrl',
......
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