Jelajahi Sumber

feat(playlist): basic UI support for tags

bergquist 10 tahun lalu
induk
melakukan
a7de2ceae4

+ 1 - 0
public/app/features/playlist/all.js

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

+ 25 - 26
public/app/features/playlist/partials/playlist.html

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

+ 26 - 0
public/app/features/playlist/partials/playlist_search.html

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

+ 34 - 25
public/app/features/playlist/playlist_edit_ctrl.ts

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

+ 95 - 0
public/app/features/playlist/playlist_search.ts

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

+ 1 - 0
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";

+ 99 - 0
public/less/playlist.less

@@ -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;
+  }
+}