Chore: Introduce playlist service (#52252)

* Store: Introduce playlist service

* Integrate playlist service

* Update swagger
pull/52389/head
Sofia Papagiannaki 3 years ago committed by GitHub
parent 332639ce43
commit fb379ae436
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      pkg/api/docs/definitions/playlists.go
  2. 5
      pkg/api/http_server.go
  3. 67
      pkg/api/playlist.go
  4. 2
      pkg/server/wire.go
  5. 95
      pkg/services/playlist/model.go
  6. 14
      pkg/services/playlist/playlist.go
  7. 44
      pkg/services/playlist/playlistimpl/playlist.go
  8. 226
      pkg/services/playlist/playlistimpl/store.go
  9. 82
      pkg/services/playlist/playlistimpl/store_test.go
  10. 43
      pkg/services/playlist/playlisttest/fake.go
  11. 6
      pkg/services/sqlstore/store.go
  12. 37
      public/api-merged.json
  13. 8
      public/api-spec.json

@ -2,7 +2,7 @@ package definitions
import (
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/playlist"
)
// swagger:route GET /playlists playlists searchPlaylists
@ -121,7 +121,7 @@ type DeletePlaylistParams struct {
type UpdatePlaylistParams struct {
// in:body
// required:true
Body models.UpdatePlaylistCommand
Body playlist.UpdatePlaylistCommand
// in:path
// required:true
UID string `json:"uid"`
@ -131,28 +131,28 @@ type UpdatePlaylistParams struct {
type CreatePlaylistParams struct {
// in:body
// required:true
Body models.CreatePlaylistCommand
Body playlist.CreatePlaylistCommand
}
// swagger:response searchPlaylistsResponse
type SearchPlaylistsResponse struct {
// The response message
// in: body
Body models.Playlists `json:"body"`
Body playlist.Playlists `json:"body"`
}
// swagger:response getPlaylistResponse
type GetPlaylistResponse struct {
// The response message
// in: body
Body *models.PlaylistDTO `json:"body"`
Body *playlist.PlaylistDTO `json:"body"`
}
// swagger:response getPlaylistItemsResponse
type GetPlaylistItemsResponse struct {
// The response message
// in: body
Body []models.PlaylistItemDTO `json:"body"`
Body []playlist.PlaylistItemDTO `json:"body"`
}
// swagger:response getPlaylistDashboardsResponse
@ -166,12 +166,12 @@ type GetPlaylistDashboardsResponse struct {
type UpdatePlaylistResponseResponse struct {
// The response message
// in: body
Body *models.PlaylistDTO `json:"body"`
Body *playlist.PlaylistDTO `json:"body"`
}
// swagger:response createPlaylistResponse
type CreatePlaylistResponse struct {
// The response message
// in: body
Body *models.Playlist `json:"body"`
Body *playlist.Playlist `json:"body"`
}

@ -58,6 +58,7 @@ import (
"github.com/grafana/grafana/pkg/services/login"
"github.com/grafana/grafana/pkg/services/ngalert"
"github.com/grafana/grafana/pkg/services/notifications"
"github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/services/plugindashboards"
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service"
pref "github.com/grafana/grafana/pkg/services/preference"
@ -168,6 +169,7 @@ type HTTPServer struct {
dashboardVersionService dashver.Service
PublicDashboardsApi *publicdashboardsApi.Api
starService star.Service
playlistService playlist.Service
CoremodelRegistry *registry.Generic
CoremodelStaticRegistry *registry.Static
kvStore kvstore.KVStore
@ -206,7 +208,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service, entityEventsService store.EntityEventsService,
teamsPermissionsService accesscontrol.TeamPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService,
dashboardPermissionsService accesscontrol.DashboardPermissionsService, dashboardVersionService dashver.Service,
starService star.Service, csrfService csrf.Service, coremodelRegistry *registry.Generic, coremodelStaticRegistry *registry.Static,
starService star.Service, playlistService playlist.Service, csrfService csrf.Service, coremodelRegistry *registry.Generic, coremodelStaticRegistry *registry.Static,
kvStore kvstore.KVStore, secretsMigrator secrets.Migrator, remoteSecretsCheck secretsKV.UseRemoteSecretsPluginCheck,
publicDashboardsApi *publicdashboardsApi.Api, userService user.Service) (*HTTPServer, error) {
web.Env = cfg.Env
@ -289,6 +291,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
dashboardPermissionsService: dashboardPermissionsService,
dashboardVersionService: dashboardVersionService,
starService: starService,
playlistService: playlistService,
CoremodelRegistry: coremodelRegistry,
CoremodelStaticRegistry: coremodelStaticRegistry,
kvStore: kvStore,

@ -6,25 +6,26 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/web"
)
func (hs *HTTPServer) ValidateOrgPlaylist(c *models.ReqContext) {
uid := web.Params(c.Req)[":uid"]
query := models.GetPlaylistByUidQuery{UID: uid, OrgId: c.OrgId}
err := hs.SQLStore.GetPlaylist(c.Req.Context(), &query)
query := playlist.GetPlaylistByUidQuery{UID: uid, OrgId: c.OrgId}
p, err := hs.playlistService.Get(c.Req.Context(), &query)
if err != nil {
c.JsonApiErr(404, "Playlist not found", err)
return
}
if query.Result.OrgId == 0 {
if p.OrgId == 0 {
c.JsonApiErr(404, "Playlist not found", err)
return
}
if query.Result.OrgId != c.OrgId {
if p.OrgId != c.OrgId {
c.JsonApiErr(403, "You are not allowed to edit/view playlist", nil)
return
}
@ -38,53 +39,54 @@ func (hs *HTTPServer) SearchPlaylists(c *models.ReqContext) response.Response {
limit = 1000
}
searchQuery := models.GetPlaylistsQuery{
searchQuery := playlist.GetPlaylistsQuery{
Name: query,
Limit: limit,
OrgId: c.OrgId,
}
err := hs.SQLStore.SearchPlaylists(c.Req.Context(), &searchQuery)
playlists, err := hs.playlistService.Search(c.Req.Context(), &searchQuery)
if err != nil {
return response.Error(500, "Search failed", err)
}
return response.JSON(http.StatusOK, searchQuery.Result)
return response.JSON(http.StatusOK, playlists)
}
func (hs *HTTPServer) GetPlaylist(c *models.ReqContext) response.Response {
uid := web.Params(c.Req)[":uid"]
cmd := models.GetPlaylistByUidQuery{UID: uid, OrgId: c.OrgId}
cmd := playlist.GetPlaylistByUidQuery{UID: uid, OrgId: c.OrgId}
if err := hs.SQLStore.GetPlaylist(c.Req.Context(), &cmd); err != nil {
p, err := hs.playlistService.Get(c.Req.Context(), &cmd)
if err != nil {
return response.Error(500, "Playlist not found", err)
}
playlistDTOs, _ := hs.LoadPlaylistItemDTOs(c.Req.Context(), uid, c.OrgId)
dto := &models.PlaylistDTO{
Id: cmd.Result.Id,
UID: cmd.Result.UID,
Name: cmd.Result.Name,
Interval: cmd.Result.Interval,
OrgId: cmd.Result.OrgId,
dto := &playlist.PlaylistDTO{
Id: p.Id,
UID: p.UID,
Name: p.Name,
Interval: p.Interval,
OrgId: p.OrgId,
Items: playlistDTOs,
}
return response.JSON(http.StatusOK, dto)
}
func (hs *HTTPServer) LoadPlaylistItemDTOs(ctx context.Context, uid string, orgId int64) ([]models.PlaylistItemDTO, error) {
func (hs *HTTPServer) LoadPlaylistItemDTOs(ctx context.Context, uid string, orgId int64) ([]playlist.PlaylistItemDTO, error) {
playlistitems, err := hs.LoadPlaylistItems(ctx, uid, orgId)
if err != nil {
return nil, err
}
playlistDTOs := make([]models.PlaylistItemDTO, 0)
playlistDTOs := make([]playlist.PlaylistItemDTO, 0)
for _, item := range playlistitems {
playlistDTOs = append(playlistDTOs, models.PlaylistItemDTO{
playlistDTOs = append(playlistDTOs, playlist.PlaylistItemDTO{
Id: item.Id,
PlaylistId: item.PlaylistId,
Type: item.Type,
@ -97,13 +99,14 @@ func (hs *HTTPServer) LoadPlaylistItemDTOs(ctx context.Context, uid string, orgI
return playlistDTOs, nil
}
func (hs *HTTPServer) LoadPlaylistItems(ctx context.Context, uid string, orgId int64) ([]models.PlaylistItem, error) {
itemQuery := models.GetPlaylistItemsByUidQuery{PlaylistUID: uid, OrgId: orgId}
if err := hs.SQLStore.GetPlaylistItem(ctx, &itemQuery); err != nil {
func (hs *HTTPServer) LoadPlaylistItems(ctx context.Context, uid string, orgId int64) ([]playlist.PlaylistItem, error) {
itemQuery := playlist.GetPlaylistItemsByUidQuery{PlaylistUID: uid, OrgId: orgId}
items, err := hs.playlistService.GetItems(ctx, &itemQuery)
if err != nil {
return nil, err
}
return *itemQuery.Result, nil
return items, nil
}
func (hs *HTTPServer) GetPlaylistItems(c *models.ReqContext) response.Response {
@ -132,8 +135,8 @@ func (hs *HTTPServer) GetPlaylistDashboards(c *models.ReqContext) response.Respo
func (hs *HTTPServer) DeletePlaylist(c *models.ReqContext) response.Response {
uid := web.Params(c.Req)[":uid"]
cmd := models.DeletePlaylistCommand{UID: uid, OrgId: c.OrgId}
if err := hs.SQLStore.DeletePlaylist(c.Req.Context(), &cmd); err != nil {
cmd := playlist.DeletePlaylistCommand{UID: uid, OrgId: c.OrgId}
if err := hs.playlistService.Delete(c.Req.Context(), &cmd); err != nil {
return response.Error(500, "Failed to delete playlist", err)
}
@ -141,28 +144,30 @@ func (hs *HTTPServer) DeletePlaylist(c *models.ReqContext) response.Response {
}
func (hs *HTTPServer) CreatePlaylist(c *models.ReqContext) response.Response {
cmd := models.CreatePlaylistCommand{}
cmd := playlist.CreatePlaylistCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
cmd.OrgId = c.OrgId
if err := hs.SQLStore.CreatePlaylist(c.Req.Context(), &cmd); err != nil {
p, err := hs.playlistService.Create(c.Req.Context(), &cmd)
if err != nil {
return response.Error(500, "Failed to create playlist", err)
}
return response.JSON(http.StatusOK, cmd.Result)
return response.JSON(http.StatusOK, p)
}
func (hs *HTTPServer) UpdatePlaylist(c *models.ReqContext) response.Response {
cmd := models.UpdatePlaylistCommand{}
cmd := playlist.UpdatePlaylistCommand{}
if err := web.Bind(c.Req, &cmd); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}
cmd.OrgId = c.OrgId
cmd.UID = web.Params(c.Req)[":uid"]
if err := hs.SQLStore.UpdatePlaylist(c.Req.Context(), &cmd); err != nil {
p, err := hs.playlistService.Update(c.Req.Context(), &cmd)
if err != nil {
return response.Error(500, "Failed to save playlist", err)
}
@ -171,6 +176,6 @@ func (hs *HTTPServer) UpdatePlaylist(c *models.ReqContext) response.Response {
return response.Error(500, "Failed to save playlist", err)
}
cmd.Result.Items = playlistDTOs
return response.JSON(http.StatusOK, cmd.Result)
p.Items = playlistDTOs
return response.JSON(http.StatusOK, p)
}

@ -6,6 +6,7 @@ package server
import (
"github.com/google/wire"
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana/pkg/services/playlist/playlistimpl"
"github.com/grafana/grafana/pkg/services/store/sanitizer"
"github.com/grafana/grafana/pkg/api"
@ -286,6 +287,7 @@ var wireBasicSet = wire.NewSet(
ossaccesscontrol.ProvideDashboardPermissions,
wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)),
starimpl.ProvideService,
playlistimpl.ProvideService,
dashverimpl.ProvideService,
publicdashboardsService.ProvideService,
wire.Bind(new(publicdashboards.Service), new(*publicdashboardsService.PublicDashboardServiceImpl)),

@ -0,0 +1,95 @@
package playlist
import (
"errors"
)
// Typed errors
var (
ErrPlaylistNotFound = errors.New("Playlist not found")
ErrPlaylistFailedGenerateUniqueUid = errors.New("failed to generate unique playlist UID")
ErrCommandValidationFailed = errors.New("command missing required fields")
)
// Playlist model
type Playlist struct {
Id int64 `json:"id"`
UID string `json:"uid" xorm:"uid"`
Name string `json:"name"`
Interval string `json:"interval"`
OrgId int64 `json:"-"`
}
type PlaylistDTO struct {
Id int64 `json:"id"`
UID string `json:"uid"`
Name string `json:"name"`
Interval string `json:"interval"`
OrgId int64 `json:"-"`
Items []PlaylistItemDTO `json:"items"`
}
type PlaylistItemDTO struct {
Id int64 `json:"id"`
PlaylistId int64 `json:"playlistid"`
Type string `json:"type"`
Title string `json:"title"`
Value string `json:"value"`
Order int `json:"order"`
}
type PlaylistItem struct {
Id int64
PlaylistId int64
Type string
Value string
Order int
Title string
}
type Playlists []*Playlist
//
// COMMANDS
//
type UpdatePlaylistCommand struct {
OrgId int64 `json:"-"`
UID string `json:"uid"`
Name string `json:"name" binding:"Required"`
Interval string `json:"interval"`
Items []PlaylistItemDTO `json:"items"`
}
type CreatePlaylistCommand struct {
Name string `json:"name" binding:"Required"`
Interval string `json:"interval"`
Items []PlaylistItemDTO `json:"items"`
OrgId int64 `json:"-"`
}
type DeletePlaylistCommand struct {
UID string
OrgId int64
}
//
// QUERIES
//
type GetPlaylistsQuery struct {
Name string
Limit int
OrgId int64
}
type GetPlaylistByUidQuery struct {
UID string
OrgId int64
}
type GetPlaylistItemsByUidQuery struct {
PlaylistUID string
OrgId int64
}

@ -0,0 +1,14 @@
package playlist
import (
"context"
)
type Service interface {
Create(context.Context, *CreatePlaylistCommand) (*Playlist, error)
Update(context.Context, *UpdatePlaylistCommand) (*PlaylistDTO, error)
Get(context.Context, *GetPlaylistByUidQuery) (*Playlist, error)
GetItems(context.Context, *GetPlaylistItemsByUidQuery) ([]PlaylistItem, error)
Search(context.Context, *GetPlaylistsQuery) (Playlists, error)
Delete(ctx context.Context, cmd *DeletePlaylistCommand) error
}

@ -0,0 +1,44 @@
package playlistimpl
import (
"context"
"github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
)
type Service struct {
store store
}
func ProvideService(db db.DB) playlist.Service {
return &Service{
store: &sqlStore{
db: db,
},
}
}
func (s *Service) Create(ctx context.Context, cmd *playlist.CreatePlaylistCommand) (*playlist.Playlist, error) {
return s.store.Insert(ctx, cmd)
}
func (s *Service) Update(ctx context.Context, cmd *playlist.UpdatePlaylistCommand) (*playlist.PlaylistDTO, error) {
return s.store.Update(ctx, cmd)
}
func (s *Service) Get(ctx context.Context, q *playlist.GetPlaylistByUidQuery) (*playlist.Playlist, error) {
return s.store.Get(ctx, q)
}
func (s *Service) GetItems(ctx context.Context, q *playlist.GetPlaylistItemsByUidQuery) ([]playlist.PlaylistItem, error) {
return s.store.GetItems(ctx, q)
}
func (s *Service) Search(ctx context.Context, q *playlist.GetPlaylistsQuery) (playlist.Playlists, error) {
return s.store.List(ctx, q)
}
func (s *Service) Delete(ctx context.Context, cmd *playlist.DeletePlaylistCommand) error {
return s.store.Delete(ctx, cmd)
}

@ -0,0 +1,226 @@
package playlistimpl
import (
"context"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/sqlstore/db"
"github.com/grafana/grafana/pkg/util"
)
type store interface {
Insert(context.Context, *playlist.CreatePlaylistCommand) (*playlist.Playlist, error)
Delete(context.Context, *playlist.DeletePlaylistCommand) error
Get(context.Context, *playlist.GetPlaylistByUidQuery) (*playlist.Playlist, error)
GetItems(context.Context, *playlist.GetPlaylistItemsByUidQuery) ([]playlist.PlaylistItem, error)
List(context.Context, *playlist.GetPlaylistsQuery) (playlist.Playlists, error)
Update(context.Context, *playlist.UpdatePlaylistCommand) (*playlist.PlaylistDTO, error)
}
type sqlStore struct {
db db.DB
}
func (s *sqlStore) Insert(ctx context.Context, cmd *playlist.CreatePlaylistCommand) (*playlist.Playlist, error) {
p := playlist.Playlist{}
err := s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
uid, err := generateAndValidateNewPlaylistUid(sess, cmd.OrgId)
if err != nil {
return err
}
p = playlist.Playlist{
Name: cmd.Name,
Interval: cmd.Interval,
OrgId: cmd.OrgId,
UID: uid,
}
_, err = sess.Insert(&p)
if err != nil {
return err
}
playlistItems := make([]playlist.PlaylistItem, 0)
for _, item := range cmd.Items {
playlistItems = append(playlistItems, playlist.PlaylistItem{
PlaylistId: p.Id,
Type: item.Type,
Value: item.Value,
Order: item.Order,
Title: item.Title,
})
}
_, err = sess.Insert(&playlistItems)
return err
})
return &p, err
}
func (s *sqlStore) Update(ctx context.Context, cmd *playlist.UpdatePlaylistCommand) (*playlist.PlaylistDTO, error) {
dto := playlist.PlaylistDTO{}
err := s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
p := playlist.Playlist{
UID: cmd.UID,
OrgId: cmd.OrgId,
Name: cmd.Name,
Interval: cmd.Interval,
}
existingPlaylist := playlist.Playlist{UID: cmd.UID, OrgId: cmd.OrgId}
_, err := sess.Get(&existingPlaylist)
if err != nil {
return err
}
p.Id = existingPlaylist.Id
dto = playlist.PlaylistDTO{
Id: p.Id,
UID: p.UID,
OrgId: p.OrgId,
Name: p.Name,
Interval: p.Interval,
}
_, err = sess.Where("id=?", p.Id).Cols("name", "interval").Update(&p)
if err != nil {
return err
}
rawSQL := "DELETE FROM playlist_item WHERE playlist_id = ?"
_, err = sess.Exec(rawSQL, p.Id)
if err != nil {
return err
}
playlistItems := make([]models.PlaylistItem, 0)
for index, item := range cmd.Items {
playlistItems = append(playlistItems, models.PlaylistItem{
PlaylistId: p.Id,
Type: item.Type,
Value: item.Value,
Order: index + 1,
Title: item.Title,
})
}
_, err = sess.Insert(&playlistItems)
return err
})
return &dto, err
}
func (s *sqlStore) Get(ctx context.Context, query *playlist.GetPlaylistByUidQuery) (*playlist.Playlist, error) {
if query.UID == "" || query.OrgId == 0 {
return nil, playlist.ErrCommandValidationFailed
}
p := playlist.Playlist{}
err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
p = playlist.Playlist{UID: query.UID, OrgId: query.OrgId}
exists, err := sess.Get(&p)
if !exists {
return playlist.ErrPlaylistNotFound
}
return err
})
return &p, err
}
func (s *sqlStore) Delete(ctx context.Context, cmd *playlist.DeletePlaylistCommand) error {
if cmd.UID == "" || cmd.OrgId == 0 {
return playlist.ErrCommandValidationFailed
}
return s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error {
playlist := playlist.Playlist{UID: cmd.UID, OrgId: cmd.OrgId}
_, err := sess.Get(&playlist)
if err != nil {
return err
}
var rawPlaylistSQL = "DELETE FROM playlist WHERE uid = ? and org_id = ?"
_, err = sess.Exec(rawPlaylistSQL, cmd.UID, cmd.OrgId)
if err != nil {
return err
}
var rawItemSQL = "DELETE FROM playlist_item WHERE playlist_id = ?"
_, err = sess.Exec(rawItemSQL, playlist.Id)
return err
})
}
func (s *sqlStore) List(ctx context.Context, query *playlist.GetPlaylistsQuery) (playlist.Playlists, error) {
playlists := make(playlist.Playlists, 0)
if query.OrgId == 0 {
return playlists, playlist.ErrCommandValidationFailed
}
err := s.db.WithDbSession(ctx, func(dbSess *sqlstore.DBSession) error {
sess := dbSess.Limit(query.Limit)
if query.Name != "" {
sess.Where("name LIKE ?", "%"+query.Name+"%")
}
sess.Where("org_id = ?", query.OrgId)
err := sess.Find(&playlists)
return err
})
return playlists, err
}
func (s *sqlStore) GetItems(ctx context.Context, query *playlist.GetPlaylistItemsByUidQuery) ([]playlist.PlaylistItem, error) {
var playlistItems = make([]playlist.PlaylistItem, 0)
err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
if query.PlaylistUID == "" || query.OrgId == 0 {
return models.ErrCommandValidationFailed
}
// getQuery the playlist Id
getQuery := &playlist.GetPlaylistByUidQuery{UID: query.PlaylistUID, OrgId: query.OrgId}
p, err := s.Get(ctx, getQuery)
if err != nil {
return err
}
err = sess.Where("playlist_id=?", p.Id).Find(&playlistItems)
return err
})
return playlistItems, err
}
// generateAndValidateNewPlaylistUid generates a playlistUID and verifies that
// the uid isn't already in use. This is deliberately overly cautious, since users
// can also specify playlist uids during provisioning.
func generateAndValidateNewPlaylistUid(sess *sqlstore.DBSession, orgId int64) (string, error) {
for i := 0; i < 3; i++ {
uid := generateNewUid()
playlist := models.Playlist{OrgId: orgId, UID: uid}
exists, err := sess.Get(&playlist)
if err != nil {
return "", err
}
if !exists {
return uid, nil
}
}
return "", models.ErrPlaylistFailedGenerateUniqueUid
}
var generateNewUid func() string = util.GenerateShortUID

@ -0,0 +1,82 @@
package playlistimpl
import (
"context"
"testing"
"github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/stretchr/testify/require"
)
func TestIntegrationPlaylistDataAccess(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ss := sqlstore.InitTestDB(t)
playlistStore := sqlStore{db: ss}
t.Run("Can create playlist", func(t *testing.T) {
items := []playlist.PlaylistItemDTO{
{Title: "graphite", Value: "graphite", Type: "dashboard_by_tag"},
{Title: "Backend response times", Value: "3", Type: "dashboard_by_id"},
}
cmd := playlist.CreatePlaylistCommand{Name: "NYC office", Interval: "10m", OrgId: 1, Items: items}
p, err := playlistStore.Insert(context.Background(), &cmd)
require.NoError(t, err)
uid := p.UID
t.Run("Can get playlist items", func(t *testing.T) {
get := &playlist.GetPlaylistItemsByUidQuery{PlaylistUID: uid, OrgId: 1}
storedPlaylistItems, err := playlistStore.GetItems(context.Background(), get)
require.NoError(t, err)
require.Equal(t, len(storedPlaylistItems), len(items))
})
t.Run("Can update playlist", func(t *testing.T) {
items := []playlist.PlaylistItemDTO{
{Title: "influxdb", Value: "influxdb", Type: "dashboard_by_tag"},
{Title: "Backend response times", Value: "2", Type: "dashboard_by_id"},
}
query := playlist.UpdatePlaylistCommand{Name: "NYC office ", OrgId: 1, UID: uid, Interval: "10s", Items: items}
_, err = playlistStore.Update(context.Background(), &query)
require.NoError(t, err)
})
t.Run("Can remove playlist", func(t *testing.T) {
deleteQuery := playlist.DeletePlaylistCommand{UID: uid, OrgId: 1}
err = playlistStore.Delete(context.Background(), &deleteQuery)
require.NoError(t, err)
getQuery := playlist.GetPlaylistByUidQuery{UID: uid, OrgId: 1}
p, err := playlistStore.Get(context.Background(), &getQuery)
require.Error(t, err)
require.Equal(t, uid, p.UID, "playlist should've been removed")
require.ErrorIs(t, err, playlist.ErrPlaylistNotFound)
})
})
t.Run("Delete playlist that doesn't exist", func(t *testing.T) {
deleteQuery := playlist.DeletePlaylistCommand{UID: "654312", OrgId: 1}
err := playlistStore.Delete(context.Background(), &deleteQuery)
require.NoError(t, err)
})
t.Run("Delete playlist with invalid command yields error", func(t *testing.T) {
testCases := []struct {
desc string
cmd playlist.DeletePlaylistCommand
}{
{desc: "none", cmd: playlist.DeletePlaylistCommand{}},
{desc: "no OrgId", cmd: playlist.DeletePlaylistCommand{UID: "1"}},
{desc: "no Uid", cmd: playlist.DeletePlaylistCommand{OrgId: 1}},
}
for _, tc := range testCases {
t.Run(tc.desc, func(t *testing.T) {
err := playlistStore.Delete(context.Background(), &tc.cmd)
require.EqualError(t, err, playlist.ErrCommandValidationFailed.Error())
})
}
})
}

@ -0,0 +1,43 @@
package playlisttest
import (
"context"
"github.com/grafana/grafana/pkg/services/playlist"
)
type FakePlaylistService struct {
ExpectedPlaylist *playlist.Playlist
ExpectedPlaylistDTO *playlist.PlaylistDTO
ExpectedPlaylistItems []playlist.PlaylistItem
ExpectedPlaylists playlist.Playlists
ExpectedError error
}
func NewPlaylistServiveFake() *FakePlaylistService {
return &FakePlaylistService{}
}
func (f *FakePlaylistService) Create(context.Context, *playlist.CreatePlaylistCommand) (*playlist.Playlist, error) {
return f.ExpectedPlaylist, f.ExpectedError
}
func (f *FakePlaylistService) Update(context.Context, *playlist.UpdatePlaylistCommand) (*playlist.PlaylistDTO, error) {
return f.ExpectedPlaylistDTO, f.ExpectedError
}
func (f *FakePlaylistService) Get(context.Context, *playlist.GetPlaylistByUidQuery) (*playlist.Playlist, error) {
return f.ExpectedPlaylist, f.ExpectedError
}
func (f *FakePlaylistService) GetItems(context.Context, *playlist.GetPlaylistItemsByUidQuery) ([]playlist.PlaylistItem, error) {
return f.ExpectedPlaylistItems, f.ExpectedError
}
func (f *FakePlaylistService) Search(context.Context, *playlist.GetPlaylistsQuery) (playlist.Playlists, error) {
return f.ExpectedPlaylists, f.ExpectedError
}
func (f *FakePlaylistService) Delete(ctx context.Context, cmd *playlist.DeletePlaylistCommand) error {
return f.ExpectedError
}

@ -72,11 +72,17 @@ type Store interface {
GetGlobalQuotaByTarget(ctx context.Context, query *models.GetGlobalQuotaByTargetQuery) error
WithTransactionalDbSession(ctx context.Context, callback DBTransactionFunc) error
InTransaction(ctx context.Context, fn func(ctx context.Context) error) error
// deprecated
CreatePlaylist(ctx context.Context, cmd *models.CreatePlaylistCommand) error
// deprecated
UpdatePlaylist(ctx context.Context, cmd *models.UpdatePlaylistCommand) error
// deprecated
GetPlaylist(ctx context.Context, query *models.GetPlaylistByUidQuery) error
// deprecated
DeletePlaylist(ctx context.Context, cmd *models.DeletePlaylistCommand) error
// deprecated
SearchPlaylists(ctx context.Context, query *models.GetPlaylistsQuery) error
// deprecated
GetPlaylistItem(ctx context.Context, query *models.GetPlaylistItemsByUidQuery) error
GetAlertById(ctx context.Context, query *models.GetAlertByIdQuery) error
GetAllAlertQueryHandler(ctx context.Context, query *models.GetAllAlertsQuery) error

@ -8535,6 +8535,14 @@
"tags": ["provisioning"],
"summary": "Get all the contact points.",
"operationId": "RouteGetContactpoints",
"parameters": [
{
"type": "string",
"description": "Filter by name",
"name": "name",
"in": "query"
}
],
"responses": {
"200": {
"description": "ContactPoints",
@ -8874,6 +8882,20 @@
}
}
}
},
"delete": {
"consumes": ["application/json"],
"tags": ["provisioning"],
"summary": "Clears the notification policy tree.",
"operationId": "RouteResetPolicyTree",
"responses": {
"202": {
"description": "Ack",
"schema": {
"$ref": "#/definitions/Ack"
}
}
}
}
},
"/v1/provisioning/templates": {
@ -10250,7 +10272,7 @@
"$ref": "#/definitions/ScheduleDTO"
},
"state": {
"type": "string"
"$ref": "#/definitions/State"
},
"templateVars": {
"type": "object"
@ -10268,9 +10290,6 @@
"CreatePlaylistCommand": {
"type": "object",
"properties": {
"Result": {
"$ref": "#/definitions/Playlist"
},
"interval": {
"type": "string"
},
@ -15496,9 +15515,8 @@
"type": "string"
},
"URL": {
"description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.",
"type": "object",
"title": "A URL represents a parsed URL (technically, a URI reference).",
"title": "URL is a custom URL type that allows validation at configuration load time.",
"properties": {
"ForceQuery": {
"type": "boolean"
@ -15768,9 +15786,6 @@
"UpdatePlaylistCommand": {
"type": "object",
"properties": {
"Result": {
"$ref": "#/definitions/PlaylistDTO"
},
"interval": {
"type": "string"
},
@ -16298,6 +16313,7 @@
}
},
"alertGroup": {
"description": "AlertGroup alert group",
"type": "object",
"required": ["alerts", "labels", "receiver"],
"properties": {
@ -16461,6 +16477,7 @@
}
},
"gettableSilence": {
"description": "GettableSilence gettable silence",
"type": "object",
"required": ["comment", "createdBy", "endsAt", "matchers", "startsAt", "id", "status", "updatedAt"],
"properties": {
@ -16500,7 +16517,6 @@
}
},
"gettableSilences": {
"description": "GettableSilences gettable silences",
"type": "array",
"items": {
"$ref": "#/definitions/gettableSilence"
@ -16634,7 +16650,6 @@
}
},
"receiver": {
"description": "Receiver receiver",
"type": "object",
"required": ["name"],
"properties": {

@ -9380,7 +9380,7 @@
"$ref": "#/definitions/ScheduleDTO"
},
"state": {
"type": "string"
"$ref": "#/definitions/State"
},
"templateVars": {
"type": "object"
@ -9398,9 +9398,6 @@
"CreatePlaylistCommand": {
"type": "object",
"properties": {
"Result": {
"$ref": "#/definitions/Playlist"
},
"interval": {
"type": "string"
},
@ -12676,9 +12673,6 @@
"UpdatePlaylistCommand": {
"type": "object",
"properties": {
"Result": {
"$ref": "#/definitions/PlaylistDTO"
},
"interval": {
"type": "string"
},

Loading…
Cancel
Save