feat(playlist): basic UI support for tags

This commit is contained in:
bergquist 2016-01-28 20:06:50 +01:00
parent 12dfee544f
commit a7de2ceae4
7 changed files with 281 additions and 51 deletions

View File

@ -1,5 +1,6 @@
define([
'./playlists_ctrl',
'./playlist_search',
'./playlist_srv',
'./playlist_edit_ctrl',
'./playlist_routes'

View File

@ -39,34 +39,24 @@
</div>
<br>
<h4>Add dashboards</h4>
<div style="display: inline-block">
<div class="tight-form last">
<ul class="tight-form-list">
<li class="tight-form-item">
Search
</li>
<li>
<input type="text"
class="tight-form-input input-xlarge last"
ng-model="ctrl.searchQuery"
placeholder="dashboard search term"
ng-trim="true"
ng-change="ctrl.search()">
</li>
</ul>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="span5 pull-left">
<h5>Search results ({{ctrl.filteredPlaylistItems.length}})</h5>
<h5>Add dashboards</h5>
<div style="">
<playlist-search class="playlist-search-container" search-started="ctrl.searchStarted(promise)"></playlist-search>
</div>
</div>
</div>
<div class="row">
<div class="span5 pull-left" ng-if="ctrl.filteredDashboards.length > 0">
<h5>Search results ({{ctrl.filteredDashboards.length}})</h5>
<table class="grafana-options-table">
<tr ng-repeat="playlistItem in ctrl.filteredPlaylistItems">
<tr ng-repeat="playlistItem in ctrl.filteredDashboards">
<td style="white-space: nowrap;">
{{playlistItem.title}}
</td>
@ -77,13 +67,22 @@
</button>
</td>
</tr>
<tr ng-if="ctrl.isSearchResultsEmpty()">
<td colspan="2">
<i class="fa fa-warning"></i> Search results empty
</td>
</tr>
</table>
</div>
<div class="playlist-search-results-container" ng-if="ctrl.filteredTags.length > 0">
<div class="row">
<div class="span6 offset1">
<div ng-repeat="tag in ctrl.filteredTags" class="pointer" style="width: 180px; float: left;"
ng-class="{'selected': $index === selectedIndex }"
ng-click="ctrl.addTagPlaylistItem(tag, $event)">
<a class="search-result-tag label label-tag" tag-color-from-name="tag.term">
<i class="fa fa-tag"></i>
<span>{{tag.term}} &nbsp;({{tag.count}})</span>
</a>
</div>
</div>
</div>
</div>
<div class="span5 pull-left">
<h5>Added dashboards</h5>
<table class="grafana-options-table">

View File

@ -0,0 +1,26 @@
<div class="playlist-search-field-wrapper">
<span style="position: relative;">
<input type="text" placeholder="Find dashboards by name" give-focus="ctrl.giveSearchFocus" tabindex="1"
ng-keydown="ctrl.keyDown($event)" ng-model="ctrl.query.query" ng-model-options="{ debounce: 500 }" spellcheck='false' ng-change="ctrl.searchDashboards()" />
</span>
<div class="playlist-search-switches">
<i class="fa fa-filter"></i>
<a class="pointer" href="javascript:void 0;" ng-click="ctrl.showStarred()" tabindex="2">
<i class="fa fa-remove" ng-show="ctrl.query.starred"></i>
starred
</a> |
<a class="pointer" href="javascript:void 0;" ng-click="ctrl.getTags()" tabindex="3">
<i class="fa fa-remove" ng-show="ctrl.tagsMode"></i>
tags
</a>
<span ng-if="ctrl.query.tag.length">
|
<span ng-repeat="tagName in ctrl.query.tag">
<a ng-click="ctrl.removeTag(tagName, $event)" tag-color-from-name="ctrl.tagName" class="label label-tag">
<i class="fa fa-remove"></i>
{{tagName}}
</a>
</span>
</span>
</div>
</div>

View File

@ -6,14 +6,16 @@ import coreModule from '../../core/core_module';
import config from 'app/core/config';
export class PlaylistEditCtrl {
filteredPlaylistItems: any = [];
foundPlaylistItems: any = [];
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) {
@ -30,35 +32,18 @@ export class PlaylistEditCtrl {
this.playlistItems = result;
});
}
this.search();
}
search() {
var query: any = {limit: 10};
if (this.searchQuery) {
query.query = this.searchQuery;
}
this.loading = true;
this.backendSrv.search(query)
.then((results) => {
this.foundPlaylistItems = results;
this.filterFoundPlaylistItems();
})
.finally(() => {
this.loading = false;
});
};
filterFoundPlaylistItems() {
this.filteredPlaylistItems = _.reject(this.foundPlaylistItems, (playlistItem) => {
console.log('filter !');
console.log(this.dashboardresult);
this.filteredDashboards = _.reject(this.dashboardresult, (playlistItem) => {
return _.findWhere(this.playlistItems, (listPlaylistItem) => {
return parseInt(listPlaylistItem.value) === playlistItem.id;
});
});
this.filteredTags = this.tagresult;
};
addPlaylistItem(playlistItem) {
@ -70,6 +55,20 @@ export class PlaylistEditCtrl {
this.filterFoundPlaylistItems();
};
addTagPlaylistItem(tag) {
console.log(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;
@ -104,7 +103,7 @@ export class PlaylistEditCtrl {
};
isSearchResultsEmpty() {
return !this.foundPlaylistItems.length;
return !this.dashboardresult.length;
};
isSearchQueryEmpty() {
@ -119,6 +118,16 @@ export class PlaylistEditCtrl {
return this.loading;
};
searchStarted(promise) {
promise.then((data) => {
console.log('searchStarted: ', data);
this.dashboardresult = data.dashboardResult;
this.tagresult = data.tagResult;
this.filterFoundPlaylistItems();
});
};
movePlaylistItem(playlistItem, offset) {
var currentPosition = this.playlistItems.indexOf(playlistItem);
var newPosition = currentPosition + offset;

View File

@ -0,0 +1,95 @@
///<reference path="../../headers/common.d.ts" />
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((results) => {
console.log('playlist_search_ctrl: ', results);
return results;
});
*/
prom.promise = this.backendSrv.search(this.query).then((result) => {
return {
dashboardResult: result,
tagResult: []
};
});
this.searchStarted(prom);
}
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) => {
console.log('getTags: result', result);
return {
dashboardResult: [],
tagResult: result
};
});
this.searchStarted(prom);
/*
this.searchStarted(prom);
return this.backendSrv.get('/api/dashboards/tags').then((results) => {
this.tagsMode = true;
console.log(results);
});
*/
};
}
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);

View File

@ -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";

99
public/less/playlist.less Normal file
View File

@ -0,0 +1,99 @@
.playlist-search-container {
//left: 59px;
//top: 39px;
margin: 15px;
z-index: 1000;
//position: relative;
position: relative;
width: 700px;
box-shadow: 0px 0px 55px 0px black;
//padding: 10px;
background-color: @grafanaPanelBackground;
//border: 1px solid @grafanaTargetFuncBackground;
.label-tag {
margin-left: 6px;
font-size: 11px;
padding: 2px 6px;
}
}
.playlist-search-switches {
position: relative;
top: -39px;
right: -268px;
}
.playlist-search-field-wrapper {
//padding-bottom: 10px;
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;
}
}