Commit 313735bd by Torkel Ödegaard

search: worked on search results

parent ccbd1800
...@@ -15,6 +15,7 @@ type Hit struct { ...@@ -15,6 +15,7 @@ type Hit struct {
Id int64 `json:"id"` Id int64 `json:"id"`
Title string `json:"title"` Title string `json:"title"`
Uri string `json:"uri"` Uri string `json:"uri"`
Slug string `json:"slug"`
Type HitType `json:"type"` Type HitType `json:"type"`
Tags []string `json:"tags"` Tags []string `json:"tags"`
IsStarred bool `json:"isStarred"` IsStarred bool `json:"isStarred"`
......
...@@ -260,6 +260,7 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard ...@@ -260,6 +260,7 @@ func makeQueryResult(query *search.FindPersistedDashboardsQuery, res []Dashboard
Id: item.Id, Id: item.Id,
Title: item.Title, Title: item.Title,
Uri: "db/" + item.Slug, Uri: "db/" + item.Slug,
Slug: item.Slug,
Type: getHitType(item), Type: getHitType(item),
FolderId: item.FolderId, FolderId: item.FolderId,
FolderTitle: item.FolderTitle, FolderTitle: item.FolderTitle,
......
<div ng-repeat="section in ctrl.results" class="search-section"> <div ng-repeat="section in ctrl.results" class="search-section">
<a class="search-section__header pointer" ng-hide="section.hideHeader" ng-class="{'selected': section.selected}" ng-click="ctrl.toggleFolderExpand(section)"> <div class="search-section__header pointer" ng-hide="section.hideHeader" ng-class="{'selected': section.selected}" ng-click="ctrl.toggleFolderExpand(section)">
<div ng-click="ctrl.toggleSelection(section, $event)"> <div ng-click="ctrl.toggleSelection(section, $event)">
<gf-form-switch <gf-form-switch
ng-show="ctrl.editable" ng-show="ctrl.editable"
on-change="ctrl.selectionChanged($event)" on-change="ctrl.selectionChanged($event)"
checked="section.checked" checked="section.checked"
switch-class="gf-form-switch--transparent gf-form-switch--search-result__section"> switch-class="gf-form-switch--transparent gf-form-switch--search-result__section">
</gf-form-switch> </gf-form-switch>
</div> </div>
<i class="search-section__header__icon" ng-class="section.icon"></i> <i class="search-section__header__icon" ng-class="section.icon"></i>
<span class="search-section__header__text">{{::section.title}}</span> <span class="search-section__header__text">{{::section.title}}</span>
<div ng-show="ctrl.editable && section.id > 0" ng-click="ctrl.navigateToFolder(section, $event)"> <a ng-show="section.url" href="{{section.url}}" class="search-section__header__link">
<i class="fa fa-cog search-section__header__toggle"></i>&nbsp; <i class="fa fa-cog"></i>
</div> </a>
<i class="fa fa-angle-down search-section__header__toggle" ng-show="section.expanded"></i> <i class="fa fa-angle-down search-section__header__toggle" ng-show="section.expanded"></i>
<i class="fa fa-angle-right search-section__header__toggle" ng-hide="section.expanded"></i> <i class="fa fa-angle-right search-section__header__toggle" ng-hide="section.expanded"></i>
</a> </div>
<div class="search-section__header" ng-show="section.hideHeader"></div> <div class="search-section__header" ng-show="section.hideHeader"></div>
...@@ -23,10 +23,10 @@ ...@@ -23,10 +23,10 @@
<a ng-repeat="item in section.items" class="search-item" ng-class="{'selected': item.selected}" ng-href="{{::item.url}}"> <a ng-repeat="item in section.items" class="search-item" ng-class="{'selected': item.selected}" ng-href="{{::item.url}}">
<div ng-click="ctrl.toggleSelection(item, $event)"> <div ng-click="ctrl.toggleSelection(item, $event)">
<gf-form-switch <gf-form-switch
ng-show="ctrl.editable" ng-show="ctrl.editable"
on-change="ctrl.selectionChanged()" on-change="ctrl.selectionChanged()"
checked="item.checked" checked="item.checked"
switch-class="gf-form-switch--transparent gf-form-switch--search-result__item"> switch-class="gf-form-switch--transparent gf-form-switch--search-result__item">
</gf-form-switch> </gf-form-switch>
</div> </div>
<span class="search-item__icon"> <span class="search-item__icon">
...@@ -34,9 +34,6 @@ ...@@ -34,9 +34,6 @@
</span> </span>
<span class="search-item__body"> <span class="search-item__body">
<div class="search-item__body-title">{{::item.title}}</div> <div class="search-item__body-title">{{::item.title}}</div>
<div class="search-item__body-sub-title" ng-show="item.folderTitle && section.hideHeader">
{{::item.folderTitle}}
</div>
</span> </span>
<span class="search-item__tags"> <span class="search-item__tags">
<span ng-click="ctrl.selectTag(tag, $event)" ng-repeat="tag in item.tags" tag-color-from-name="tag" class="label label-tag"> <span ng-click="ctrl.selectTag(tag, $event)" ng-repeat="tag in item.tags" tag-color-from-name="tag" class="label label-tag">
......
...@@ -78,17 +78,17 @@ function setupAngularRoutes($routeProvider, $locationProvider) { ...@@ -78,17 +78,17 @@ function setupAngularRoutes($routeProvider, $locationProvider) {
controller : 'CreateFolderCtrl', controller : 'CreateFolderCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
}) })
.when('/dashboards/folder/:folderId/:type/:slug/permissions', { .when('/dashboards/folder/:folderId/:slug/permissions', {
templateUrl: 'public/app/features/dashboard/partials/folder_permissions.html', templateUrl: 'public/app/features/dashboard/partials/folder_permissions.html',
controller : 'FolderPermissionsCtrl', controller : 'FolderPermissionsCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
}) })
.when('/dashboards/folder/:folderId/:type/:slug/settings', { .when('/dashboards/folder/:folderId/:slug/settings', {
templateUrl: 'public/app/features/dashboard/partials/folder_settings.html', templateUrl: 'public/app/features/dashboard/partials/folder_settings.html',
controller : 'FolderSettingsCtrl', controller : 'FolderSettingsCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
}) })
.when('/dashboards/folder/:folderId/:type/:slug', { .when('/dashboards/folder/:folderId/:slug', {
templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html', templateUrl: 'public/app/features/dashboard/partials/folder_dashboards.html',
controller : 'FolderDashboardsCtrl', controller : 'FolderDashboardsCtrl',
controllerAs: 'ctrl', controllerAs: 'ctrl',
......
...@@ -84,105 +84,96 @@ export class SearchSrv { ...@@ -84,105 +84,96 @@ export class SearchSrv {
}); });
} }
private getDashboardsAndFolders(sections) { private transformToViewModel(hit) {
const rootFolderId = 0; hit.url = 'dashboard/db/' + hit.slug;
return hit;
let query = {
folderIds: [rootFolderId],
};
return this.backendSrv.search(query).then(results => {
for (let hit of results) {
if (hit.type === 'dash-folder') {
sections[hit.id] = {
id: hit.id,
title: hit.title,
items: [],
icon: 'fa fa-folder',
score: _.keys(sections).length,
uri: hit.uri,
toggle: this.toggleFolder.bind(this),
};
}
}
sections[0] = {
id: 0,
title: 'Root',
items: [],
icon: 'fa fa-folder-open',
score: _.keys(sections).length,
expanded: true,
};
for (let hit of results) {
if (hit.type === 'dash-folder') {
continue;
}
let section = sections[hit.folderId || 0];
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(options) { search(options) {
let sections: any = {}; let sections: any = {};
let promises = []; let promises = [];
let query = _.clone(options);
let hasFilters = options.query ||
(options.tag && options.tag.length > 0) || options.starred ||
(options.folderIds && options.folderIds.length > 0);
if (!options.skipRecent) { if (!options.skipRecent && !hasFilters) {
promises.push(this.getRecentDashboards(sections)); promises.push(this.getRecentDashboards(sections));
} }
if (!options.skipStarred) { if (!options.skipStarred && !hasFilters) {
promises.push(this.getStarred(sections)); promises.push(this.getStarred(sections));
} }
promises.push(this.getDashboardsAndFolders(sections)); query.folderIds = query.folderIds || [];
if (!hasFilters) {
query.folderIds = [0];
}
promises.push(this.backendSrv.search(query).then(results => {
return this.handleSearchResult(sections, results);
}));
return this.$q.all(promises).then(() => { return this.$q.all(promises).then(() => {
return _.sortBy(_.values(sections), 'score'); return _.sortBy(_.values(sections), 'score');
}); });
} }
private transformToViewModel(hit) { private handleSearchResult(sections, results) {
hit.url = 'dashboard/' + hit.uri; if (results.length === 0) {
return hit; return sections;
}
search(options) {
if (!options.folderIds && !options.query && (!options.tag || options.tag.length === 0) && !options.starred) {
return this.browse(options);
} }
let query = _.clone(options); // create folder index
query.folderIds = options.folderIds || []; for (let hit of results) {
query.type = 'dash-db'; if (hit.type === 'dash-folder') {
sections[hit.id] = {
return this.backendSrv.search(query).then(results => { id: hit.id,
if (results.length === 0) { title: hit.title,
return results; expanded: false,
items: [],
toggle: this.toggleFolder.bind(this),
url: `dashboards/folder/${hit.id}/${hit.slug}`,
icon: 'fa fa-folder',
score: _.keys(sections).length,
};
} }
}
let section = { for (let hit of results) {
hideHeader: true, if (hit.type === 'dash-folder') {
items: [], continue;
expanded: true, }
};
for (let hit of results) { let section = sections[hit.folderId || 0];
if (hit.type === 'dash-folder') { if (!section) {
continue; if (hit.folderId) {
section = {
id: hit.folderId,
title: hit.folderTitle,
url: `dashboards/folder/${hit.folderId}/${hit.folderSlug}`,
items: [],
icon: 'fa fa-folder-open',
toggle: this.toggleFolder.bind(this),
score: _.keys(sections).length,
};
} else {
section = {
id: 0,
title: 'Root',
items: [],
icon: 'fa fa-folder-open',
toggle: this.toggleFolder.bind(this),
score: _.keys(sections).length,
};
} }
section.items.push(this.transformToViewModel(hit)); // add section
sections[hit.folderId || 0] = section;
} }
return [section]; section.expanded = true;
}); section.items.push(this.transformToViewModel(hit));
}
} }
private toggleFolder(section) { private toggleFolder(section) {
......
...@@ -214,9 +214,8 @@ describe('SearchSrv', () => { ...@@ -214,9 +214,8 @@ describe('SearchSrv', () => {
expect(backendSrvMock.search.mock.calls[0][0].folderIds).toHaveLength(0); expect(backendSrvMock.search.mock.calls[0][0].folderIds).toHaveLength(0);
}); });
it('should place all results in a single section', () => { it('should group results by folder', () => {
expect(results).toHaveLength(1); expect(results).toHaveLength(2);
expect(results[0].hideHeader).toBe(true);
}); });
}); });
......
...@@ -6,10 +6,12 @@ export class FolderDashboardsCtrl { ...@@ -6,10 +6,12 @@ export class FolderDashboardsCtrl {
/** @ngInject */ /** @ngInject */
constructor(private backendSrv, navModelSrv, private $routeParams) { constructor(private backendSrv, navModelSrv, private $routeParams) {
if (this.$routeParams.folderId && this.$routeParams.type && this.$routeParams.slug) { if (this.$routeParams.folderId && this.$routeParams.slug) {
this.folderId = $routeParams.folderId; this.folderId = $routeParams.folderId;
new FolderPageLoader(this.backendSrv, this.$routeParams).load(this, this.folderId, 'manage-folder-dashboards'); const loader = new FolderPageLoader(this.backendSrv, this.$routeParams);
loader.load(this, this.folderId, 'manage-folder-dashboards');
} }
} }
} }
...@@ -9,7 +9,7 @@ export class FolderPageLoader { ...@@ -9,7 +9,7 @@ export class FolderPageLoader {
icon: 'fa fa-folder-open', icon: 'fa fa-folder-open',
id: 'manage-folder', id: 'manage-folder',
subTitle: 'Manage folder dashboards & permissions', subTitle: 'Manage folder dashboards & permissions',
url: '/fsdfds', url: '',
text: '', text: '',
breadcrumbs: [ breadcrumbs: [
{ title: 'Dashboards', url: '/dashboards' }, { title: 'Dashboards', url: '/dashboards' },
...@@ -41,11 +41,11 @@ export class FolderPageLoader { ...@@ -41,11 +41,11 @@ export class FolderPageLoader {
} }
}; };
return this.backendSrv.getDashboard(this.$routeParams.type, this.$routeParams.slug).then(result => { return this.backendSrv.getDashboard('db', this.$routeParams.slug).then(result => {
const folderTitle = result.dashboard.title; const folderTitle = result.dashboard.title;
ctrl.navModel.main.text = ''; ctrl.navModel.main.text = '';
ctrl.navModel.main.breadcrumbs = [ ctrl.navModel.main.breadcrumbs = [
{ title: 'Dashboards', uri: '/dashboards' }, { title: 'Dashboards', url: '/dashboards' },
{ title: folderTitle } { title: folderTitle }
]; ];
...@@ -65,6 +65,6 @@ export class FolderPageLoader { ...@@ -65,6 +65,6 @@ export class FolderPageLoader {
} }
createFolderUrl(folderId: number, type: string, slug: string) { createFolderUrl(folderId: number, type: string, slug: string) {
return `/dashboards/folder/${folderId}/${type}/${slug}`; return `/dashboards/folder/${folderId}/${slug}`;
} }
} }
...@@ -6,7 +6,7 @@ export class FolderPermissionsCtrl { ...@@ -6,7 +6,7 @@ export class FolderPermissionsCtrl {
/** @ngInject */ /** @ngInject */
constructor(private backendSrv, navModelSrv, private $routeParams) { constructor(private backendSrv, navModelSrv, private $routeParams) {
if (this.$routeParams.folderId && this.$routeParams.type && this.$routeParams.slug) { if (this.$routeParams.folderId && this.$routeParams.slug) {
this.folderId = $routeParams.folderId; this.folderId = $routeParams.folderId;
new FolderPageLoader(this.backendSrv, this.$routeParams).load(this, this.folderId, 'manage-folder-permissions'); new FolderPageLoader(this.backendSrv, this.$routeParams).load(this, this.folderId, 'manage-folder-permissions');
......
...@@ -11,7 +11,7 @@ export class FolderSettingsCtrl { ...@@ -11,7 +11,7 @@ export class FolderSettingsCtrl {
/** @ngInject */ /** @ngInject */
constructor(private backendSrv, navModelSrv, private $routeParams, private $location) { constructor(private backendSrv, navModelSrv, private $routeParams, private $location) {
if (this.$routeParams.folderId && this.$routeParams.type && this.$routeParams.slug) { if (this.$routeParams.folderId && this.$routeParams.slug) {
this.folderId = $routeParams.folderId; this.folderId = $routeParams.folderId;
this.folderPageLoader = new FolderPageLoader(this.backendSrv, this.$routeParams); this.folderPageLoader = new FolderPageLoader(this.backendSrv, this.$routeParams);
......
...@@ -65,11 +65,11 @@ ...@@ -65,11 +65,11 @@
</div> </div>
<div class="gf-form-button-row"> <div class="gf-form-button-row">
<button type="submit" class="btn btn-success" ng-disabled="ctrl.current.readOnly" ng-click="ctrl.saveChanges()">Save</button> <button type="submit" class="btn btn-success" ng-disabled="ctrl.current.readOnly" ng-click="ctrl.saveChanges()">Save &amp; Test</button>
<button type="submit" class="btn btn-danger" ng-disabled="ctrl.current.readOnly" ng-show="!ctrl.isNew" ng-click="ctrl.delete()"> <button type="submit" class="btn btn-danger" ng-disabled="ctrl.current.readOnly" ng-show="!ctrl.isNew" ng-click="ctrl.delete()">
Delete Delete
</button> </button>
<a class="btn btn-link" href="datasources">Cancel</a> <a class="btn btn-inverse" href="datasources">Back</a>
</div> </div>
<br /> <br />
......
...@@ -137,6 +137,12 @@ ...@@ -137,6 +137,12 @@
&:hover, &.selected { &:hover, &.selected {
color: $text-color; color: $text-color;
} }
&:hover {
.search-section__header__link {
opacity: 1;
}
}
} }
.search-section__header__icon { .search-section__header__icon {
...@@ -154,6 +160,13 @@ ...@@ -154,6 +160,13 @@
line-height: 24px; line-height: 24px;
} }
.search-section__header__link {
padding: 2px 10px 0;
color: $text-muted;
opacity: 0;
transition: opacity 150ms ease-in-out;
}
.search-item { .search-item {
@include list-item(); @include list-item();
...@@ -181,12 +194,6 @@ ...@@ -181,12 +194,6 @@
color: $list-item-link-color; color: $list-item-link-color;
} }
.search-item__body-sub-title {
color: $text-muted;
font-size: $font-size-sm;
line-height: 9pt;
}
.search-item__icon { .search-item__icon {
padding: 5px; padding: 5px;
flex: 0 0 auto; flex: 0 0 auto;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment