diff --git a/docs/sources/reference/playlist.md b/docs/sources/reference/playlist.md
index 3e90f7361a3..5d546b1a3df 100644
--- a/docs/sources/reference/playlist.md
+++ b/docs/sources/reference/playlist.md
@@ -18,11 +18,11 @@ The Playlist feature can be accessed from Grafana's sidemenu. Click the 'Playlis
Click on "New Playlist" button to create a new playlist. Firstly, name your playlist and configure a time interval for Grafana to wait on a particular Dashboard before advancing to the next one on the Playlist.
-You can search Dashboards by name (or use a regular expression), and add them to your Playlist. By default, your starred dashboards will appear as candidates for the Playlist.
+You can search Dashboards by name (or use a regular expression), and add them to your Playlist. Or you could add tags which will include all the dashboards that belongs to a tag when the playlist start playing. By default, your starred dashboards will appear as candidates for the Playlist.
Be sure to click the "Add to dashboard" button next to the Dashboard name to add it to the Playlist. To remove a dashboard from the playlist click on "Remove[x]" button from the playlist.
-Since the Playlist is basically a list of Dashboards, ensure that all the Dashboards you want to appear in your Playlist are added here.
+Since the Playlist is basically a list of Dashboards, ensure that all the Dashboards you want to appear in your Playlist are added here.
## Saving the playlist
diff --git a/pkg/api/playlist.go b/pkg/api/playlist.go
index 0b017d6e2cb..abc092b8021 100644
--- a/pkg/api/playlist.go
+++ b/pkg/api/playlist.go
@@ -1,11 +1,8 @@
package api
import (
- "errors"
- "strconv"
-
"github.com/grafana/grafana/pkg/bus"
- "github.com/grafana/grafana/pkg/log"
+ _ "github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
)
@@ -101,39 +98,6 @@ func LoadPlaylistItems(id int64) ([]m.PlaylistItem, error) {
return *itemQuery.Result, nil
}
-func LoadPlaylistDashboards(id int64) ([]m.PlaylistDashboardDto, error) {
- playlistItems, _ := LoadPlaylistItems(id)
-
- dashboardIds := make([]int64, 0)
-
- for _, i := range playlistItems {
- dashboardId, _ := strconv.ParseInt(i.Value, 10, 64)
- dashboardIds = append(dashboardIds, dashboardId)
- }
-
- if len(dashboardIds) == 0 {
- return make([]m.PlaylistDashboardDto, 0), nil
- }
-
- dashboardQuery := m.GetPlaylistDashboardsQuery{DashboardIds: dashboardIds}
- if err := bus.Dispatch(&dashboardQuery); err != nil {
- log.Warn("dashboardquery failed: %v", err)
- return nil, errors.New("Playlist not found")
- }
-
- dtos := make([]m.PlaylistDashboardDto, 0)
- for _, item := range *dashboardQuery.Result {
- dtos = append(dtos, m.PlaylistDashboardDto{
- Id: item.Id,
- Slug: item.Slug,
- Title: item.Title,
- Uri: "db/" + item.Slug,
- })
- }
-
- return dtos, nil
-}
-
func GetPlaylistItems(c *middleware.Context) Response {
id := c.ParamsInt64(":id")
@@ -147,9 +111,9 @@ func GetPlaylistItems(c *middleware.Context) Response {
}
func GetPlaylistDashboards(c *middleware.Context) Response {
- id := c.ParamsInt64(":id")
+ playlistId := c.ParamsInt64(":id")
- playlists, err := LoadPlaylistDashboards(id)
+ playlists, err := LoadPlaylistDashboards(c.OrgId, c.UserId, playlistId)
if err != nil {
return ApiError(500, "Could not load dashboards", err)
}
diff --git a/pkg/api/playlist_play.go b/pkg/api/playlist_play.go
new file mode 100644
index 00000000000..9bfcf1532fa
--- /dev/null
+++ b/pkg/api/playlist_play.go
@@ -0,0 +1,88 @@
+package api
+
+import (
+ "errors"
+ "strconv"
+
+ "github.com/grafana/grafana/pkg/bus"
+ _ "github.com/grafana/grafana/pkg/log"
+ m "github.com/grafana/grafana/pkg/models"
+ "github.com/grafana/grafana/pkg/services/search"
+)
+
+func populateDashboardsById(dashboardByIds []int64) ([]m.PlaylistDashboardDto, error) {
+ result := make([]m.PlaylistDashboardDto, 0)
+
+ if len(dashboardByIds) > 0 {
+ dashboardQuery := m.GetDashboardsQuery{DashboardIds: dashboardByIds}
+ if err := bus.Dispatch(&dashboardQuery); err != nil {
+ return result, errors.New("Playlist not found") //TODO: dont swallow error
+ }
+
+ for _, item := range *dashboardQuery.Result {
+ result = append(result, m.PlaylistDashboardDto{
+ Id: item.Id,
+ Slug: item.Slug,
+ Title: item.Title,
+ Uri: "db/" + item.Slug,
+ })
+ }
+ }
+
+ return result, nil
+}
+
+func populateDashboardsByTag(orgId, userId int64, dashboardByTag []string) []m.PlaylistDashboardDto {
+ result := make([]m.PlaylistDashboardDto, 0)
+
+ if len(dashboardByTag) > 0 {
+ for _, tag := range dashboardByTag {
+ searchQuery := search.Query{
+ Title: "",
+ Tags: []string{tag},
+ UserId: userId,
+ Limit: 100,
+ IsStarred: false,
+ OrgId: orgId,
+ }
+
+ if err := bus.Dispatch(&searchQuery); err == nil {
+ for _, item := range searchQuery.Result {
+ result = append(result, m.PlaylistDashboardDto{
+ Id: item.Id,
+ Title: item.Title,
+ Uri: item.Uri,
+ })
+ }
+ }
+ }
+ }
+
+ return result
+}
+
+func LoadPlaylistDashboards(orgId, userId, playlistId int64) ([]m.PlaylistDashboardDto, error) {
+ playlistItems, _ := LoadPlaylistItems(playlistId)
+
+ dashboardByIds := make([]int64, 0)
+ dashboardByTag := make([]string, 0)
+
+ for _, i := range playlistItems {
+ if i.Type == "dashboard_by_id" {
+ dashboardId, _ := strconv.ParseInt(i.Value, 10, 64)
+ dashboardByIds = append(dashboardByIds, dashboardId)
+ }
+
+ if i.Type == "dashboard_by_tag" {
+ dashboardByTag = append(dashboardByTag, i.Value)
+ }
+ }
+
+ result := make([]m.PlaylistDashboardDto, 0)
+
+ var k, _ = populateDashboardsById(dashboardByIds)
+ result = append(result, k...)
+ result = append(result, populateDashboardsByTag(orgId, userId, dashboardByTag)...)
+
+ return result, nil
+}
diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go
index ddf5dd244f5..63ed1f5c006 100644
--- a/pkg/models/dashboards.go
+++ b/pkg/models/dashboards.go
@@ -146,3 +146,8 @@ type GetDashboardTagsQuery struct {
OrgId int64
Result []*DashboardTagCloudItem
}
+
+type GetDashboardsQuery struct {
+ DashboardIds []int64
+ Result *[]Dashboard
+}
diff --git a/pkg/models/playlist.go b/pkg/models/playlist.go
index 4c37c369b3e..4c6eacbb6a6 100644
--- a/pkg/models/playlist.go
+++ b/pkg/models/playlist.go
@@ -76,9 +76,7 @@ type UpdatePlaylistCommand struct {
OrgId int64 `json:"-"`
Id int64 `json:"id" binding:"Required"`
Name string `json:"name" binding:"Required"`
- Type string `json:"type"`
Interval string `json:"interval"`
- Data []int64 `json:"data"`
Items []PlaylistItemDTO `json:"items"`
Result *PlaylistDTO
@@ -86,9 +84,7 @@ type UpdatePlaylistCommand struct {
type CreatePlaylistCommand struct {
Name string `json:"name" binding:"Required"`
- Type string `json:"type"`
Interval string `json:"interval"`
- Data []int64 `json:"data"`
Items []PlaylistItemDTO `json:"items"`
OrgId int64 `json:"-"`
@@ -121,8 +117,3 @@ type GetPlaylistItemsByIdQuery struct {
PlaylistId int64
Result *[]PlaylistItem
}
-
-type GetPlaylistDashboardsQuery struct {
- DashboardIds []int64
- Result *PlaylistDashboards
-}
diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go
index bbec541589c..2a8ff2dc941 100644
--- a/pkg/services/sqlstore/dashboard.go
+++ b/pkg/services/sqlstore/dashboard.go
@@ -14,6 +14,7 @@ import (
func init() {
bus.AddHandler("sql", SaveDashboard)
bus.AddHandler("sql", GetDashboard)
+ bus.AddHandler("sql", GetDashboards)
bus.AddHandler("sql", DeleteDashboard)
bus.AddHandler("sql", SearchDashboards)
bus.AddHandler("sql", GetDashboardTags)
@@ -223,3 +224,20 @@ func DeleteDashboard(cmd *m.DeleteDashboardCommand) error {
return nil
})
}
+
+func GetDashboards(query *m.GetDashboardsQuery) error {
+ if len(query.DashboardIds) == 0 {
+ return m.ErrCommandValidationFailed
+ }
+
+ var dashboards = make([]m.Dashboard, 0)
+
+ err := x.In("id", query.DashboardIds).Find(&dashboards)
+ query.Result = &dashboards
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/services/sqlstore/playlist.go b/pkg/services/sqlstore/playlist.go
index ee0b3b950c7..56fae9d3feb 100644
--- a/pkg/services/sqlstore/playlist.go
+++ b/pkg/services/sqlstore/playlist.go
@@ -15,7 +15,6 @@ func init() {
bus.AddHandler("sql", DeletePlaylist)
bus.AddHandler("sql", SearchPlaylists)
bus.AddHandler("sql", GetPlaylist)
- bus.AddHandler("sql", GetPlaylistDashboards)
bus.AddHandler("sql", GetPlaylistItem)
}
@@ -162,20 +161,3 @@ func GetPlaylistItem(query *m.GetPlaylistItemsByIdQuery) error {
return err
}
-
-func GetPlaylistDashboards(query *m.GetPlaylistDashboardsQuery) error {
- if len(query.DashboardIds) == 0 {
- return m.ErrCommandValidationFailed
- }
-
- var dashboards = make(m.PlaylistDashboards, 0)
-
- err := x.In("id", query.DashboardIds).Find(&dashboards)
- query.Result = &dashboards
-
- if err != nil {
- return err
- }
-
- return nil
-}
diff --git a/pkg/services/sqlstore/playlist_test.go b/pkg/services/sqlstore/playlist_test.go
new file mode 100644
index 00000000000..ba530f74fcf
--- /dev/null
+++ b/pkg/services/sqlstore/playlist_test.go
@@ -0,0 +1,44 @@
+package sqlstore
+
+import (
+ "testing"
+
+ . "github.com/smartystreets/goconvey/convey"
+
+ m "github.com/grafana/grafana/pkg/models"
+)
+
+func TestPlaylistDataAccess(t *testing.T) {
+
+ Convey("Testing Playlist data access", t, func() {
+ InitTestDB(t)
+
+ Convey("Can create playlist", func() {
+ items := []m.PlaylistItemDTO{
+ {Title: "graphite", Value: "graphite", Type: "dashboard_by_tag"},
+ {Title: "Backend response times", Value: "3", Type: "dashboard_by_id"},
+ }
+ cmd := m.CreatePlaylistCommand{Name: "NYC office", Interval: "10m", OrgId: 1, Items: items}
+ err := CreatePlaylist(&cmd)
+ So(err, ShouldBeNil)
+
+ Convey("can update playlist", func() {
+ items := []m.PlaylistItemDTO{
+ {Title: "influxdb", Value: "influxdb", Type: "dashboard_by_tag"},
+ {Title: "Backend response times", Value: "2", Type: "dashboard_by_id"},
+ }
+ query := m.UpdatePlaylistCommand{Name: "NYC office ", OrgId: 1, Id: 1, Interval: "10s", Items: items}
+ err = UpdatePlaylist(&query)
+
+ So(err, ShouldBeNil)
+
+ Convey("can remove playlist", func() {
+ query := m.DeletePlaylistCommand{Id: 1}
+ err = DeletePlaylist(&query)
+
+ So(err, ShouldBeNil)
+ })
+ })
+ })
+ })
+}
diff --git a/public/app/features/playlist/all.js b/public/app/features/playlist/all.js
index a37de0f4612..3b07b0d74c5 100644
--- a/public/app/features/playlist/all.js
+++ b/public/app/features/playlist/all.js
@@ -1,5 +1,6 @@
define([
'./playlists_ctrl',
+ './playlist_search',
'./playlist_srv',
'./playlist_edit_ctrl',
'./playlist_routes'
diff --git a/public/app/features/playlist/partials/playlist.html b/public/app/features/playlist/partials/playlist.html
index 6f47c65568f..b82f00f69f9 100644
--- a/public/app/features/playlist/partials/playlist.html
+++ b/public/app/features/playlist/partials/playlist.html
@@ -1,14 +1,14 @@
-
Search results ({{filteredPlaylistItems.length}})
+
Add dashboards
+
+
+
+
+
+
+
Search results ({{ctrl.filteredDashboards.length}})
-
+
|
{{playlistItem.title}}
|
- |
-
- |
- Search results empty
- |
-
+
Added dashboards
-
- |
+ |
+ |
{{playlistItem.title}}
|
+
+
+
+ {{playlistItem.title}}
+
+ |
+
-
+
-
+
-
+
|
@@ -113,11 +119,11 @@
Save
+ ng-disabled="ctrl.playlistEditForm.$invalid || ctrl.isPlaylistEmpty()"
+ ng-click="ctrl.savePlaylist(ctrl.playlist, ctrl.playlistItems)">Save
Cancel
+ ng-click="ctrl.backToList()">Cancel
diff --git a/public/app/features/playlist/partials/playlist_search.html b/public/app/features/playlist/partials/playlist_search.html
new file mode 100644
index 00000000000..e3005c1b74b
--- /dev/null
+++ b/public/app/features/playlist/partials/playlist_search.html
@@ -0,0 +1,26 @@
+
diff --git a/public/app/features/playlist/partials/playlists.html b/public/app/features/playlist/partials/playlists.html
index a8a4724b34c..4aa7944623c 100644
--- a/public/app/features/playlist/partials/playlists.html
+++ b/public/app/features/playlist/partials/playlists.html
@@ -19,7 +19,7 @@
|
-
+
|
{{playlist.name}}
|
@@ -39,7 +39,7 @@
-
+
|
diff --git a/public/app/features/playlist/playlist_edit_ctrl.js b/public/app/features/playlist/playlist_edit_ctrl.js
deleted file mode 100644
index 9b493c47d25..00000000000
--- a/public/app/features/playlist/playlist_edit_ctrl.js
+++ /dev/null
@@ -1,144 +0,0 @@
-define([
- 'angular',
- 'app/core/config',
- 'lodash'
-],
-function (angular, config, _) {
- 'use strict';
-
- var module = angular.module('grafana.controllers');
-
- module.controller('PlaylistEditCtrl', function($scope, playlistSrv, backendSrv, $location, $route) {
- $scope.filteredPlaylistItems = [];
- $scope.foundPlaylistItems = [];
- $scope.searchQuery = '';
- $scope.loading = false;
- $scope.playlist = {
- interval: '10m',
- };
- $scope.playlistItems = [];
-
- $scope.init = function() {
- if ($route.current.params.id) {
- var playlistId = $route.current.params.id;
-
- backendSrv.get('/api/playlists/' + playlistId)
- .then(function(result) {
- $scope.playlist = result;
- });
-
- backendSrv.get('/api/playlists/' + playlistId + '/items')
- .then(function(result) {
- $scope.playlistItems = result;
- });
- }
-
- $scope.search();
- };
-
- $scope.search = function() {
- var query = {limit: 10};
-
- if ($scope.searchQuery) {
- query.query = $scope.searchQuery;
- }
-
- $scope.loading = true;
-
- backendSrv.search(query)
- .then(function(results) {
- $scope.foundPlaylistItems = results;
- $scope.filterFoundPlaylistItems();
- })
- .finally(function() {
- $scope.loading = false;
- });
- };
-
- $scope.filterFoundPlaylistItems = function() {
- $scope.filteredPlaylistItems = _.reject($scope.foundPlaylistItems, function(playlistItem) {
- return _.findWhere($scope.playlistItems, function(listPlaylistItem) {
- return parseInt(listPlaylistItem.value) === playlistItem.id;
- });
- });
- };
-
- $scope.addPlaylistItem = function(playlistItem) {
- playlistItem.value = playlistItem.id.toString();
- playlistItem.type = 'dashboard_by_id';
- playlistItem.order = $scope.playlistItems.length + 1;
-
- $scope.playlistItems.push(playlistItem);
- $scope.filterFoundPlaylistItems();
- };
-
- $scope.removePlaylistItem = function(playlistItem) {
- _.remove($scope.playlistItems, function(listedPlaylistItem) {
- return playlistItem === listedPlaylistItem;
- });
- $scope.filterFoundPlaylistItems();
- };
-
- $scope.savePlaylist = function(playlist, playlistItems) {
- var savePromise;
-
- playlist.items = playlistItems;
-
- savePromise = playlist.id
- ? backendSrv.put('/api/playlists/' + playlist.id, playlist)
- : backendSrv.post('/api/playlists', playlist);
-
- savePromise
- .then(function() {
- $scope.appEvent('alert-success', ['Playlist saved', '']);
- $location.path('/playlists');
- }, function() {
- $scope.appEvent('alert-error', ['Unable to save playlist', '']);
- });
- };
-
- $scope.isNew = function() {
- return !$scope.playlist.id;
- };
-
- $scope.isPlaylistEmpty = function() {
- return !$scope.playlistItems.length;
- };
-
- $scope.isSearchResultsEmpty = function() {
- return !$scope.foundPlaylistItems.length;
- };
-
- $scope.isSearchQueryEmpty = function() {
- return $scope.searchQuery === '';
- };
-
- $scope.backToList = function() {
- $location.path('/playlists');
- };
-
- $scope.isLoading = function() {
- return $scope.loading;
- };
-
- $scope.movePlaylistItem = function(playlistItem, offset) {
- var currentPosition = $scope.playlistItems.indexOf(playlistItem);
- var newPosition = currentPosition + offset;
-
- if (newPosition >= 0 && newPosition < $scope.playlistItems.length) {
- $scope.playlistItems.splice(currentPosition, 1);
- $scope.playlistItems.splice(newPosition, 0, playlistItem);
- }
- };
-
- $scope.movePlaylistItemUp = function(playlistItem) {
- $scope.movePlaylistItem(playlistItem, -1);
- };
-
- $scope.movePlaylistItemDown = function(playlistItem) {
- $scope.movePlaylistItem(playlistItem, 1);
- };
-
- $scope.init();
- });
-});
diff --git a/public/app/features/playlist/playlist_edit_ctrl.ts b/public/app/features/playlist/playlist_edit_ctrl.ts
new file mode 100644
index 00000000000..0f5bdc86858
--- /dev/null
+++ b/public/app/features/playlist/playlist_edit_ctrl.ts
@@ -0,0 +1,132 @@
+///
+
+import angular from 'angular';
+import _ from 'lodash';
+import coreModule from '../../core/core_module';
+import config from 'app/core/config';
+
+export class PlaylistEditCtrl {
+ filteredDashboards: any = [];
+ filteredTags: any = [];
+ searchQuery: string = '';
+ loading: boolean = false;
+ playlist: any = {
+ interval: '10m',
+ };
+ playlistItems: any = [];
+ dashboardresult: any = [];
+ tagresult: any = [];
+
+ /** @ngInject */
+ constructor(private $scope, private playlistSrv, private backendSrv, private $location, private $route) {
+ if ($route.current.params.id) {
+ var playlistId = $route.current.params.id;
+
+ backendSrv.get('/api/playlists/' + playlistId)
+ .then((result) => {
+ this.playlist = result;
+ });
+
+ backendSrv.get('/api/playlists/' + playlistId + '/items')
+ .then((result) => {
+ this.playlistItems = result;
+ });
+ }
+ }
+
+ filterFoundPlaylistItems() {
+ this.filteredDashboards = _.reject(this.dashboardresult, (playlistItem) => {
+ return _.findWhere(this.playlistItems, (listPlaylistItem) => {
+ return parseInt(listPlaylistItem.value) === playlistItem.id;
+ });
+ });
+
+ this.filteredTags = this.tagresult;
+ }
+
+ addPlaylistItem(playlistItem) {
+ playlistItem.value = playlistItem.id.toString();
+ playlistItem.type = 'dashboard_by_id';
+ playlistItem.order = this.playlistItems.length + 1;
+
+ this.playlistItems.push(playlistItem);
+ this.filterFoundPlaylistItems();
+ }
+
+ addTagPlaylistItem(tag) {
+ var playlistItem: any = {
+ value: tag.term,
+ type: 'dashboard_by_tag',
+ order: this.playlistItems.length + 1,
+ title: tag.term
+ };
+
+ this.playlistItems.push(playlistItem);
+ this.filterFoundPlaylistItems();
+ }
+
+ removePlaylistItem(playlistItem) {
+ _.remove(this.playlistItems, (listedPlaylistItem) => {
+ return playlistItem === listedPlaylistItem;
+ });
+ this.filterFoundPlaylistItems();
+ };
+
+ savePlaylist(playlist, playlistItems) {
+ var savePromise;
+
+ playlist.items = playlistItems;
+
+ savePromise = playlist.id
+ ? this.backendSrv.put('/api/playlists/' + playlist.id, playlist)
+ : this.backendSrv.post('/api/playlists', playlist);
+
+ savePromise
+ .then(() => {
+ this.$scope.appEvent('alert-success', ['Playlist saved', '']);
+ this.$location.path('/playlists');
+ }, () => {
+ this.$scope.appEvent('alert-error', ['Unable to save playlist', '']);
+ });
+ }
+
+ isNew() {
+ return !this.playlist.id;
+ }
+
+ isPlaylistEmpty() {
+ return !this.playlistItems.length;
+ }
+
+ backToList() {
+ this.$location.path('/playlists');
+ }
+
+ searchStarted(promise) {
+ promise.then((data) => {
+ this.dashboardresult = data.dashboardResult;
+ this.tagresult = data.tagResult;
+ this.filterFoundPlaylistItems();
+ });
+ }
+
+ movePlaylistItem(playlistItem, offset) {
+ var currentPosition = this.playlistItems.indexOf(playlistItem);
+ var newPosition = currentPosition + offset;
+
+ if (newPosition >= 0 && newPosition < this.playlistItems.length) {
+ this.playlistItems.splice(currentPosition, 1);
+ this.playlistItems.splice(newPosition, 0, playlistItem);
+ }
+ }
+
+ movePlaylistItemUp(playlistItem) {
+ this.movePlaylistItem(playlistItem, -1);
+ }
+
+ movePlaylistItemDown(playlistItem) {
+ this.movePlaylistItem(playlistItem, 1);
+ }
+}
+
+coreModule.controller('PlaylistEditCtrl', PlaylistEditCtrl);
diff --git a/public/app/features/playlist/playlist_routes.js b/public/app/features/playlist/playlist_routes.js
index 2de1f7b30c8..a3f9cc26f8c 100644
--- a/public/app/features/playlist/playlist_routes.js
+++ b/public/app/features/playlist/playlist_routes.js
@@ -1,4 +1,4 @@
-define([
+ define([
'angular',
'app/core/config',
'lodash'
@@ -12,14 +12,17 @@ function (angular) {
$routeProvider
.when('/playlists', {
templateUrl: 'app/features/playlist/partials/playlists.html',
+ controllerAs: 'ctrl',
controller : 'PlaylistsCtrl'
})
.when('/playlists/create', {
templateUrl: 'app/features/playlist/partials/playlist.html',
+ controllerAs: 'ctrl',
controller : 'PlaylistEditCtrl'
})
.when('/playlists/edit/:id', {
templateUrl: 'app/features/playlist/partials/playlist.html',
+ controllerAs: 'ctrl',
controller : 'PlaylistEditCtrl'
})
.when('/playlists/play/:id', {
diff --git a/public/app/features/playlist/playlist_search.ts b/public/app/features/playlist/playlist_search.ts
new file mode 100644
index 00000000000..6663b106ab7
--- /dev/null
+++ b/public/app/features/playlist/playlist_search.ts
@@ -0,0 +1,83 @@
+///
+
+import angular from 'angular';
+import config from 'app/core/config';
+import _ from 'lodash';
+import $ from 'jquery';
+import coreModule from '../../core/core_module';
+
+export class PlaylistSearchCtrl {
+ query: any;
+ tagsMode: boolean;
+
+ searchStarted: any;
+
+ /** @ngInject */
+ constructor(private $scope, private $location, private $timeout, private backendSrv, private contextSrv) {
+ this.query = { query: '', tag: [], starred: false };
+
+ $timeout(() => {
+ this.query.query = '';
+ this.searchDashboards();
+ }, 100);
+ }
+
+ searchDashboards() {
+ this.tagsMode = false;
+ var prom: any = {};
+
+ prom.promise = this.backendSrv.search(this.query).then((result) => {
+ return {
+ dashboardResult: result,
+ tagResult: []
+ };
+ });
+
+ this.searchStarted(prom);
+ }
+
+ showStarred() {
+ this.query.starred = !this.query.starred;
+ this.searchDashboards();
+ }
+
+ queryHasNoFilters() {
+ return this.query.query === '' && this.query.starred === false && this.query.tag.length === 0;
+ }
+
+ filterByTag(tag, evt) {
+ this.query.tag.push(tag);
+ this.searchDashboards();
+ if (evt) {
+ evt.stopPropagation();
+ evt.preventDefault();
+ }
+ }
+
+ getTags() {
+ var prom: any = {};
+ prom.promise = this.backendSrv.get('/api/dashboards/tags').then((result) => {
+ return {
+ dashboardResult: [],
+ tagResult: result
+ };
+ });
+
+ this.searchStarted(prom);
+ }
+}
+
+export function playlistSearchDirective() {
+ return {
+ restrict: 'E',
+ templateUrl: 'app/features/playlist/partials/playlist_search.html',
+ controller: PlaylistSearchCtrl,
+ bindToController: true,
+ controllerAs: 'ctrl',
+ scope: {
+ searchStarted: '&'
+ },
+ };
+}
+
+coreModule.directive('playlistSearch', playlistSearchDirective);
diff --git a/public/app/features/playlist/playlists_ctrl.js b/public/app/features/playlist/playlists_ctrl.js
deleted file mode 100644
index 375492e6772..00000000000
--- a/public/app/features/playlist/playlists_ctrl.js
+++ /dev/null
@@ -1,43 +0,0 @@
-define([
- 'angular',
- 'lodash'
-],
-function (angular, _) {
- 'use strict';
-
- var module = angular.module('grafana.controllers');
-
- module.controller('PlaylistsCtrl', function($scope, $location, backendSrv) {
- backendSrv.get('/api/playlists')
- .then(function(result) {
- $scope.playlists = result;
- });
-
- $scope.removePlaylistConfirmed = function(playlist) {
- _.remove($scope.playlists, {id: playlist.id});
-
- backendSrv.delete('/api/playlists/' + playlist.id)
- .then(function() {
- $scope.appEvent('alert-success', ['Playlist deleted', '']);
- }, function() {
- $scope.appEvent('alert-error', ['Unable to delete playlist', '']);
- $scope.playlists.push(playlist);
- });
- };
-
- $scope.removePlaylist = function(playlist) {
-
- $scope.appEvent('confirm-modal', {
- title: 'Confirm delete playlist',
- text: 'Are you sure you want to delete playlist ' + playlist.name + '?',
- yesText: "Delete",
- icon: "fa-warning",
- onConfirm: function() {
- $scope.removePlaylistConfirmed(playlist);
- }
- });
-
- };
-
- });
-});
diff --git a/public/app/features/playlist/playlists_ctrl.ts b/public/app/features/playlist/playlists_ctrl.ts
new file mode 100644
index 00000000000..10d6d86d309
--- /dev/null
+++ b/public/app/features/playlist/playlists_ctrl.ts
@@ -0,0 +1,44 @@
+///
+
+import angular from 'angular';
+import _ from 'lodash';
+import coreModule from '../../core/core_module';
+
+export class PlaylistsCtrl {
+ playlists: any;
+
+ /** @ngInject */
+ constructor(private $scope, private $location, private backendSrv) {
+ backendSrv.get('/api/playlists')
+ .then((result) => {
+ this.playlists = result;
+ });
+ }
+
+ removePlaylistConfirmed(playlist) {
+ _.remove(this.playlists, { id: playlist.id });
+
+ this.backendSrv.delete('/api/playlists/' + playlist.id)
+ .then(() => {
+ this.$scope.appEvent('alert-success', ['Playlist deleted', '']);
+ }, () => {
+ this.$scope.appEvent('alert-error', ['Unable to delete playlist', '']);
+ this.playlists.push(playlist);
+ });
+ }
+
+ removePlaylist(playlist) {
+
+ this.$scope.appEvent('confirm-modal', {
+ title: 'Confirm delete playlist',
+ text: 'Are you sure you want to delete playlist ' + playlist.name + '?',
+ yesText: "Delete",
+ icon: "fa-warning",
+ onConfirm: () => {
+ this.removePlaylistConfirmed(playlist);
+ }
+ });
+ }
+}
+
+coreModule.controller('PlaylistsCtrl', PlaylistsCtrl);
diff --git a/public/app/features/playlist/specs/playlist-edit-ctrl-specs.ts b/public/app/features/playlist/specs/playlist-edit-ctrl-specs.ts
deleted file mode 100644
index 4a71979b8ab..00000000000
--- a/public/app/features/playlist/specs/playlist-edit-ctrl-specs.ts
+++ /dev/null
@@ -1,86 +0,0 @@
-import '../playlist_edit_ctrl';
-import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
-import helpers from 'test/specs/helpers';
-
-describe('PlaylistEditCtrl', function() {
- var ctx = new helpers.ControllerTestContext();
-
- var searchResult = [
- {
- id: 2,
- title: 'dashboard: 2'
- },
- {
- id: 3,
- title: 'dashboard: 3'
- }
- ];
-
- var playlistSrv = {};
- var backendSrv = {
- search: (query) => {
- return ctx.$q.when(searchResult);
- }
- };
-
- beforeEach(angularMocks.module('grafana.core'));
- beforeEach(angularMocks.module('grafana.controllers'));
- beforeEach(angularMocks.module('grafana.services'));
- beforeEach(ctx.providePhase({
- playlistSrv: playlistSrv,
- backendSrv: backendSrv,
- $route: { current: { params: { } } },
- }));
-
- beforeEach(ctx.createControllerPhase('PlaylistEditCtrl'));
-
- beforeEach(() => {
- ctx.scope.$digest();
- });
-
- describe('searchresult returns 2 dashboards', function() {
- it('found dashboard should be 2', function() {
- expect(ctx.scope.foundPlaylistItems.length).to.be(2);
- });
-
- it('filtred dashboard should be 2', function() {
- expect(ctx.scope.filteredPlaylistItems.length).to.be(2);
- });
-
- describe('adds one dashboard to playlist', () => {
- beforeEach(() => {
- ctx.scope.addPlaylistItem({ id: 2, title: 'dashboard: 2' });
- });
-
- it('playlistitems should be increased by one', () => {
- expect(ctx.scope.playlistItems.length).to.be(1);
- });
-
- it('filtred playlistitems should be reduced by one', () => {
- expect(ctx.scope.filteredPlaylistItems.length).to.be(1);
- });
-
- it('found dashboard should be 2', function() {
- expect(ctx.scope.foundPlaylistItems.length).to.be(2);
- });
-
- describe('removes one dashboard from playlist', () => {
- beforeEach(() => {
- ctx.scope.removePlaylistItem(ctx.scope.playlistItems[0]);
- });
-
- it('playlistitems should be increased by one', () => {
- expect(ctx.scope.playlistItems.length).to.be(0);
- });
-
- it('found dashboard should be 2', function() {
- expect(ctx.scope.foundPlaylistItems.length).to.be(2);
- });
-
- it('filtred playlist should be reduced by one', () => {
- expect(ctx.scope.filteredPlaylistItems.length).to.be(2);
- });
- });
- });
- });
-});
diff --git a/public/app/features/playlist/specs/playlist_edit_ctrl_specs.ts b/public/app/features/playlist/specs/playlist_edit_ctrl_specs.ts
new file mode 100644
index 00000000000..e4df82d624b
--- /dev/null
+++ b/public/app/features/playlist/specs/playlist_edit_ctrl_specs.ts
@@ -0,0 +1,69 @@
+import '../playlist_edit_ctrl';
+import {describe, beforeEach, it, expect} from 'test/lib/common';
+import {PlaylistEditCtrl} from '../playlist_edit_ctrl';
+
+describe.only('PlaylistEditCtrl', function() {
+ var ctx: any;
+ beforeEach(() => {
+ ctx = new PlaylistEditCtrl(null, null, null, null, { current: { params: {} } });
+
+ ctx.dashboardresult = [
+ { id: 2, title: 'dashboard: 2' },
+ { id: 3, title: 'dashboard: 3' }
+ ];
+
+ ctx.tagresult = [
+ { term: 'graphie', count: 1 },
+ { term: 'nyc', count: 2 }
+ ];
+ });
+
+ describe('searchresult returns 2 dashboards', function() {
+ it('found dashboard should be 2', function() {
+ expect(ctx.dashboardresult.length).to.be(2);
+ });
+
+ it('filtred dashboard should be 2', function() {
+ ctx.filterFoundPlaylistItems();
+ expect(ctx.filteredDashboards.length).to.be(2);
+ });
+
+ describe('adds one dashboard to playlist', () => {
+ beforeEach(() => {
+ ctx.addPlaylistItem({ id: 2, title: 'dashboard: 2' });
+ ctx.filterFoundPlaylistItems();
+ });
+
+ it('playlistitems should be increased by one', () => {
+ expect(ctx.playlistItems.length).to.be(1);
+ });
+
+ it('filtred playlistitems should be reduced by one', () => {
+ expect(ctx.filteredDashboards.length).to.be(1);
+ });
+
+ it('found dashboard should be 2', function() {
+ expect(ctx.dashboardresult.length).to.be(2);
+ });
+
+ describe('removes one dashboard from playlist', () => {
+ beforeEach(() => {
+ ctx.removePlaylistItem(ctx.playlistItems[0]);
+ ctx.filterFoundPlaylistItems();
+ });
+
+ it('playlistitems should be increased by one', () => {
+ expect(ctx.playlistItems.length).to.be(0);
+ });
+
+ it('found dashboard should be 2', function() {
+ expect(ctx.dashboardresult.length).to.be(2);
+ });
+
+ it('filtred playlist should be reduced by one', () => {
+ expect(ctx.filteredDashboards.length).to.be(2);
+ });
+ });
+ });
+ });
+});
diff --git a/public/less/grafana.less b/public/less/grafana.less
index 0e82615baf1..689b5c947e7 100644
--- a/public/less/grafana.less
+++ b/public/less/grafana.less
@@ -8,6 +8,7 @@
@import "bootstrap-tagsinput.less";
@import "tables_lists.less";
@import "search.less";
+@import "playlist.less";
@import "panel.less";
@import "forms.less";
@import "tightform.less";
diff --git a/public/less/playlist.less b/public/less/playlist.less
new file mode 100644
index 00000000000..5aa5e960979
--- /dev/null
+++ b/public/less/playlist.less
@@ -0,0 +1,93 @@
+.playlist-search-container {
+ margin: 15px;
+ z-index: 1000;
+ position: relative;
+ width: 700px;
+ box-shadow: 0px 0px 55px 0px black;
+ background-color: @grafanaPanelBackground;
+
+ .label-tag {
+ margin-left: 6px;
+ font-size: 11px;
+ padding: 2px 6px;
+ }
+}
+
+.playlist-search-switches {
+ position: relative;
+ top: -39px;
+ left: 260px;
+}
+
+.playlist-search-field-wrapper {
+ input {
+ width: 100%;
+ padding: 8px 8px;
+ height: 100%;
+ box-sizing: border-box;
+ }
+ button {
+ margin: 0 4px 0 0;
+ }
+ > span {
+ display: block;
+ overflow: hidden;
+ }
+}
+
+.playlist-search-results-container {
+ min-height: 100px;
+ overflow: auto;
+ display: block;
+ line-height: 28px;
+
+ .search-item:hover, .search-item.selected {
+ background-color: @grafanaListHighlight;
+ }
+
+ .selected {
+ .search-result-tag {
+ opacity: 0.70;
+ color: white;
+ }
+ }
+
+ .fa-star, .fa-star-o {
+ padding-left: 13px;
+ }
+
+ .fa-star {
+ color: @orange;
+ }
+
+ .search-result-link {
+ color: @grafanaListMainLinkColor;
+ .fa {
+ padding-right: 10px;
+ }
+ }
+
+ .search-item {
+ display: block;
+ padding: 3px 10px;
+ white-space: nowrap;
+ background-color: @grafanaListBackground;
+ margin-bottom: 4px;
+ .search-result-icon:before {
+ content: "\f009";
+ }
+
+ &.search-item-dash-home .search-result-icon:before {
+ content: "\f015";
+ }
+ }
+
+ .search-result-tags {
+ float: right;
+ }
+
+ .search-result-actions {
+ float: right;
+ padding-left: 20px;
+ }
+}
\ No newline at end of file