mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
search: worked on search results
This commit is contained in:
@@ -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
|
|||||||
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,
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
<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>
|
<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 @@
|
|||||||
<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 @@
|
|||||||
</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) {
|
|||||||
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 {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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] = {
|
||||||
|
id: hit.id,
|
||||||
|
title: hit.title,
|
||||||
|
expanded: false,
|
||||||
|
items: [],
|
||||||
|
toggle: this.toggleFolder.bind(this),
|
||||||
|
url: `dashboards/folder/${hit.id}/${hit.slug}`,
|
||||||
|
icon: 'fa fa-folder',
|
||||||
|
score: _.keys(sections).length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return this.backendSrv.search(query).then(results => {
|
for (let hit of results) {
|
||||||
if (results.length === 0) {
|
if (hit.type === 'dash-folder') {
|
||||||
return results;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let section = {
|
let section = sections[hit.folderId || 0];
|
||||||
hideHeader: true,
|
if (!section) {
|
||||||
items: [],
|
if (hit.folderId) {
|
||||||
expanded: true,
|
section = {
|
||||||
};
|
id: hit.folderId,
|
||||||
|
title: hit.folderTitle,
|
||||||
for (let hit of results) {
|
url: `dashboards/folder/${hit.folderId}/${hit.folderSlug}`,
|
||||||
if (hit.type === 'dash-folder') {
|
items: [],
|
||||||
continue;
|
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', () => {
|
|||||||
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 {
|
|||||||
|
|
||||||
/** @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 {
|
|||||||
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 {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
|||||||
|
|
||||||
/** @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 {
|
|||||||
|
|
||||||
/** @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 @@
|
|||||||
</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 & 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 @@
|
|||||||
&: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 @@
|
|||||||
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 @@
|
|||||||
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;
|
||||||
|
|||||||
Reference in New Issue
Block a user