First draft of a working in-browser bugs pagination.

This commit is contained in:
Kevin Gaudin 2013-05-10 00:29:33 +02:00
parent ee15ec2199
commit 84d82a268e
6 changed files with 181 additions and 84 deletions

View File

@ -51,7 +51,8 @@
</ul>
</li>
<li ng-class="{active: $route.current.activetab == 'dashboard'}"><a href="#/dashboard/{{acralyzer.app}}">Dashboard</a></li>
<li ng-class="{active: $route.current.activetab == 'reports-browser'}"><a href="#/reports-browser/{{acralyzer.app}}">Reports browser</a></li>
<li ng-class="{active: $route.current.activetab == 'reports-browser'}"><a href="#/reports-browser/{{acralyzer.app}}">Reports</a></li>
<li ng-class="{active: $route.current.activetab == 'bugs-browser'}"><a href="#/bugs-browser/{{acralyzer.app}}">Bugs</a></li>
<li ng-class="{active: $route.current.activetab == 'admin'}"><a href="#/admin/{{acralyzer.app}}">Admin</a></li>
</ul>
@ -131,6 +132,7 @@
<script src="script/NavigationController.js"></script>
<script src="script/ReportDetailsController.js"></script>
<script src="script/ReportsBrowserControllers.js"></script>
<script src="script/BugsBrowserControllers.js"></script>
<script src="script/AdminControllers.js"></script>
<script src="script/services.js"></script>

View File

@ -3,7 +3,7 @@
<div id="filters" class="row-fluid">
<form ng-submit="getData()" class="form-inline">
<div class="input-append">
<input ng-model="reportsCount" type="number" alt="Number of reports per page" class="input-mini"/>
<input ng-model="bugsCount" type="number" alt="Number of reports per page" class="input-mini"/>
<button class="btn" type="submit">OK</button>
</div>
<select ng-model="filterName" ng-change="changeFilterValues()" ng-options="f.label for f in availableFilters">
@ -16,38 +16,40 @@
</label>
</form>
</div>
<div id="reports-list" class="row-fluid tab-content">
<span ng-bind-template="reports {{startNumber}} to {{endNumber}} (out of {{totalReports}}):">Loading...</span>
<div class="row-fluid tab-content">
<span ng-bind-template="Bugs {{startNumber}} to {{endNumber}} (out of {{totalBugs}}):">Loading...</span>
<button class="btn btn-small" ng-click="hideSolvedBugs = !hideSolvedBugs">
<span ng-hide="hideSolvedBugs">Hide solved bugs</span>
<span ng-show="hideSolvedBugs">Show solved bugs</span>
</button>
<div class="loader" ng-show="loading"><img src="img/loader.gif"/></div>
<div class="div-table" ng-hide="loading">
<div ng-hide="loading" id="bugs-list" class="div-table table table-condensed">
<div class="div-table-row">
<div class="div-table-cell"/>
<div class="div-table-cell"></div>
<div class="div-table-cell"><i class="icon-time"></i></div>
<div class="div-table-cell"><i class="icon-th-list"></i></div>
<div class="div-table-cell"><i class="icon-gift"></i></div>
<div class="div-table-cell"><i class="icon-bugdroid"></i></div>
<div class="div-table-cell"><i class="icon-phone"></i></div>
<div class="div-table-cell exceptions"><i class="icon-fire"></i></div>
</div>
<div class="div-table-row" ng-repeat="report in reports | filter:search" ng-class-odd="'odd'" ng-class-even="'even'" ng-cloak>
<div class="div-table-cell actions">
<a ng-click="loadReport(report)" class="action">
<i class="icon-search" ng-hide="report.id == selectedReport._id" title="Display details"></i>
<i class="icon-asterisk" ng-show="report.id == selectedReport._id" title="Current selection"></i>
</a>
<a ng-click="deleteReport(report)" class="action">
<i class="icon-trash" title="Delete permanently"></i>
</a>
<div class="div-table-row"
ng-repeat="bug in pageItems()"
ng-animate="{ enter: 'example-repeat-enter',
leave: 'example-repeat-leave',
move: 'example-repeat-move'}">
<div class="div-table-cell" ng-click="toggleSolved(bug)">
<span title="Unsolved bug" ng-hide="bug.value.solved"><i class="icon-bug-unsolved"></i></span>
<span title="Solved bug" ng-show="bug.value.solved"><i class="icon-bug-solved"></i></span>
</div>
<span title="Crash date" class="div-table-cell label label-info">{{report.displayDate}}</span>
<span title="Application version name" class="div-table-cell label label-warning">{{report.value.application_version_name}}</span>
<span title="Android verion" class="div-table-cell label label-success">{{report.value.android_version}}</span>
<span title="Device" class="div-table-cell label">{{report.value.device}}</span>
<div title="Latest report" class="div-table-cell label label-info">{{bug.latest}}</div>
<div title="Number of reports" class="div-table-cell label">{{bug.value.count}}</div>
<div title="Application version code" class="div-table-cell label label-warning">{{bug.key[0]}}</div>
<div class="div-table-cell exceptions">
<span title="Exception" class="label label-important">{{report.value.signature.digest}}</span>
<span title="Root exception" class="label label-inverse" ng-show="report.value.signature.rootCause">Caused by: {{report.value.signature.rootCause}}</span>
<span title="Exception" class="label label-important">{{bug.key[1]}}</span><br/>
<span title="Root exception" class="label label-inverse" ng-show="bug.key[2]">Caused by: {{bug.key[2]}}</span>
</div>
</div>
</div>
<span paginator items="bugsList | filter:shouldBeDisplayed | orderBy:'value.latest':true" pageSize="bugsCount"></span>
<ul class="pager">
<li class="previous" ng-show="previousStartKeys.length > 0">
<a ng-click="getPreviousPage()"><i class="icon-chevron-left"></i> Newer</a>
@ -63,6 +65,5 @@
</li>
</ul>
</div>
<report-details report="selectedReport" acralyzer="acralyzer"></report-details>
</div>
</div>

View File

@ -75,8 +75,6 @@
</div>
<div class="div-table-row"
ng-repeat="bug in bugs | filter:shouldBeDisplayed | orderBy:'value.latest':true | limitTo:bugsLimit"
ng-class-odd="'odd'"
ng-class-even="'even'"
ng-animate="{ enter: 'example-repeat-enter',
leave: 'example-repeat-leave',
move: 'example-repeat-move'}">

View File

@ -19,23 +19,25 @@
(function(acralyzerConfig,angular,acralyzer,acralyzerEvents) {
"use strict";
function ReportsBrowserCtrl($scope, ReportsStore, $routeParams) {
function BugsBrowserCtrl($scope, ReportsStore, $routeParams) {
if($routeParams.app) {
console.log("ReportsBrowser: Direct access to app " + $routeParams.app);
console.log("BugsBrowser: Direct access to app " + $routeParams.app);
$scope.acralyzer.setApp($routeParams.app);
} else {
console.log("ReportsBorwser: Access to default app " + acralyzerConfig.defaultApp);
console.log("BugsBorwser: Access to default app " + acralyzerConfig.defaultApp);
$scope.acralyzer.setApp(acralyzerConfig.defaultApp);
}
console.log("Init ReportsBrowserCtrl");
$scope.reportsCount = 15;
console.log("Init BugsBrowserCtrl");
$scope.bugsList = [];
$scope.bugsCount = 15;
$scope.hideSolvedBugs = true;
$scope.previousStartKeys = [];
$scope.selectedReport = "";
$scope.selectedBug = "";
$scope.startKey = null;
$scope.nextKey = null;
$scope.startNumber = 1;
$scope.endNumber = $scope.reportsCount;
$scope.endNumber = $scope.bugsCount;
$scope.fullSearch = false;
$scope.loading = true;
$scope.noFilter = { value: "false", label: "No filter"};
@ -63,42 +65,55 @@
$scope.getData();
};
$scope.getData = function() {
$scope.loading = true;
var successHandler = function(data) {
// Success Handler
console.log("Refresh data for latest reports");
$scope.reports = data.rows;
$scope.totalReports = data.total_rows;
for(var row = 0; row < $scope.reports.length; row++) {
if($scope.filterName === $scope.noFilter && $scope.filterValue === $scope.noFilterValue) {
$scope.reports[row].displayDate = moment($scope.reports[row].key).fromNow();
} else {
$scope.reports[row].displayDate = moment($scope.reports[row].key[1]).fromNow();
var mergeBugsLists = function(list1, list2) {
var bugslist = {};
for(var i1 = 0; i1 < list1.length; i1++) {
bugslist[list1[i1].id] = {idxlist1: i1};
}
for(var i2 = 0; i2 < list2.length; i2++) {
if(!bugslist[list2[i2].id]){
// Mark bug as not found in list1
bugslist[list2[i2].id] = {idxlist1: -1};
}
bugslist[list2[i2].id].idxlist2 = i2;
}
for(var iBugs in bugslist) {
if(bugslist[iBugs].idxlist1 < 0 && bugslist[iBugs].idxlist2 >= 0) {
// New bug
list1.push(list2[bugslist[iBugs].idxlist2]);
} else if (bugslist[iBugs].idxlist1 >= 0 && bugslist[iBugs].idxlist2 < 0) {
// Deleted bug
list1.splice(bugslist[iBugs].idxlist1, 1);
} else if(bugslist[iBugs].idxlist1 >= 0 && bugslist[iBugs].idxlist2 >= 0) {
if(!list1[bugslist[iBugs].idxlist1].equals(list2[bugslist[iBugs].idxlist2])) {
// Updated bug
list1[bugslist[iBugs].idxlist1].updateWithBug(list2[bugslist[iBugs].idxlist2]);
}
}
// If there are more rows, here is the key to the next page
$scope.nextKey =data.next_row ? data.next_row.key : null;
$scope.startNumber = ($scope.previousStartKeys.length * $scope.reportsCount) + 1;
$scope.endNumber = $scope.startNumber + $scope.reports.length - 1;
console.log($scope);
$scope.loading = false;
};
var errorHandler = function(response, getResponseHeaders){
// Error Handler
$scope.reports=[];
$scope.totalReports="";
};
if($scope.filterName === $scope.noFilter || $scope.filterValue === $scope.noFilterValue) {
ReportsStore.reportsList($scope.startKey, $scope.reportsCount, $scope.fullSearch, successHandler, errorHandler);
} else {
ReportsStore.filteredReportsList($scope.filterName.value, $scope.filterValue.value,$scope.startKey, $scope.reportsCount, $scope.fullSearch, successHandler, errorHandler);
}
};
$scope.getData = function() {
$scope.loading = true;
ReportsStore.bugsList(function(data) {
console.log("Refresh data for latest bugs");
console.log(data);
mergeBugsLists($scope.bugsList, data.rows);
$scope.totalBugs = data.total_rows;
for(var row = 0; row < $scope.bugsList.length; row++) {
$scope.bugsList[row].latest = moment($scope.bugsList[row].value.latest).fromNow();
}
$scope.loading = false;
},
function(response, getResponseHeaders){
$scope.bugsList=[];
$scope.totalBugs="";
$scope.loading = false;
});
};
$scope.changeFilterValues = function() {
if($scope.filterName === $scope.noFilter) {
@ -126,6 +141,22 @@
}
};
$scope.toggleSolved = function(bug) {
console.log("let's mark this bug as solved:");
console.log(bug);
ReportsStore.toggleSolved(bug, function(data){
console.log(data);
});
};
$scope.shouldBeDisplayed = function(bug) {
if($scope.hideSolvedBugs && bug.value.solved) {
return false;
} else {
return true;
}
};
$scope.filterValueSelected = function() {
// reset pagination
$scope.startKey = null;
@ -134,31 +165,12 @@
$scope.getData();
};
$scope.loadReport = function(report) {
$scope.selectedReport = ReportsStore.reportDetails(report.id, function(data) {
data.readableUptime = moment.duration(data.uptime, 'seconds').humanize();
data.formatedCrashDate = moment(data.USER_CRASH_DATE).format('LLL');
data.formatedTimestamp = moment(data.timestamp).format('LLL');
});
};
$scope.deleteReport = function(report) {
if($scope.selectedReport === report) {
$scope.selectedReport = "";
}
ReportsStore.deleteReport(report, function(data) {
var index = $scope.reports.indexOf(report);
$scope.reports.splice(index, 1);
});
};
$scope.$on(acralyzerEvents.LOGGED_IN, $scope.getData);
$scope.$on(acralyzerEvents.LOGGED_OUT, $scope.getData);
$scope.getData();
}
acralyzer.controller('ReportsBrowserCtrl', ["$scope", "ReportsStore", "$routeParams", ReportsBrowserCtrl]);
acralyzer.controller('BugsBrowserCtrl', ["$scope", "ReportsStore", "$routeParams", BugsBrowserCtrl]);
})(window.acralyzerConfig,window.angular,window.acralyzer,window.acralyzerEvents);

View File

@ -25,6 +25,7 @@
$routeProvider.
when('/dashboard/:app', {templateUrl: 'partials/dashboard.html', controller: 'DashboardCtrl', activetab: "dashboard"}).
when('/reports-browser/:app', {templateUrl: 'partials/reports-browser.html', controller: 'ReportsBrowserCtrl', activetab: "reports-browser"}).
when('/bugs-browser/:app', {templateUrl: 'partials/bugs-browser.html', controller: 'BugsBrowserCtrl', activetab: "bugs-browser"}).
when('/report-details/:app/:reportId', {templateUrl: 'partials/report-details.html', controller: 'ReportDetailsCtrl', activetab: "none"}).
when('/admin/:app', {templateUrl: 'partials/admin.html', controller: 'AdminCtrl', activetab: "admin"}).
otherwise({redirectTo: '/dashboard/' + acralyzerConfig.defaultApp});
@ -162,4 +163,72 @@
}
};
});
/* http://www.frangular.com/2012/12/pagination-cote-client-directive-angularjs.html */
acralyzer.directive('paginator', function () {
var pageSizeLabel = "Page size";
return {
priority: 0,
restrict: 'A',
scope: {items: '&'},
template:
'<button ng-disabled="isFirstPage()" ng-click="decPage()" class="btn">&lt; newer</button>' +
pageSizeLabel + ' <select ng-model="paginator.pageSize" ng-options="size for size in pageSizeList" class="input-small"></select>' +
'<button ng-disabled="isLastPage()" ng-click="incPage()" class="btn">older &gt;</button>',
replace: false,
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
scope.pageSizeList = [5, 10, 20, 50, 100];
scope.paginator = {
pageSize: 5,
currentPage: 0
};
scope.isFirstPage = function () {
return scope.paginator.currentPage === 0;
};
scope.isLastPage = function () {
return scope.paginator.currentPage >= scope.items().length / scope.paginator.pageSize - 1;
};
scope.incPage = function () {
if (!scope.isLastPage()) {
scope.paginator.currentPage++;
}
};
scope.decPage = function () {
if (!scope.isFirstPage()) {
scope.paginator.currentPage--;
}
};
scope.firstPage = function () {
scope.paginator.currentPage = 0;
};
scope.numberOfPages = function () {
return Math.ceil(scope.items().length / scope.paginator.pageSize);
};
scope.$watch('paginator.pageSize', function(newValue, oldValue) {
if (newValue !== oldValue) {
scope.firstPage();
}
});
// ---- Functions available in parent scope -----
scope.$parent.firstPage = function () {
scope.firstPage();
};
// Function that returns the reduced items list, to use in ng-repeat
scope.$parent.pageItems = function () {
var start = scope.paginator.currentPage * scope.paginator.pageSize;
var limit = scope.paginator.pageSize;
console.log(scope.items());
return scope.items().slice(start, start + limit);
};
},
post: function postLink(scope, iElement, iAttrs, controller) {}
};
}
};
});
})(window.acralyzerConfig, window.angular, window.jQuery, window.acralyzerEvents);

View File

@ -262,4 +262,19 @@ report-summary .label-androidversion {
.icon-phone {
background-image: url(../img/device-black.png);
background-position: 0px;
}
.icon-bug-unsolved {
background-image: url(../img/bug.png);
background-position: 0px;
}
.icon-bug-unsolved:hover, .icon-bug-solved:hover {
background-image: url(../img/bug-rollover.png);
background-position: 0px;
}
.icon-bug-solved {
background-image: url(../img/bug-solved.png);
background-position: 0px;
}