Favorites: page for favorite dashboards and queries

This commit is contained in:
Levko Kravets 2018-05-09 20:07:58 +03:00
parent 3974adeacd
commit fcc75cbd16
5 changed files with 176 additions and 54 deletions

View File

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

View File

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

View File

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

View File

@ -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)}'

View File

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