Commit d8d82c17 by Marcus Efraimsson Committed by GitHub

Merge pull request #10706 from grafana/7883_frontend_step2

WIP: Dashboard & Persistent urls - Frontend Step 2
parents b135df9a cdf2fd64
......@@ -249,6 +249,7 @@ func (hs *HttpServer) registerRoutes() {
// Dashboard
apiRoute.Group("/dashboards", func(dashboardRoute RouteRegister) {
dashboardRoute.Get("/uid/:uid", wrap(GetDashboard))
dashboardRoute.Delete("/uid/:uid", wrap(DeleteDashboardByUid))
dashboardRoute.Get("/db/:slug", wrap(GetDashboard))
dashboardRoute.Delete("/db/:slug", wrap(DeleteDashboard))
......
......@@ -141,6 +141,16 @@ func getDashboardHelper(orgId int64, slug string, id int64, uid string) (*m.Dash
}
func DeleteDashboard(c *middleware.Context) Response {
query := m.GetDashboardsBySlugQuery{OrgId: c.OrgId, Slug: c.Params(":slug")}
if err := bus.Dispatch(&query); err != nil {
return ApiError(500, "Failed to retrieve dashboards by slug", err)
}
if len(query.Result) > 1 {
return Json(412, util.DynMap{"status": "multiple-slugs-exists", "message": m.ErrDashboardsWithSameSlugExists.Error()})
}
dash, rsp := getDashboardHelper(c.OrgId, c.Params(":slug"), 0, "")
if rsp != nil {
return rsp
......@@ -160,6 +170,26 @@ func DeleteDashboard(c *middleware.Context) Response {
return Json(200, resp)
}
func DeleteDashboardByUid(c *middleware.Context) Response {
dash, rsp := getDashboardHelper(c.OrgId, "", 0, c.Params(":uid"))
if rsp != nil {
return rsp
}
guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
if canSave, err := guardian.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err)
}
cmd := m.DeleteDashboardCommand{OrgId: c.OrgId, Id: dash.Id}
if err := bus.Dispatch(&cmd); err != nil {
return ApiError(500, "Failed to delete dashboard", err)
}
var resp = map[string]interface{}{"title": dash.Title}
return Json(200, resp)
}
func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
cmd.OrgId = c.OrgId
cmd.UserId = c.UserId
......@@ -440,6 +470,7 @@ func RestoreDashboardVersion(c *middleware.Context, apiCmd dtos.RestoreDashboard
saveCmd.UserId = c.UserId
saveCmd.Dashboard = version.Data
saveCmd.Dashboard.Set("version", dash.Version)
saveCmd.Dashboard.Set("uid", dash.Uid)
saveCmd.Message = fmt.Sprintf("Restored from version %d", version.Version)
return PostDashboard(c, saveCmd)
......
......@@ -39,6 +39,12 @@ func TestDashboardApiEndpoint(t *testing.T) {
fakeDash.FolderId = 1
fakeDash.HasAcl = false
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
dashboards := []*m.Dashboard{fakeDash}
query.Result = dashboards
return nil
})
var getDashboardQueries []*m.GetDashboardQuery
bus.AddHandler("test", func(query *m.GetDashboardQuery) error {
......@@ -117,6 +123,15 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
CallGetDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 403)
......@@ -173,6 +188,15 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
CallGetDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 200)
......@@ -218,6 +242,12 @@ func TestDashboardApiEndpoint(t *testing.T) {
fakeDash.HasAcl = true
setting.ViewersCanEdit = false
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
dashboards := []*m.Dashboard{fakeDash}
query.Result = dashboards
return nil
})
aclMockResp := []*m.DashboardAclInfoDTO{
{
DashboardId: 1,
......@@ -299,6 +329,15 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
CallGetDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 403)
......@@ -353,6 +392,15 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
CallGetDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 403)
......@@ -418,6 +466,15 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
CallGetDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 200)
......@@ -482,6 +539,15 @@ func TestDashboardApiEndpoint(t *testing.T) {
So(getDashboardQueries[0].Slug, ShouldEqual, "child-dash")
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
})
})
})
Convey("When user is an Org Viewer but has an admin permission", func() {
......@@ -533,6 +599,15 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
So(sc.resp.Code, ShouldEqual, 200)
Convey("Should lookup dashboard by uid", func() {
So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
CallGetDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 200)
......@@ -595,6 +670,15 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/uid/abcdefghi", "/api/dashboards/uid/:uid", role, func(sc *scenarioContext) {
CallDeleteDashboardByUid(sc)
So(sc.resp.Code, ShouldEqual, 403)
Convey("Should lookup dashboard by uid", func() {
So(getDashboardQueries[0].Uid, ShouldEqual, "abcdefghi")
})
})
loggedInUserScenarioWithRole("When calling GET on", "GET", "/api/dashboards/id/2/versions/1", "/api/dashboards/id/:dashboardId/versions/:id", role, func(sc *scenarioContext) {
CallGetDashboardVersion(sc)
So(sc.resp.Code, ShouldEqual, 403)
......@@ -611,6 +695,37 @@ func TestDashboardApiEndpoint(t *testing.T) {
})
})
})
Convey("Given two dashboards with the same title in different folders", t, func() {
dashOne := m.NewDashboard("dash")
dashOne.Id = 2
dashOne.FolderId = 1
dashOne.HasAcl = false
dashTwo := m.NewDashboard("dash")
dashTwo.Id = 4
dashTwo.FolderId = 3
dashTwo.HasAcl = false
bus.AddHandler("test", func(query *m.GetDashboardsBySlugQuery) error {
dashboards := []*m.Dashboard{dashOne, dashTwo}
query.Result = dashboards
return nil
})
role := m.ROLE_EDITOR
loggedInUserScenarioWithRole("When calling DELETE on", "DELETE", "/api/dashboards/db/dash", "/api/dashboards/db/:slug", role, func(sc *scenarioContext) {
CallDeleteDashboard(sc)
Convey("Should result in 412 Precondition failed", func() {
So(sc.resp.Code, ShouldEqual, 412)
result := sc.ToJson()
So(result.Get("status").MustString(), ShouldEqual, "multiple-slugs-exists")
So(result.Get("message").MustString(), ShouldEqual, m.ErrDashboardsWithSameSlugExists.Error())
})
})
})
}
func GetDashboardShouldReturn200(sc *scenarioContext) dtos.DashboardFullWithMeta {
......@@ -655,6 +770,15 @@ func CallDeleteDashboard(sc *scenarioContext) {
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
}
func CallDeleteDashboardByUid(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *m.DeleteDashboardCommand) error {
return nil
})
sc.handlerFunc = DeleteDashboardByUid
sc.fakeReqWithParams("DELETE", sc.url, map[string]string{}).exec()
}
func CallPostDashboard(sc *scenarioContext) {
bus.AddHandler("test", func(cmd *alerting.ValidateDashboardAlertsCommand) error {
return nil
......
......@@ -22,7 +22,8 @@ var (
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard uid.")
ErrDashboardsWithSameSlugExists = errors.New("Multiple dashboards with the same slug exists")
ErrDashboardFailedGenerateUniqueUid = errors.New("Failed to generate unique dashboard id")
)
type UpdatePluginDashboardError struct {
......@@ -177,7 +178,7 @@ func GetDashboardUrl(uid string, slug string) string {
// GetFolderUrl return the html url for a folder
func GetFolderUrl(folderUid string, slug string) string {
return fmt.Sprintf("%s/f/%v/%s", setting.AppSubUrl, folderUid, slug)
return fmt.Sprintf("%s/dashboards/f/%s/%s", setting.AppSubUrl, folderUid, slug)
}
//
......@@ -252,6 +253,13 @@ type GetDashboardSlugByIdQuery struct {
Result string
}
type GetDashboardsBySlugQuery struct {
OrgId int64
Slug string
Result []*Dashboard
}
type GetFoldersForSignedInUserQuery struct {
OrgId int64
SignedInUser *SignedInUser
......
......@@ -13,10 +13,10 @@ const (
type Hit struct {
Id int64 `json:"id"`
Uid string `json:"uid"`
Title string `json:"title"`
Uri string `json:"uri"`
Url string `json:"url"`
Slug string `json:"slug"`
Type HitType `json:"type"`
Tags []string `json:"tags"`
IsStarred bool `json:"isStarred"`
......
......@@ -314,10 +314,10 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard
if !exists {
hit = &search.Hit{
Id: item.Id,
Uid: item.Uid,
Title: item.Title,
Uri: "db/" + item.Slug,
Url: m.GetDashboardFolderUrl(item.IsFolder, item.Uid, item.Slug),
Slug: item.Slug,
Type: getHitType(item),
FolderId: item.FolderId,
FolderTitle: item.FolderTitle,
......@@ -550,3 +550,14 @@ func GetDashboardSlugById(query *m.GetDashboardSlugByIdQuery) error {
query.Result = slug.Slug
return nil
}
func GetDashboardsBySlug(query *m.GetDashboardsBySlugQuery) error {
var dashboards = make([]*m.Dashboard, 0)
if err := x.Where("org_id=? AND slug=?", query.OrgId, query.Slug).Find(&dashboards); err != nil {
return err
}
query.Result = dashboards
return nil
}
......@@ -146,7 +146,7 @@ func TestDashboardDataAccess(t *testing.T) {
So(len(query.Result), ShouldEqual, 1)
hit := query.Result[0]
So(hit.Type, ShouldEqual, search.DashHitFolder)
So(hit.Url, ShouldEqual, fmt.Sprintf("/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
So(hit.Url, ShouldEqual, fmt.Sprintf("/dashboards/f/%s/%s", savedFolder.Uid, savedFolder.Slug))
})
Convey("Should be able to search for a dashboard folder's children", func() {
......
......@@ -16,7 +16,7 @@ export class FolderPermissions extends Component<IContainerProps, any> {
loadStore() {
const { nav, folder, view } = this.props;
return folder.load(view.routeParams.get('slug') as string).then(res => {
return folder.load(view.routeParams.get('uid') as string).then(res => {
return nav.initFolderNav(toJS(folder.folder), 'manage-folder-permissions');
});
}
......
......@@ -9,14 +9,14 @@ describe('FolderSettings', () => {
let page;
beforeAll(() => {
backendSrv.getDashboard.mockReturnValue(
backendSrv.getDashboardByUid.mockReturnValue(
Promise.resolve({
dashboard: {
id: 1,
title: 'Folder Name',
},
meta: {
slug: 'folder-name',
url: '/dashboards/f/uid/folder-name',
canSave: true,
},
})
......
......@@ -20,10 +20,12 @@ export class FolderSettings extends React.Component<IContainerProps, any> {
loadStore() {
const { nav, folder, view } = this.props;
return folder.load(view.routeParams.get('slug') as string).then(res => {
return folder.load(view.routeParams.get('uid') as string).then(res => {
this.formSnapshot = getSnapshot(folder);
this.dashboard = res.dashboard;
view.updatePathAndQuery(`${res.meta.url}/settings`, {}, {});
return nav.initFolderNav(toJS(folder.folder), 'manage-folder-settings');
});
}
......@@ -51,7 +53,7 @@ export class FolderSettings extends React.Component<IContainerProps, any> {
folder
.saveFolder(this.dashboard, { overwrite: false })
.then(newUrl => {
view.updatePathAndQuery(newUrl, '', '');
view.updatePathAndQuery(newUrl, {}, {});
appEvents.emit('dashboard-saved');
appEvents.emit('alert-success', ['Folder saved']);
......
......@@ -34,7 +34,7 @@ export class ManageDashboardsCtrl {
// used when managing dashboards for a specific folder
folderId?: number;
folderSlug?: string;
folderUid?: string;
// if user can add new folders and/or add new dashboards
canSave: boolean;
......@@ -74,11 +74,11 @@ export class ManageDashboardsCtrl {
return this.initDashboardList(result);
})
.then(() => {
if (!this.folderSlug) {
if (!this.folderUid) {
return;
}
return this.backendSrv.getDashboard('db', this.folderSlug).then(dash => {
return this.backendSrv.getDashboardByUid(this.folderUid).then(dash => {
this.canSave = dash.meta.canSave;
});
});
......@@ -130,10 +130,10 @@ export class ManageDashboardsCtrl {
for (const section of this.sections) {
if (section.checked && section.id !== 0) {
selectedDashboards.folders.push(section.slug);
selectedDashboards.folders.push(section.uid);
} else {
const selected = _.filter(section.items, { checked: true });
selectedDashboards.dashboards.push(..._.map(selected, 'slug'));
selectedDashboards.dashboards.push(..._.map(selected, 'uid'));
}
}
......@@ -179,8 +179,8 @@ export class ManageDashboardsCtrl {
});
}
private deleteFoldersAndDashboards(slugs) {
this.backendSrv.deleteDashboards(slugs).then(result => {
private deleteFoldersAndDashboards(uids) {
this.backendSrv.deleteDashboards(uids).then(result => {
const folders = _.filter(result, dash => dash.meta.isFolder);
const folderCount = folders.length;
const dashboards = _.filter(result, dash => !dash.meta.isFolder);
......@@ -224,7 +224,7 @@ export class ManageDashboardsCtrl {
for (const section of this.sections) {
const selected = _.filter(section.items, { checked: true });
selectedDashboards.push(..._.map(selected, 'slug'));
selectedDashboards.push(..._.map(selected, 'uid'));
}
return selectedDashboards;
......@@ -334,7 +334,7 @@ export function manageDashboardsDirective() {
controllerAs: 'ctrl',
scope: {
folderId: '=',
folderSlug: '=',
folderUid: '=',
},
};
}
......
......@@ -257,11 +257,22 @@ export class BackendSrv {
});
}
deleteDashboard(slug) {
saveFolder(dash, options) {
options = options || {};
return this.post('/api/dashboards/db/', {
dashboard: dash,
isFolder: true,
overwrite: options.overwrite === true,
message: options.message || '',
});
}
deleteDashboard(uid) {
let deferred = this.$q.defer();
this.getDashboard('db', slug).then(fullDash => {
this.delete(`/api/dashboards/db/${slug}`)
this.getDashboardByUid(uid).then(fullDash => {
this.delete(`/api/dashboards/uid/${uid}`)
.then(() => {
deferred.resolve(fullDash);
})
......@@ -273,21 +284,21 @@ export class BackendSrv {
return deferred.promise;
}
deleteDashboards(dashboardSlugs) {
deleteDashboards(dashboardUids) {
const tasks = [];
for (let slug of dashboardSlugs) {
tasks.push(this.createTask(this.deleteDashboard.bind(this), true, slug));
for (let uid of dashboardUids) {
tasks.push(this.createTask(this.deleteDashboard.bind(this), true, uid));
}
return this.executeInOrder(tasks, []);
}
moveDashboards(dashboardSlugs, toFolder) {
moveDashboards(dashboardUids, toFolder) {
const tasks = [];
for (let slug of dashboardSlugs) {
tasks.push(this.createTask(this.moveDashboard.bind(this), true, slug, toFolder));
for (let uid of dashboardUids) {
tasks.push(this.createTask(this.moveDashboard.bind(this), true, uid, toFolder));
}
return this.executeInOrder(tasks, []).then(result => {
......@@ -299,10 +310,10 @@ export class BackendSrv {
});
}
private moveDashboard(slug, toFolder) {
private moveDashboard(uid, toFolder) {
let deferred = this.$q.defer();
this.getDashboard('db', slug).then(fullDash => {
this.getDashboardByUid(uid).then(fullDash => {
const model = new DashboardModel(fullDash.dashboard, fullDash.meta);
if ((!fullDash.meta.folderId && toFolder.id === 0) || fullDash.meta.folderId === toFolder.id) {
......
......@@ -34,10 +34,7 @@ export class BridgeSrv {
});
this.$rootScope.$on('$routeChangeSuccess', (evt, data) => {
let angularUrl = this.$location.url();
if (store.view.currentUrl !== angularUrl) {
store.view.updatePathAndQuery(this.$location.path(), this.$location.search(), this.$route.current.params);
}
store.view.updatePathAndQuery(this.$location.path(), this.$location.search(), this.$route.current.params);
});
reaction(
......@@ -45,7 +42,9 @@ export class BridgeSrv {
currentUrl => {
let angularUrl = this.$location.url();
if (angularUrl !== currentUrl) {
this.$location.url(currentUrl);
this.$timeout(() => {
this.$location.url(currentUrl);
});
console.log('store updating angular $location.url', currentUrl);
}
}
......
......@@ -128,12 +128,12 @@ export class SearchSrv {
if (hit.type === 'dash-folder') {
sections[hit.id] = {
id: hit.id,
uid: hit.uid,
title: hit.title,
expanded: false,
items: [],
toggle: this.toggleFolder.bind(this),
url: `dashboards/folder/${hit.id}/${hit.slug}`,
slug: hit.slug,
url: hit.url,
icon: 'fa fa-folder',
score: _.keys(sections).length,
};
......@@ -150,9 +150,9 @@ export class SearchSrv {
if (hit.folderId) {
section = {
id: hit.folderId,
uid: hit.uid,
title: hit.folderTitle,
url: `dashboards/folder/${hit.folderId}/${hit.folderSlug}`,
slug: hit.slug,
url: hit.url,
items: [],
icon: 'fa fa-folder-open',
toggle: this.toggleFolder.bind(this),
......
......@@ -483,22 +483,22 @@ describe('ManageDashboards', () => {
ctrl.sections = [
{
id: 1,
uid: 'folder',
title: 'folder',
items: [{ id: 2, checked: true, slug: 'folder-dash' }],
items: [{ id: 2, checked: true, uid: 'folder-dash' }],
checked: true,
slug: 'folder',
},
{
id: 3,
title: 'folder-2',
items: [{ id: 3, checked: true, slug: 'folder-2-dash' }],
items: [{ id: 3, checked: true, uid: 'folder-2-dash' }],
checked: false,
slug: 'folder-2',
uid: 'folder-2',
},
{
id: 0,
title: 'Root',
items: [{ id: 3, checked: true, slug: 'root-dash' }],
items: [{ id: 3, checked: true, uid: 'root-dash' }],
checked: true,
},
];
......@@ -535,14 +535,14 @@ describe('ManageDashboards', () => {
{
id: 1,
title: 'folder',
items: [{ id: 2, checked: true, slug: 'dash' }],
items: [{ id: 2, checked: true, uid: 'dash' }],
checked: false,
slug: 'folder',
uid: 'folder',
},
{
id: 0,
title: 'Root',
items: [{ id: 3, checked: true, slug: 'dash-2' }],
items: [{ id: 3, checked: true, uid: 'dash-2' }],
checked: false,
},
];
......
......@@ -19,9 +19,7 @@ export class CreateFolderCtrl {
return this.backendSrv.createDashboardFolder(this.title).then(result => {
appEvents.emit('alert-success', ['Folder Created', 'OK']);
var folderUrl = `dashboards/folder/${result.dashboard.id}/${result.meta.slug}`;
this.$location.url(folderUrl);
this.$location.url(result.meta.url);
});
}
......
......@@ -3,17 +3,19 @@ import { FolderPageLoader } from './folder_page_loader';
export class FolderDashboardsCtrl {
navModel: any;
folderId: number;
folderSlug: string;
uid: string;
/** @ngInject */
constructor(private backendSrv, navModelSrv, private $routeParams) {
if (this.$routeParams.folderId && this.$routeParams.slug) {
this.folderId = $routeParams.folderId;
constructor(private backendSrv, navModelSrv, private $routeParams, $location) {
if (this.$routeParams.uid) {
this.uid = $routeParams.uid;
const loader = new FolderPageLoader(this.backendSrv, this.$routeParams);
const loader = new FolderPageLoader(this.backendSrv);
loader.load(this, this.folderId, 'manage-folder-dashboards').then(result => {
this.folderSlug = result.meta.slug;
loader.load(this, this.uid, 'manage-folder-dashboards').then(folder => {
if ($location.path() !== folder.meta.url) {
$location.path(folder.meta.url).replace();
}
});
}
}
......
export class FolderPageLoader {
constructor(private backendSrv, private $routeParams) {}
constructor(private backendSrv) {}
load(ctrl, folderId, activeChildId) {
load(ctrl, uid, activeChildId) {
ctrl.navModel = {
main: {
icon: 'fa fa-folder-open',
......@@ -36,11 +36,12 @@ export class FolderPageLoader {
},
};
return this.backendSrv.getDashboard('db', this.$routeParams.slug).then(result => {
return this.backendSrv.getDashboardByUid(uid).then(result => {
ctrl.folderId = result.dashboard.id;
const folderTitle = result.dashboard.title;
const folderUrl = result.meta.url;
ctrl.navModel.main.text = folderTitle;
const folderUrl = this.createFolderUrl(folderId, result.meta.slug);
const dashTab = ctrl.navModel.main.children.find(child => child.id === 'manage-folder-dashboards');
dashTab.url = folderUrl;
......@@ -57,8 +58,4 @@ export class FolderPageLoader {
return result;
});
}
createFolderUrl(folderId: number, slug: string) {
return `dashboards/folder/${folderId}/${slug}`;
}
}
......@@ -3,20 +3,23 @@ import { FolderPageLoader } from './folder_page_loader';
export class FolderPermissionsCtrl {
navModel: any;
folderId: number;
uid: string;
dashboard: any;
meta: any;
/** @ngInject */
constructor(private backendSrv, navModelSrv, private $routeParams) {
if (this.$routeParams.folderId && this.$routeParams.slug) {
this.folderId = $routeParams.folderId;
constructor(private backendSrv, navModelSrv, private $routeParams, $location) {
if (this.$routeParams.uid) {
this.uid = $routeParams.uid;
new FolderPageLoader(this.backendSrv, this.$routeParams)
.load(this, this.folderId, 'manage-folder-permissions')
.then(result => {
this.dashboard = result.dashboard;
this.meta = result.meta;
});
new FolderPageLoader(this.backendSrv).load(this, this.uid, 'manage-folder-permissions').then(folder => {
if ($location.path() !== folder.meta.url) {
$location.path(`${folder.meta.url}/permissions`).replace();
}
this.dashboard = folder.dashboard;
this.meta = folder.meta;
});
}
}
}
......@@ -5,6 +5,7 @@ export class FolderSettingsCtrl {
folderPageLoader: FolderPageLoader;
navModel: any;
folderId: number;
uid: string;
canSave = false;
dashboard: any;
meta: any;
......@@ -13,14 +14,18 @@ export class FolderSettingsCtrl {
/** @ngInject */
constructor(private backendSrv, navModelSrv, private $routeParams, private $location) {
if (this.$routeParams.folderId && this.$routeParams.slug) {
this.folderId = $routeParams.folderId;
this.folderPageLoader = new FolderPageLoader(this.backendSrv, this.$routeParams);
this.folderPageLoader.load(this, this.folderId, 'manage-folder-settings').then(result => {
this.dashboard = result.dashboard;
this.meta = result.meta;
this.canSave = result.meta.canSave;
if (this.$routeParams.uid) {
this.uid = $routeParams.uid;
this.folderPageLoader = new FolderPageLoader(this.backendSrv);
this.folderPageLoader.load(this, this.uid, 'manage-folder-settings').then(folder => {
if ($location.path() !== folder.meta.url) {
$location.path(`${folder.meta.url}/settings`).replace();
}
this.dashboard = folder.dashboard;
this.meta = folder.meta;
this.canSave = folder.meta.canSave;
this.title = this.dashboard.title;
});
}
......@@ -36,11 +41,10 @@ export class FolderSettingsCtrl {
this.dashboard.title = this.title.trim();
return this.backendSrv
.saveDashboard(this.dashboard, { overwrite: false })
.updateDashboardFolder(this.dashboard, { overwrite: false })
.then(result => {
var folderUrl = this.folderPageLoader.createFolderUrl(this.folderId, result.slug);
if (folderUrl !== this.$location.path()) {
this.$location.url(folderUrl + '/settings');
if (result.url !== this.$location.path()) {
this.$location.url(result.url + '/settings');
}
appEvents.emit('dashboard-saved');
......@@ -65,7 +69,7 @@ export class FolderSettingsCtrl {
icon: 'fa-trash',
yesText: 'Delete',
onConfirm: () => {
return this.backendSrv.deleteDashboard(this.meta.slug).then(() => {
return this.backendSrv.deleteDashboard(this.dashboard.uid).then(() => {
appEvents.emit('alert-success', ['Folder Deleted', `${this.dashboard.title} has been deleted`]);
this.$location.url('dashboards');
});
......@@ -84,7 +88,7 @@ export class FolderSettingsCtrl {
yesText: 'Save & Overwrite',
icon: 'fa-warning',
onConfirm: () => {
this.backendSrv.saveDashboard(this.dashboard, { overwrite: true });
this.backendSrv.updateDashboardFolder(this.dashboard, { overwrite: true });
},
});
}
......
<page-header ng-if="ctrl.navModel" model="ctrl.navModel"></page-header>
<div class="page-container page-body">
<manage-dashboards ng-if="ctrl.folderId && ctrl.folderSlug" folder-id="ctrl.folderId" folder-slug="ctrl.folderSlug" />
<manage-dashboards ng-if="ctrl.folderId && ctrl.uid" folder-id="ctrl.folderId" folder-uid="ctrl.uid" />
</div>
......@@ -182,7 +182,7 @@ export class SettingsCtrl {
}
deleteDashboardConfirmed() {
this.backendSrv.deleteDashboard(this.dashboard.meta.slug).then(() => {
this.backendSrv.deleteDashboard(this.dashboard.uid).then(() => {
appEvents.emit('alert-success', ['Dashboard Deleted', this.dashboard.title + ' has been deleted']);
this.$location.url('/');
});
......
......@@ -29,6 +29,10 @@ export class LoadDashboardCtrl {
}
dashboardLoaderSrv.loadDashboard($routeParams.type, $routeParams.slug, $routeParams.uid).then(function(result) {
if ($location.path() !== result.meta.url) {
$location.path(result.meta.url).replace();
}
if ($routeParams.keepRows) {
result.meta.keepRows = true;
}
......
......@@ -81,19 +81,19 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
controller: 'CreateFolderCtrl',
controllerAs: 'ctrl',
})
.when('/dashboards/folder/:folderId/:slug/permissions', {
.when('/dashboards/f/:uid/:slug/permissions', {
template: '<react-container />',
resolve: {
component: () => FolderPermissions,
},
})
.when('/dashboards/folder/:folderId/:slug/settings', {
.when('/dashboards/f/:uid/:slug/settings', {
template: '<react-container />',
resolve: {
component: () => FolderSettings,
},
})
.when('/dashboards/folder/:folderId/:slug', {
.when('/dashboards/f/:uid/:slug', {
templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html',
controller: 'FolderDashboardsCtrl',
controllerAs: 'ctrl',
......
......@@ -2,8 +2,8 @@ import { types, getEnv, flow } from 'mobx-state-tree';
export const Folder = types.model('Folder', {
id: types.identifier(types.number),
slug: types.string,
title: types.string,
url: types.string,
canSave: types.boolean,
hasChanged: types.boolean,
});
......@@ -13,13 +13,13 @@ export const FolderStore = types
folder: types.maybe(Folder),
})
.actions(self => ({
load: flow(function* load(slug: string) {
load: flow(function* load(uid: string) {
const backendSrv = getEnv(self).backendSrv;
const res = yield backendSrv.getDashboard('db', slug);
const res = yield backendSrv.getDashboardByUid(uid);
self.folder = Folder.create({
id: res.dashboard.id,
title: res.dashboard.title,
slug: res.meta.slug,
url: res.meta.url,
canSave: res.meta.canSave,
hasChanged: false,
});
......@@ -35,14 +35,15 @@ export const FolderStore = types
const backendSrv = getEnv(self).backendSrv;
dashboard.title = self.folder.title.trim();
const res = yield backendSrv.saveDashboard(dashboard, options);
self.folder.slug = res.slug;
return `dashboards/folder/${self.folder.id}/${res.slug}/settings`;
const res = yield backendSrv.saveFolder(dashboard, options);
self.folder.url = res.url;
return `${self.folder.url}/settings`;
}),
deleteFolder: flow(function* deleteFolder() {
const backendSrv = getEnv(self).backendSrv;
return backendSrv.deleteDashboard(self.folder.slug);
return backendSrv.deleteDashboard(self.folder.url);
}),
}));
......@@ -3,12 +3,12 @@ import { NavStore } from './NavStore';
describe('NavStore', () => {
const folderId = 1;
const folderTitle = 'Folder Name';
const folderSlug = 'folder-name';
const folderUrl = '/dashboards/f/uid/folder-name';
const canAdmin = true;
const folder = {
id: folderId,
slug: folderSlug,
url: folderUrl,
title: folderTitle,
canAdmin: canAdmin,
};
......@@ -33,9 +33,9 @@ describe('NavStore', () => {
it('Should set correct urls for each tab', () => {
expect(store.main.children.length).toBe(3);
expect(store.main.children[0].url).toBe(`dashboards/folder/${folderId}/${folderSlug}`);
expect(store.main.children[1].url).toBe(`dashboards/folder/${folderId}/${folderSlug}/permissions`);
expect(store.main.children[2].url).toBe(`dashboards/folder/${folderId}/${folderSlug}/settings`);
expect(store.main.children[0].url).toBe(folderUrl);
expect(store.main.children[1].url).toBe(`${folderUrl}/permissions`);
expect(store.main.children[2].url).toBe(`${folderUrl}/settings`);
});
it('Should set active tab', () => {
......
......@@ -41,8 +41,6 @@ export const NavStore = types
},
initFolderNav(folder: any, activeChildId: string) {
const folderUrl = createFolderUrl(folder.id, folder.slug);
let main = {
icon: 'fa fa-folder-open',
id: 'manage-folder',
......@@ -56,21 +54,21 @@ export const NavStore = types
icon: 'fa fa-fw fa-th-large',
id: 'manage-folder-dashboards',
text: 'Dashboards',
url: folderUrl,
url: folder.url,
},
{
active: activeChildId === 'manage-folder-permissions',
icon: 'fa fa-fw fa-lock',
id: 'manage-folder-permissions',
text: 'Permissions',
url: folderUrl + '/permissions',
url: `${folder.url}/permissions`,
},
{
active: activeChildId === 'manage-folder-settings',
icon: 'fa fa-fw fa-cog',
id: 'manage-folder-settings',
text: 'Settings',
url: folderUrl + '/settings',
url: `${folder.url}/settings`,
},
],
};
......@@ -118,7 +116,3 @@ export const NavStore = types
self.main = NavItem.create(main);
},
}));
function createFolderUrl(folderId: number, slug: string) {
return `dashboards/folder/${folderId}/${slug}`;
}
export const backendSrv = {
get: jest.fn(),
getDashboard: jest.fn(),
getDashboardByUid: jest.fn(),
post: jest.fn(),
};
......
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