mirror of
https://github.com/valitydev/redash.git
synced 2024-11-07 01:25:16 +00:00
Favorites: page for favorite dashboards and queries
This commit is contained in:
parent
3974adeacd
commit
fcc75cbd16
@ -11,19 +11,31 @@
|
||||
<div class="col-lg-3">
|
||||
<input type='text' class='form-control' placeholder="Search Dashboards..."
|
||||
ng-change="$ctrl.update()" ng-model="$ctrl.searchText" autofocus/>
|
||||
<div class='list-group m-t-10 m-b-10 tags-list tiled'>
|
||||
<a href="" class="list-group-item">
|
||||
<div class="list-group m-t-10 m-b-10 tags-list tiled">
|
||||
<a href="dashboards" class="list-group-item"
|
||||
ng-class="{active: $ctrl.currentPage == 'all'}">
|
||||
<span style="padding-left: 13px;"></span>
|
||||
All Dashboards
|
||||
</a>
|
||||
|
||||
<a href="dashboards/favorite" class="list-group-item"
|
||||
ng-class="{active: $ctrl.currentPage == 'favorites'}">
|
||||
<span class="btn-favourite">
|
||||
<i class="fa fa-star" aria-hidden="true"></i>
|
||||
</span>
|
||||
Favorites
|
||||
</a>
|
||||
<a href="" class="list-group-item">
|
||||
<img ng-src="{{$ctrl.currentUser.profile_image_url}}" class="profile__image--navbar" width="16" style="margin-right: 0;"/>My Dashboards
|
||||
<a href="dashboards/my" class="list-group-item"
|
||||
ng-class="{active: $ctrl.currentPage == 'my'}">
|
||||
<img ng-src="{{$ctrl.currentUser.profile_image_url}}" class="profile__image--navbar"
|
||||
width="13" style="margin-right: 0;"/>
|
||||
My Dashboards
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<a ng-repeat='tag in $ctrl.allTags' ng-class='{"active": $ctrl.tagIsSelected(tag)}'
|
||||
class='list-group-item' ng-click='$ctrl.toggleTag($event, tag)'>
|
||||
<div class="list-group m-t-10 tags-list tiled" ng-if="$ctrl.allTags.length > 0">
|
||||
<a ng-repeat="tag in $ctrl.allTags" ng-class="{active: $ctrl.selectedTags.has(tag)}"
|
||||
class="list-group-item" ng-click="$ctrl.toggleTag($event, tag)">
|
||||
{{ tag }}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -4,31 +4,40 @@ import { Paginator } from '@/lib/pagination';
|
||||
import template from './dashboard-list.html';
|
||||
import './dashboard-list.css';
|
||||
|
||||
|
||||
function DashboardListCtrl(Dashboard, $location) {
|
||||
function DashboardListCtrl($scope, currentUser, $location) {
|
||||
const TAGS_REGEX = /(^([\w\s]|[^\u0000-\u007F])+):|(#([\w-]|[^\u0000-\u007F])+)/ig;
|
||||
|
||||
const page = parseInt($location.search().page || 1, 10);
|
||||
|
||||
this.defaultOptions = {};
|
||||
this.dashboards = Dashboard.query({}); // shared promise
|
||||
// use $parent because we're using a component as route target instead of controller;
|
||||
// $parent refers to scope created for the page by router
|
||||
this.resource = $scope.$parent.$resolve.resource;
|
||||
this.currentPage = $scope.$parent.$resolve.currentPage;
|
||||
|
||||
this.selectedTags = []; // in scope because it needs to be accessed inside a table refresh
|
||||
this.defaultOptions = {};
|
||||
this.dashboards = this.resource({}); // shared promise
|
||||
|
||||
this.selectedTags = new Set();
|
||||
this.searchText = '';
|
||||
|
||||
this.tagIsSelected = tag => this.selectedTags.indexOf(tag) > -1;
|
||||
this.currentUser = currentUser;
|
||||
|
||||
this.toggleTag = ($event, tag) => {
|
||||
if (this.tagIsSelected(tag)) {
|
||||
if ($event.shiftKey) {
|
||||
this.selectedTags = this.selectedTags.filter(e => e !== tag);
|
||||
if ($event.shiftKey) {
|
||||
// toggle tag
|
||||
if (this.selectedTags.has(tag)) {
|
||||
this.selectedTags.delete(tag);
|
||||
} else {
|
||||
this.selectedTags = [];
|
||||
this.selectedTags.add(tag);
|
||||
}
|
||||
} else if ($event.shiftKey) {
|
||||
this.selectedTags.push(tag);
|
||||
} else {
|
||||
this.selectedTags = [tag];
|
||||
// if the tag is the only selected, deselect it, otherwise select only it
|
||||
if (this.selectedTags.has(tag) && (this.selectedTags.size === 1)) {
|
||||
this.selectedTags.clear();
|
||||
} else {
|
||||
this.selectedTags.clear();
|
||||
this.selectedTags.add(tag);
|
||||
}
|
||||
}
|
||||
|
||||
this.update();
|
||||
@ -59,13 +68,13 @@ function DashboardListCtrl(Dashboard, $location) {
|
||||
dashboard.untagged_name = dashboard.name.replace(TAGS_REGEX, '').trim();
|
||||
return dashboard;
|
||||
}).filter((value) => {
|
||||
if (this.selectedTags.length) {
|
||||
const valueTags = new Set(value.tags);
|
||||
const tagMatch = this.selectedTags;
|
||||
const filteredMatch = tagMatch.filter(x => valueTags.has(x));
|
||||
if (tagMatch.length !== filteredMatch.length) {
|
||||
return false;
|
||||
}
|
||||
const valueTags = new Set(value.tags);
|
||||
const matchesAllTags = _.all(
|
||||
[...this.selectedTags.values()],
|
||||
tag => valueTags.has(tag),
|
||||
);
|
||||
if (!matchesAllTags) {
|
||||
return false;
|
||||
}
|
||||
if (this.searchText && this.searchText.length) {
|
||||
if (!value.untagged_name.toLowerCase().includes(this.searchText.toLowerCase())) {
|
||||
@ -91,10 +100,41 @@ export default function init(ngModule) {
|
||||
const route = {
|
||||
template: '<page-dashboard-list></page-dashboard-list>',
|
||||
reloadOnSearch: false,
|
||||
title: 'Dashboards',
|
||||
};
|
||||
|
||||
return {
|
||||
'/dashboards': route,
|
||||
'/dashboards': _.extend({
|
||||
title: 'Dashboards',
|
||||
resolve: {
|
||||
currentPage: () => 'all',
|
||||
resource(Dashboard) {
|
||||
'ngInject';
|
||||
|
||||
return Dashboard.query.bind(Dashboard);
|
||||
},
|
||||
},
|
||||
}, route),
|
||||
'/dashboards/my': _.extend({
|
||||
title: 'My Dashboards',
|
||||
resolve: {
|
||||
currentPage: () => 'my',
|
||||
resource(Dashboard) {
|
||||
'ngInject';
|
||||
|
||||
return Dashboard.myDashboards.bind(Dashboard);
|
||||
},
|
||||
},
|
||||
}, route),
|
||||
'/dashboards/favorite': _.extend({
|
||||
title: 'Favorite Dashboards',
|
||||
resolve: {
|
||||
currentPage: () => 'favorites',
|
||||
resource(Dashboard) {
|
||||
'ngInject';
|
||||
|
||||
return Dashboard.favorites.bind(Dashboard);
|
||||
},
|
||||
},
|
||||
}, route),
|
||||
};
|
||||
}
|
||||
|
@ -1,28 +1,23 @@
|
||||
import moment from 'moment';
|
||||
import _ from 'underscore';
|
||||
|
||||
import { LivePaginator } from '@/lib/pagination';
|
||||
import template from './queries-list.html';
|
||||
|
||||
class QueriesListCtrl {
|
||||
constructor($location, Title, Query) {
|
||||
constructor($scope, $location, Query, currentUser) {
|
||||
const page = parseInt($location.search().page || 1, 10);
|
||||
|
||||
this.defaultOptions = {};
|
||||
|
||||
const self = this;
|
||||
// use $parent because we're using a component as route target instead of controller;
|
||||
// $parent refers to scope created for the page by router
|
||||
this.resource = $scope.$parent.$resolve.resource;
|
||||
this.currentPage = $scope.$parent.$resolve.currentPage;
|
||||
|
||||
switch ($location.path()) {
|
||||
case '/queries':
|
||||
Title.set('Queries');
|
||||
this.resource = Query.query.bind(Query);
|
||||
break;
|
||||
case '/queries/my':
|
||||
Title.set('My Queries');
|
||||
this.resource = Query.myQueries.bind(Query);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
this.currentUser = currentUser;
|
||||
|
||||
const self = this;
|
||||
|
||||
function queriesFetcher(requestedPage, itemsPerPage, paginator) {
|
||||
$location.search('page', requestedPage);
|
||||
@ -62,14 +57,62 @@ export default function init(ngModule) {
|
||||
controller: QueriesListCtrl,
|
||||
});
|
||||
|
||||
const route = {
|
||||
template: '<page-queries-list></page-queries-list>',
|
||||
reloadOnSearch: false,
|
||||
};
|
||||
|
||||
return {
|
||||
'/queries': {
|
||||
template: '<page-queries-list></page-queries-list>',
|
||||
reloadOnSearch: false,
|
||||
},
|
||||
'/queries/my': {
|
||||
template: '<page-queries-list></page-queries-list>',
|
||||
reloadOnSearch: false,
|
||||
},
|
||||
'/queries': _.extend({
|
||||
title: 'Queries',
|
||||
resolve: {
|
||||
currentPage: () => 'all',
|
||||
resource(Query) {
|
||||
'ngInject';
|
||||
|
||||
return Query.query.bind(Query);
|
||||
},
|
||||
},
|
||||
}, route),
|
||||
'/queries/my': _.extend({
|
||||
title: 'My Queries',
|
||||
resolve: {
|
||||
currentPage: () => 'my',
|
||||
resource: (Query) => {
|
||||
'ngInject';
|
||||
|
||||
return Query.myQueries.bind(Query);
|
||||
},
|
||||
},
|
||||
}, route),
|
||||
'/queries/favorite': _.extend({
|
||||
title: 'Favorite Queries',
|
||||
resolve: {
|
||||
currentPage: () => 'favorites',
|
||||
resource: (Query, $q) => {
|
||||
'ngInject';
|
||||
|
||||
return (request) => {
|
||||
const result = {
|
||||
results: [],
|
||||
};
|
||||
result.$promise = $q((resolve, reject) => {
|
||||
// convert plain array to paginator
|
||||
Query.favorites(request).$promise
|
||||
.then((data) => {
|
||||
result.count = data.length;
|
||||
result.results = data;
|
||||
result.page = 1;
|
||||
result.page_size = data.length;
|
||||
|
||||
resolve(result);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
},
|
||||
},
|
||||
}, route),
|
||||
};
|
||||
}
|
||||
|
@ -9,14 +9,23 @@
|
||||
<input type='text' class='form-control' placeholder="Search Queries..."
|
||||
ng-change="$ctrl.update()" ng-model="$ctrl.searchText" autofocus/>
|
||||
<div class='list-group m-t-10 m-b-10 tags-list tiled'>
|
||||
<a href="" class="list-group-item">
|
||||
<a href="queries" class="list-group-item"
|
||||
ng-class="{active: $ctrl.currentPage == 'all'}">
|
||||
<span style="padding-left: 13px;"></span>
|
||||
All Queries
|
||||
</a>
|
||||
<a href="queries/favorite" class="list-group-item"
|
||||
ng-class="{active: $ctrl.currentPage == 'favorites'}">
|
||||
<span class="btn-favourite">
|
||||
<i class="fa fa-star" aria-hidden="true"></i>
|
||||
</span>
|
||||
Favorites
|
||||
</a>
|
||||
<a href="" class="list-group-item">
|
||||
<img ng-src="{{$ctrl.currentUser.profile_image_url}}" class="profile__image--navbar" width="16" style="margin-right: 0;"/>My Queries
|
||||
<a href="queries/my" class="list-group-item"
|
||||
ng-class="{active: $ctrl.currentPage == 'my'}">
|
||||
<img ng-src="{{$ctrl.currentUser.profile_image_url}}" class="profile__image--navbar"
|
||||
width="13" style="margin-right: 0;"/>
|
||||
My Queries
|
||||
</a>
|
||||
|
||||
<a ng-repeat='tag in $ctrl.allTags' ng-class='{"active": $ctrl.tagIsSelected(tag)}'
|
||||
|
@ -44,7 +44,7 @@ function prepareWidgetsForDashboard(widgets) {
|
||||
return widgets;
|
||||
}
|
||||
|
||||
function Dashboard($resource, $http, currentUser, Widget, dashboardGridOptions) {
|
||||
function Dashboard($resource, $http, currentUser, Widget, dashboardGridOptions, $q) {
|
||||
function prepareDashboardWidgets(widgets) {
|
||||
return prepareWidgetsForDashboard(_.map(widgets, widget => new Widget(widget)));
|
||||
}
|
||||
@ -94,6 +94,24 @@ function Dashboard($resource, $http, currentUser, Widget, dashboardGridOptions)
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: This is polyfill; move it to resource definition when API will be available
|
||||
resource.myDashboards = (request) => {
|
||||
const result = [];
|
||||
result.$promise = $q((resolve, reject) => {
|
||||
resource.query(request).$promise
|
||||
.then((data) => {
|
||||
_.each(data, (item) => {
|
||||
if (item.user_id === currentUser.id) {
|
||||
result.push(item);
|
||||
}
|
||||
});
|
||||
resolve(result);
|
||||
})
|
||||
.catch(reject);
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
resource.prototype.canEdit = function canEdit() {
|
||||
return currentUser.canEdit(this) || this.can_edit;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user