mirror of
https://github.com/grafana/grafana.git
synced 2025-01-27 00:37:04 -06:00
Merge pull request #10706 from grafana/7883_frontend_step2
WIP: Dashboard & Persistent urls - Frontend Step 2
This commit is contained in:
commit
d8d82c1769
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
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;
|
||||
if (this.$routeParams.uid) {
|
||||
this.uid = $routeParams.uid;
|
||||
|
||||
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;
|
||||
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 });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<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}`;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
export const backendSrv = {
|
||||
get: jest.fn(),
|
||||
getDashboard: jest.fn(),
|
||||
getDashboardByUid: jest.fn(),
|
||||
post: jest.fn(),
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user