Browse Source

worked on search

Torkel Ödegaard 8 years ago
parent
commit
bc81298d4c

+ 1 - 0
pkg/services/search/models.go

@@ -63,6 +63,7 @@ type FindPersistedDashboardsQuery struct {
 	FolderIds    []int64
 	Tags         []string
 	Limit        int
+	IsBrowse     bool
 
 	Result HitList
 }

+ 1 - 1
public/app/core/components/search/search.ts

@@ -151,7 +151,7 @@ export class SearchCtrl {
   }
 
   toggleFolder(section) {
-    this.searchSrv.toggleFolder(section);
+    this.searchSrv.toggleSection(section);
   }
 }
 

+ 3 - 6
public/app/features/dashboard/impression_store.ts → public/app/core/services/impression_srv.ts

@@ -2,7 +2,7 @@ import store from 'app/core/store';
 import _ from 'lodash';
 import config from 'app/core/config';
 
-export class ImpressionsStore {
+export class ImpressionSrv {
   constructor() {}
 
   addDashboardImpression(dashboardId) {
@@ -44,8 +44,5 @@ export class ImpressionsStore {
   }
 }
 
-var impressions = new ImpressionsStore();
-
-export {
-  impressions
-};
+const impressionSrv = new ImpressionSrv();
+export default impressionSrv;

+ 104 - 22
public/app/core/services/search_srv.ts

@@ -1,23 +1,87 @@
 import _ from 'lodash';
 import coreModule from 'app/core/core_module';
+import impressionSrv from 'app/core/services/impression_srv';
+import store from 'app/core/store';
 
 export class SearchSrv {
+  recentIsOpen: boolean;
+  starredIsOpen: boolean;
 
   /** @ngInject */
-  constructor(private backendSrv) {
+  constructor(private backendSrv, private $q) {
+    this.recentIsOpen = store.getBool('search.sections.recent', true);
+    this.starredIsOpen = store.getBool('search.sections.starred', true);
   }
 
-  browse() {
+  private getRecentDashboards(sections) {
+    return this.queryForRecentDashboards().then(result => {
+      if (result.length > 0) {
+        sections['recent'] = {
+          title: 'Recent Boards',
+          icon: 'fa fa-clock-o',
+          score: -1,
+          expanded: this.recentIsOpen,
+          toggle: this.toggleRecent.bind(this),
+          items: result,
+        };
+      }
+    });
+  }
+
+  private queryForRecentDashboards() {
+    var dashIds = _.take(impressionSrv.getDashboardOpened(), 5);
+    if (dashIds.length === 0) {
+      return Promise.resolve([]);
+    }
+
+    return this.backendSrv.search({ dashboardIds: dashIds }).then(result => {
+      return dashIds.map(orderId => {
+        return this.transformToViewModel(_.find(result, { id: orderId }));
+      }).filter(item => !item.isStarred);
+    });
+  }
+
+  private toggleRecent(section) {
+    this.recentIsOpen = section.expanded = !section.expanded;
+    store.set('search.sections.recent', this.recentIsOpen);
+
+    if (!section.expanded || section.items.length) {
+      return;
+    }
+
+    return this.queryForRecentDashboards().then(result => {
+      section.items = result;
+    });
+  }
+
+  private toggleStarred(section) {
+    this.starredIsOpen = section.expanded = !section.expanded;
+    store.set('search.sections.starred', this.starredIsOpen);
+  }
+
+  private getStarred(sections) {
+    return this.backendSrv.search({starred: true, limit: 5}).then(result => {
+      if (result.length > 0) {
+        sections['starred'] = {
+          title: 'Starred Boards',
+          icon: 'fa fa-star-o',
+          score: -2,
+          expanded: this.starredIsOpen,
+          toggle: this.toggleStarred.bind(this),
+          items: this.transformToViewModel(result),
+        };
+      }
+    });
+  }
+
+  private getDashboardsAndFolders(sections) {
     const rootFolderId = 0;
 
     let query = {
-      folderIds: [rootFolderId]
+      folderIds: [rootFolderId],
     };
 
     return this.backendSrv.search(query).then(results => {
-
-      let sections: any = {};
-
       for (let hit of results) {
         if (hit.type === 'dash-folder') {
           sections[hit.id] = {
@@ -26,7 +90,8 @@ export class SearchSrv {
             items: [],
             icon: 'fa fa-folder',
             score: _.keys(sections).length,
-            uri: hit.uri
+            uri: hit.uri,
+            toggle: this.toggleFolder.bind(this),
           };
         }
       }
@@ -37,7 +102,7 @@ export class SearchSrv {
         items: [],
         icon: 'fa fa-folder-open',
         score: _.keys(sections).length,
-        expanded: true
+        expanded: true,
       };
 
       for (let hit of results) {
@@ -45,18 +110,36 @@ export class SearchSrv {
           continue;
         }
         let section = sections[hit.folderId || 0];
-        hit.url = 'dashboard/' + hit.uri;
-        section.items.push(hit);
+        if (section) {
+          section.items.push(this.transformToViewModel(hit));
+        } else {
+          console.log('Error: dashboard returned from browse search but not folder', hit.id, hit.folderId);
+        }
       }
+    });
+  }
 
+  private browse() {
+    let sections: any = {};
+
+    let promises = [
+      this.getRecentDashboards(sections),
+      this.getStarred(sections),
+      this.getDashboardsAndFolders(sections),
+    ];
+
+    return this.$q.all(promises).then(() => {
       return _.sortBy(_.values(sections), 'score');
     });
   }
 
+  private transformToViewModel(hit) {
+    hit.url = 'dashboard/' + hit.uri;
+    return hit;
+  }
+
   search(options) {
-    if (!options.query &&
-      (!options.tag || options.tag.length === 0) &&
-      !options.starred) {
+    if (!options.query && (!options.tag || options.tag.length === 0) && !options.starred) {
       return this.browse();
     }
 
@@ -65,7 +148,6 @@ export class SearchSrv {
     query.type = 'dash-db';
 
     return this.backendSrv.search(query).then(results => {
-
       let section = {
         hideHeader: true,
         items: [],
@@ -76,15 +158,14 @@ export class SearchSrv {
         if (hit.type === 'dash-folder') {
           continue;
         }
-        hit.url = 'dashboard/' + hit.uri;
-        section.items.push(hit);
+        section.items.push(this.transformToViewModel(hit));
       }
 
       return [section];
     });
   }
 
-  toggleFolder(section) {
+  private toggleFolder(section) {
     section.expanded = !section.expanded;
     section.icon = section.expanded ? 'fa fa-folder-open' : 'fa fa-folder';
 
@@ -93,17 +174,18 @@ export class SearchSrv {
     }
 
     let query = {
-      folderIds: [section.id]
+      folderIds: [section.id],
     };
 
     return this.backendSrv.search(query).then(results => {
-      for (let hit of results) {
-        hit.url = 'dashboard/' + hit.uri;
-        section.items.push(hit);
-      }
+      section.items = _.map(results, this.transformToViewModel);
     });
   }
 
+  toggleSection(section) {
+    section.toggle(section);
+  }
+
   getDashboardTags() {
     return this.backendSrv.get('/api/dashboards/tags');
   }

+ 157 - 56
public/app/core/specs/search_srv.jest.ts

@@ -1,85 +1,188 @@
 import { SearchSrv } from 'app/core/services/search_srv';
 import { BackendSrvMock } from 'test/mocks/backend_srv';
+import impressionSrv from 'app/core/services/impression_srv';
+
+jest.mock('app/core/store', () => {
+  return {
+    getBool: jest.fn(),
+    set: jest.fn(),
+  };
+});
+
+jest.mock('app/core/services/impression_srv', () => {
+  return {
+    getDashboardOpened: jest.fn,
+  };
+});
 
 describe('SearchSrv', () => {
   let searchSrv, backendSrvMock;
 
   beforeEach(() => {
     backendSrvMock = new BackendSrvMock();
-    searchSrv = new SearchSrv(backendSrvMock);
+    searchSrv = new SearchSrv(backendSrvMock, Promise);
+
+    impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([]);
+  });
+
+  describe('With recent dashboards', () => {
+    let results;
+
+    beforeEach(() => {
+      backendSrvMock.search = jest
+        .fn()
+        .mockReturnValueOnce(
+          Promise.resolve([{ id: 2, title: 'second but first' }, { id: 1, title: 'first but second' }]),
+        )
+        .mockReturnValue(Promise.resolve([]));
+
+      impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1, 2]);
+
+      return searchSrv.search({ query: '' }).then(res => {
+        results = res;
+      });
+    });
+
+    it('should include recent dashboards section', () => {
+      expect(results[0].title).toBe('Recent Boards');
+    });
+
+    it('should return order decided by impressions store not api', () => {
+      expect(results[0].items[0].title).toBe('first but second');
+      expect(results[0].items[1].title).toBe('second but first');
+    });
+  });
+
+  describe('With starred dashboards', () => {
+    let results;
+
+    beforeEach(() => {
+      backendSrvMock.search = jest
+        .fn()
+        .mockReturnValue(Promise.resolve([
+          {id: 1, title: 'starred'}
+        ]));
+
+      return searchSrv.search({ query: '' }).then(res => {
+        results = res;
+      });
+    });
+
+    it('should include starred dashboards section', () => {
+      expect(results[0].title).toBe('Starred Boards');
+      expect(results[0].items.length).toBe(1);
+    });
   });
 
-  describe("with no query string and dashboards with folders returned", () => {
+  describe('With starred dashboards and recent', () => {
     let results;
 
     beforeEach(() => {
-      backendSrvMock.search = jest.fn().mockReturnValue(Promise.resolve([
-        {
-          title: 'folder1',
-          type: 'dash-folder',
-          id: 1,
-        },
-        {
-          title: 'dash with no folder',
-          type: 'dash-db',
-          id: 2,
-        },
-        {
-          title: 'dash in folder1 1',
-          type: 'dash-db',
-          id: 3,
-          folderId: 1
-        },
-        {
-          title: 'dash in folder1 2',
-          type: 'dash-db',
-          id: 4,
-          folderId: 1
-        },
-      ]));
-
-      return searchSrv.search({query: ''}).then(res => {
+      backendSrvMock.search = jest
+        .fn()
+        .mockReturnValueOnce(Promise.resolve([
+          {id: 1, title: 'starred and recent', isStarred: true},
+          {id: 2, title: 'recent'}
+        ]))
+        .mockReturnValue(Promise.resolve([
+          {id: 1, title: 'starred and recent'}
+        ]));
+
+      impressionSrv.getDashboardOpened = jest.fn().mockReturnValue([1,2]);
+      return searchSrv.search({ query: '' }).then(res => {
         results = res;
       });
     });
 
-    it("should create sections for each folder and root", () => {
+    it('should not show starred in recent', () => {
+      expect(results[1].title).toBe('Recent Boards');
+      expect(results[1].items[0].title).toBe('recent');
+    });
+
+    it('should show starred', () => {
+      expect(results[0].title).toBe('Starred Boards');
+      expect(results[0].items[0].title).toBe('starred and recent');
+    });
+
+  });
+
+  describe('with no query string and dashboards with folders returned', () => {
+    let results;
+
+    beforeEach(() => {
+      backendSrvMock.search = jest
+        .fn()
+        .mockReturnValueOnce(Promise.resolve([]))
+        .mockReturnValue(
+          Promise.resolve([
+            {
+              title: 'folder1',
+              type: 'dash-folder',
+              id: 1,
+            },
+            {
+              title: 'dash with no folder',
+              type: 'dash-db',
+              id: 2,
+            },
+            {
+              title: 'dash in folder1 1',
+              type: 'dash-db',
+              id: 3,
+              folderId: 1,
+            },
+            {
+              title: 'dash in folder1 2',
+              type: 'dash-db',
+              id: 4,
+              folderId: 1,
+            },
+          ]),
+        );
+
+      return searchSrv.search({ query: '' }).then(res => {
+        results = res;
+      });
+    });
+
+    it('should create sections for each folder and root', () => {
       expect(results).toHaveLength(2);
     });
 
     it('should place folders first', () => {
       expect(results[0].title).toBe('folder1');
     });
-
   });
 
-  describe("with query string and dashboards with folders returned", () => {
+  describe('with query string and dashboards with folders returned', () => {
     let results;
 
     beforeEach(() => {
       backendSrvMock.search = jest.fn();
 
-      backendSrvMock.search.mockReturnValue(Promise.resolve([
-        {
-          id: 2,
-          title: 'dash with no folder',
-          type: 'dash-db',
-        },
-        {
-          id: 3,
-          title: 'dash in folder1 1',
-          type: 'dash-db',
-          folderId: 1,
-          folderTitle: 'folder1',
-        },
-      ]));
-
-      return searchSrv.search({query: 'search'}).then(res => {
+      backendSrvMock.search.mockReturnValue(
+        Promise.resolve([
+          {
+            id: 2,
+            title: 'dash with no folder',
+            type: 'dash-db',
+          },
+          {
+            id: 3,
+            title: 'dash in folder1 1',
+            type: 'dash-db',
+            folderId: 1,
+            folderTitle: 'folder1',
+          },
+        ]),
+      );
+
+      return searchSrv.search({ query: 'search' }).then(res => {
         results = res;
       });
     });
 
-    it("should not specify folder ids", () => {
+    it('should not specify folder ids', () => {
       expect(backendSrvMock.search.mock.calls[0][0].folderIds).toHaveLength(0);
     });
 
@@ -87,33 +190,31 @@ describe('SearchSrv', () => {
       expect(results).toHaveLength(1);
       expect(results[0].hideHeader).toBe(true);
     });
-
   });
 
-  describe("with tags", () => {
+  describe('with tags', () => {
     beforeEach(() => {
       backendSrvMock.search = jest.fn();
       backendSrvMock.search.mockReturnValue(Promise.resolve([]));
 
-      return searchSrv.search({tag: ['atag']}).then(() => {});
+      return searchSrv.search({ tag: ['atag'] }).then(() => {});
     });
 
-    it("should send tags query to backend search", () => {
+    it('should send tags query to backend search', () => {
       expect(backendSrvMock.search.mock.calls[0][0].tag).toHaveLength(1);
     });
   });
 
-  describe("with starred", () => {
+  describe('with starred', () => {
     beforeEach(() => {
       backendSrvMock.search = jest.fn();
       backendSrvMock.search.mockReturnValue(Promise.resolve([]));
 
-      return searchSrv.search({starred: true}).then(() => {});
+      return searchSrv.search({ starred: true }).then(() => {});
     });
 
-    it("should send starred query to backend search", () => {
+    it('should send starred query to backend search', () => {
       expect(backendSrvMock.search.mock.calls[0][0].starred).toEqual(true);
     });
   });
-
 });

+ 0 - 2
public/app/features/dashboard/all.ts

@@ -1,4 +1,3 @@
-
 import './dashboard_ctrl';
 import './alerting_srv';
 import './history/history';
@@ -15,7 +14,6 @@ import './time_srv';
 import './unsavedChangesSrv';
 import './unsaved_changes_modal';
 import './timepicker/timepicker';
-import './impression_store';
 import './upload';
 import './import/dash_import';
 import './export/export_modal';

+ 4 - 3
public/app/features/dashboard/dashboardLoaderSrv.js

@@ -5,12 +5,13 @@ define([
   'jquery',
   'app/core/utils/kbn',
   'app/core/utils/datemath',
-  './impression_store'
+  'app/core/services/impression_srv'
 ],
-function (angular, moment, _, $, kbn, dateMath, impressionStore) {
+function (angular, moment, _, $, kbn, dateMath, impressionSrv) {
   'use strict';
 
   kbn = kbn.default;
+  impressionSrv = impressionSrv.default;
 
   var module = angular.module('grafana.services');
 
@@ -50,7 +51,7 @@ function (angular, moment, _, $, kbn, dateMath, impressionStore) {
       promise.then(function(result) {
 
         if (result.meta.dashboardNotFound !== true) {
-          impressionStore.impressions.addDashboardImpression(result.dashboard.id);
+          impressionSrv.addDashboardImpression(result.dashboard.id);
         }
 
         return result;

+ 1 - 9
public/app/features/dashboard/dashboard_list_ctrl.ts

@@ -26,14 +26,6 @@ export class DashboardListCtrl {
   }
 
   getDashboards() {
-    if (this.query.query.length === 0 &&
-        this.query.tag.length === 0 &&
-        !this.query.starred) {
-      return this.searchSrv.browse().then((result) => {
-        return this.initDashboardList(result);
-      });
-    }
-
     return this.searchSrv.search(this.query).then((result) => {
       return this.initDashboardList(result);
     });
@@ -144,7 +136,7 @@ export class DashboardListCtrl {
   }
 
   toggleFolder(section) {
-    return this.searchSrv.toggleFolder(section);
+    return this.searchSrv.toggleSection(section);
   }
 
   getTags() {

+ 3 - 6
public/app/features/dashboard/specs/dashboard_list_ctrl.jest.ts

@@ -93,7 +93,7 @@ describe('DashboardListCtrl', () => {
         }
       ];
 
-      ctrl = createCtrlWithStubs([], response);
+      ctrl = createCtrlWithStubs(response);
     });
 
     describe('with query filter', () => {
@@ -490,15 +490,12 @@ describe('DashboardListCtrl', () => {
   });
 });
 
-function createCtrlWithStubs(browseResponse: any, searchResponse?: any, tags?: any) {
+function createCtrlWithStubs(searchResponse: any, tags?: any) {
   const searchSrvStub = {
-    browse: () => {
-      return q.resolve(browseResponse);
-    },
     search: (options: any) => {
       return q.resolve(searchResponse);
     },
-    toggleFolder: (section) => {
+    toggleSection: (section) => {
       return;
     },
     getDashboardTags: () => {

+ 2 - 2
public/app/features/plugins/plugin_loader.ts

@@ -13,7 +13,7 @@ import * as datemath from 'app/core/utils/datemath';
 import * as fileExport from 'app/core/utils/file_export';
 import * as flatten from 'app/core/utils/flatten';
 import * as ticks from 'app/core/utils/ticks';
-import {impressions} from 'app/features/dashboard/impression_store';
+import impressionSrv from 'app/core/services/impression_srv';
 import builtInPlugins from './built_in_plugins';
 import * as d3 from 'd3';
 
@@ -78,7 +78,7 @@ exposeToPlugin('vendor/npm/rxjs/Rx', {
 });
 
 exposeToPlugin('app/features/dashboard/impression_store', {
-  impressions: impressions,
+  impressions: impressionSrv,
   __esModule: true
 });
 

+ 2 - 2
public/app/plugins/panel/dashlist/module.ts

@@ -1,6 +1,6 @@
 import _ from 'lodash';
 import {PanelCtrl} from 'app/plugins/sdk';
-import {impressions} from 'app/features/dashboard/impression_store';
+import impressionSrv from 'app/core/services/impression_srv';
 
 class DashListCtrl extends PanelCtrl {
   static templateUrl = 'module.html';
@@ -123,7 +123,7 @@ class DashListCtrl extends PanelCtrl {
       return Promise.resolve();
     }
 
-    var dashIds = _.take(impressions.getDashboardOpened(), this.panel.limit);
+    var dashIds = _.take(impressionSrv.getDashboardOpened(), this.panel.limit);
     return this.backendSrv.search({dashboardIds: dashIds, limit: this.panel.limit}).then(result => {
       this.groups[1].list = dashIds.map(orderId => {
         return _.find(result, dashboard => {

+ 0 - 2
public/app/plugins/panel/singlestat/module.ts

@@ -592,8 +592,6 @@ class SingleStatCtrl extends MetricsPanelCtrl {
       if (!ctrl.data) { return; }
       data = ctrl.data;
 
-      console.log('singlestat', elem.html());
-
       // get thresholds
       data.thresholds = panel.thresholds.split(',').map(function(strVale) {
         return Number(strVale.trim());

+ 1 - 1
public/sass/layout/_page.scss

@@ -123,7 +123,7 @@
 
 .page-breadcrumbs {
   display: flex;
-  padding: 3px 1.5rem 1.5rem 1.5rem;
+  padding: 10px 25px;
   line-height: 0.5;
 }