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