Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
411d67ca
Unverified
Commit
411d67ca
authored
Dec 10, 2018
by
Victor Cinaglia
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
snapshots: Add support for deleting external snapshots
parent
9d6da10e
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
145 additions
and
5 deletions
+145
-5
pkg/api/dashboard_snapshot.go
+45
-0
pkg/api/dashboard_snapshot_test.go
+87
-0
public/app/features/manage-dashboards/SnapshotListCtrl.ts
+6
-2
public/app/features/manage-dashboards/partials/snapshot_list.html
+7
-3
No files found.
pkg/api/dashboard_snapshot.go
View file @
411d67ca
...
...
@@ -157,6 +157,37 @@ func GetDashboardSnapshot(c *m.ReqContext) {
c
.
JSON
(
200
,
dto
)
}
func
deleteExternalDashboardSnapshot
(
externalUrl
string
)
error
{
response
,
err
:=
client
.
Get
(
externalUrl
)
if
response
!=
nil
{
defer
response
.
Body
.
Close
()
}
if
err
!=
nil
{
return
err
}
if
response
.
StatusCode
==
200
{
return
nil
}
// Gracefully ignore "snapshot not found" errors as they could have already
// been removed either via the cleanup script or by request.
if
response
.
StatusCode
==
500
{
var
respJson
map
[
string
]
interface
{}
if
err
:=
json
.
NewDecoder
(
response
.
Body
)
.
Decode
(
&
respJson
);
err
!=
nil
{
return
err
}
if
respJson
[
"message"
]
==
"Failed to get dashboard snapshot"
{
return
nil
}
}
return
fmt
.
Errorf
(
"Unexpected response when deleting external snapshot. Status code: %d"
,
response
.
StatusCode
)
}
// GET /api/snapshots-delete/:deleteKey
func
DeleteDashboardSnapshotByDeleteKey
(
c
*
m
.
ReqContext
)
Response
{
key
:=
c
.
Params
(
":deleteKey"
)
...
...
@@ -168,6 +199,13 @@ func DeleteDashboardSnapshotByDeleteKey(c *m.ReqContext) Response {
return
Error
(
500
,
"Failed to get dashboard snapshot"
,
err
)
}
if
query
.
Result
.
External
{
err
:=
deleteExternalDashboardSnapshot
(
query
.
Result
.
ExternalDeleteUrl
)
if
err
!=
nil
{
return
Error
(
500
,
"Failed to delete external dashboard"
,
err
)
}
}
cmd
:=
&
m
.
DeleteDashboardSnapshotCommand
{
DeleteKey
:
query
.
Result
.
DeleteKey
}
if
err
:=
bus
.
Dispatch
(
cmd
);
err
!=
nil
{
...
...
@@ -204,6 +242,13 @@ func DeleteDashboardSnapshot(c *m.ReqContext) Response {
return
Error
(
403
,
"Access denied to this snapshot"
,
nil
)
}
if
query
.
Result
.
External
{
err
:=
deleteExternalDashboardSnapshot
(
query
.
Result
.
ExternalDeleteUrl
)
if
err
!=
nil
{
return
Error
(
500
,
"Failed to delete external dashboard"
,
err
)
}
}
cmd
:=
&
m
.
DeleteDashboardSnapshotCommand
{
DeleteKey
:
query
.
Result
.
DeleteKey
}
if
err
:=
bus
.
Dispatch
(
cmd
);
err
!=
nil
{
...
...
pkg/api/dashboard_snapshot_test.go
View file @
411d67ca
package
api
import
(
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
...
...
@@ -13,13 +16,17 @@ import (
func
TestDashboardSnapshotApiEndpoint
(
t
*
testing
.
T
)
{
Convey
(
"Given a single snapshot"
,
t
,
func
()
{
var
externalRequest
*
http
.
Request
jsonModel
,
_
:=
simplejson
.
NewJson
([]
byte
(
`{"id":100}`
))
mockSnapshotResult
:=
&
m
.
DashboardSnapshot
{
Id
:
1
,
Key
:
"12345"
,
DeleteKey
:
"54321"
,
Dashboard
:
jsonModel
,
Expires
:
time
.
Now
()
.
Add
(
time
.
Duration
(
1000
)
*
time
.
Second
),
UserId
:
999999
,
External
:
true
,
}
bus
.
AddHandler
(
"test"
,
func
(
query
*
m
.
GetDashboardSnapshotQuery
)
error
{
...
...
@@ -45,13 +52,25 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
return
nil
})
setupRemoteServer
:=
func
(
fn
func
(
http
.
ResponseWriter
,
*
http
.
Request
))
*
httptest
.
Server
{
return
httptest
.
NewServer
(
http
.
HandlerFunc
(
func
(
rw
http
.
ResponseWriter
,
r
*
http
.
Request
)
{
fn
(
rw
,
r
)
}))
}
Convey
(
"When user has editor role and is not in the ACL"
,
func
()
{
Convey
(
"Should not be able to delete snapshot"
,
func
()
{
loggedInUserScenarioWithRole
(
"When calling DELETE on"
,
"DELETE"
,
"/api/snapshots/12345"
,
"/api/snapshots/:key"
,
m
.
ROLE_EDITOR
,
func
(
sc
*
scenarioContext
)
{
ts
:=
setupRemoteServer
(
func
(
rw
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
externalRequest
=
req
})
mockSnapshotResult
.
ExternalDeleteUrl
=
ts
.
URL
sc
.
handlerFunc
=
DeleteDashboardSnapshot
sc
.
fakeReqWithParams
(
"DELETE"
,
sc
.
url
,
map
[
string
]
string
{
"key"
:
"12345"
})
.
exec
()
So
(
sc
.
resp
.
Code
,
ShouldEqual
,
403
)
So
(
externalRequest
,
ShouldBeNil
)
})
})
})
...
...
@@ -59,6 +78,12 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
Convey
(
"When user is anonymous"
,
func
()
{
Convey
(
"Should be able to delete snapshot by deleteKey"
,
func
()
{
anonymousUserScenario
(
"When calling GET on"
,
"GET"
,
"/api/snapshots-delete/12345"
,
"/api/snapshots-delete/:deleteKey"
,
func
(
sc
*
scenarioContext
)
{
ts
:=
setupRemoteServer
(
func
(
rw
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
rw
.
WriteHeader
(
200
)
externalRequest
=
req
})
mockSnapshotResult
.
ExternalDeleteUrl
=
ts
.
URL
sc
.
handlerFunc
=
DeleteDashboardSnapshotByDeleteKey
sc
.
fakeReqWithParams
(
"GET"
,
sc
.
url
,
map
[
string
]
string
{
"deleteKey"
:
"12345"
})
.
exec
()
...
...
@@ -67,6 +92,10 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
So
(
err
,
ShouldBeNil
)
So
(
respJSON
.
Get
(
"message"
)
.
MustString
(),
ShouldStartWith
,
"Snapshot deleted"
)
So
(
externalRequest
.
Method
,
ShouldEqual
,
http
.
MethodGet
)
So
(
fmt
.
Sprintf
(
"http://%s"
,
externalRequest
.
Host
),
ShouldEqual
,
ts
.
URL
)
So
(
externalRequest
.
URL
.
EscapedPath
(),
ShouldEqual
,
"/"
)
})
})
})
...
...
@@ -79,6 +108,12 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
Convey
(
"Should be able to delete a snapshot"
,
func
()
{
loggedInUserScenarioWithRole
(
"When calling DELETE on"
,
"DELETE"
,
"/api/snapshots/12345"
,
"/api/snapshots/:key"
,
m
.
ROLE_EDITOR
,
func
(
sc
*
scenarioContext
)
{
ts
:=
setupRemoteServer
(
func
(
rw
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
rw
.
WriteHeader
(
200
)
externalRequest
=
req
})
mockSnapshotResult
.
ExternalDeleteUrl
=
ts
.
URL
sc
.
handlerFunc
=
DeleteDashboardSnapshot
sc
.
fakeReqWithParams
(
"DELETE"
,
sc
.
url
,
map
[
string
]
string
{
"key"
:
"12345"
})
.
exec
()
...
...
@@ -87,6 +122,8 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
So
(
err
,
ShouldBeNil
)
So
(
respJSON
.
Get
(
"message"
)
.
MustString
(),
ShouldStartWith
,
"Snapshot deleted"
)
So
(
fmt
.
Sprintf
(
"http://%s"
,
externalRequest
.
Host
),
ShouldEqual
,
ts
.
URL
)
So
(
externalRequest
.
URL
.
EscapedPath
(),
ShouldEqual
,
"/"
)
})
})
})
...
...
@@ -94,6 +131,7 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
Convey
(
"When user is editor and is the creator of the snapshot"
,
func
()
{
aclMockResp
=
[]
*
m
.
DashboardAclInfoDTO
{}
mockSnapshotResult
.
UserId
=
TestUserID
mockSnapshotResult
.
External
=
false
Convey
(
"Should be able to delete a snapshot"
,
func
()
{
loggedInUserScenarioWithRole
(
"When calling DELETE on"
,
"DELETE"
,
"/api/snapshots/12345"
,
"/api/snapshots/:key"
,
m
.
ROLE_EDITOR
,
func
(
sc
*
scenarioContext
)
{
...
...
@@ -108,5 +146,54 @@ func TestDashboardSnapshotApiEndpoint(t *testing.T) {
})
})
})
Convey
(
"When deleting an external snapshot"
,
func
()
{
aclMockResp
=
[]
*
m
.
DashboardAclInfoDTO
{}
mockSnapshotResult
.
UserId
=
TestUserID
Convey
(
"Should gracefully delete local snapshot when remote snapshot has already been removed"
,
func
()
{
loggedInUserScenarioWithRole
(
"When calling DELETE on"
,
"DELETE"
,
"/api/snapshots/12345"
,
"/api/snapshots/:key"
,
m
.
ROLE_EDITOR
,
func
(
sc
*
scenarioContext
)
{
ts
:=
setupRemoteServer
(
func
(
rw
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
rw
.
Write
([]
byte
(
`{"message":"Failed to get dashboard snapshot"}`
))
rw
.
WriteHeader
(
500
)
})
mockSnapshotResult
.
ExternalDeleteUrl
=
ts
.
URL
sc
.
handlerFunc
=
DeleteDashboardSnapshot
sc
.
fakeReqWithParams
(
"DELETE"
,
sc
.
url
,
map
[
string
]
string
{
"key"
:
"12345"
})
.
exec
()
So
(
sc
.
resp
.
Code
,
ShouldEqual
,
200
)
})
})
Convey
(
"Should fail to delete local snapshot when an unexpected 500 error occurs"
,
func
()
{
loggedInUserScenarioWithRole
(
"When calling DELETE on"
,
"DELETE"
,
"/api/snapshots/12345"
,
"/api/snapshots/:key"
,
m
.
ROLE_EDITOR
,
func
(
sc
*
scenarioContext
)
{
ts
:=
setupRemoteServer
(
func
(
rw
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
rw
.
WriteHeader
(
500
)
rw
.
Write
([]
byte
(
`{"message":"Unexpected"}`
))
})
mockSnapshotResult
.
ExternalDeleteUrl
=
ts
.
URL
sc
.
handlerFunc
=
DeleteDashboardSnapshot
sc
.
fakeReqWithParams
(
"DELETE"
,
sc
.
url
,
map
[
string
]
string
{
"key"
:
"12345"
})
.
exec
()
So
(
sc
.
resp
.
Code
,
ShouldEqual
,
500
)
})
})
Convey
(
"Should fail to delete local snapshot when an unexpected remote error occurs"
,
func
()
{
loggedInUserScenarioWithRole
(
"When calling DELETE on"
,
"DELETE"
,
"/api/snapshots/12345"
,
"/api/snapshots/:key"
,
m
.
ROLE_EDITOR
,
func
(
sc
*
scenarioContext
)
{
ts
:=
setupRemoteServer
(
func
(
rw
http
.
ResponseWriter
,
req
*
http
.
Request
)
{
rw
.
WriteHeader
(
404
)
})
mockSnapshotResult
.
ExternalDeleteUrl
=
ts
.
URL
sc
.
handlerFunc
=
DeleteDashboardSnapshot
sc
.
fakeReqWithParams
(
"DELETE"
,
sc
.
url
,
map
[
string
]
string
{
"key"
:
"12345"
})
.
exec
()
So
(
sc
.
resp
.
Code
,
ShouldEqual
,
500
)
})
})
})
})
}
public/app/features/manage-dashboards/SnapshotListCtrl.ts
View file @
411d67ca
...
...
@@ -5,10 +5,14 @@ export class SnapshotListCtrl {
snapshots
:
any
;
/** @ngInject */
constructor
(
private
$rootScope
,
private
backendSrv
,
navModelSrv
)
{
constructor
(
private
$rootScope
,
private
backendSrv
,
navModelSrv
,
private
$location
)
{
this
.
navModel
=
navModelSrv
.
getNav
(
'dashboards'
,
'snapshots'
,
0
);
this
.
backendSrv
.
get
(
'/api/dashboard/snapshots'
).
then
(
result
=>
{
this
.
snapshots
=
result
;
const
baseUrl
=
this
.
$location
.
absUrl
().
replace
(
$location
.
url
(),
''
);
this
.
snapshots
=
result
.
map
(
snapshot
=>
({
...
snapshot
,
url
:
snapshot
.
externalUrl
||
`
${
baseUrl
}
/dashboard/snapshot/
${
snapshot
.
key
}
`
,
}));
});
}
...
...
public/app/features/manage-dashboards/partials/snapshot_list.html
View file @
411d67ca
...
...
@@ -6,17 +6,21 @@
<th><strong>
Name
</strong></th>
<th><strong>
Snapshot url
</strong></th>
<th
style=
"width: 70px"
></th>
<th
style=
"width: 30px"
></th>
<th
style=
"width: 25px"
></th>
</thead>
<tr
ng-repeat=
"snapshot in ctrl.snapshots"
>
<td>
<a
href=
"dashboard/snapshot/{{snapshot.key
}}"
>
{{snapshot.name}}
</a>
<a
href=
"{{snapshot.url
}}"
>
{{snapshot.name}}
</a>
</td>
<td
>
<a
href=
"dashboard/snapshot/{{snapshot.key}}"
>
dashboard/snapshot/{{snapshot.key}}
</a>
<a
href=
"{{snapshot.url}}"
>
{{snapshot.url}}
</a>
</td>
<td>
<span
class=
"query-keyword"
ng-if=
"snapshot.external"
>
External
</span>
</td>
<td
class=
"text-center"
>
<a
href=
"
dashboard/snapshot/{{snapshot.key
}}"
class=
"btn btn-inverse btn-mini"
>
<a
href=
"
{{snapshot.url
}}"
class=
"btn btn-inverse btn-mini"
>
<i
class=
"fa fa-eye"
></i>
View
</a>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment