Commit a1fcd40a authored by Sam Man's avatar Sam Man

repository synced with latest changes made to http://gitlabreports.cosango.com/

parent 19757b98
Pipeline #35685732 failed with stages
in 1 minute and 16 seconds
......@@ -4,13 +4,7 @@
function main($rootScope, $scope, $mdSidenav, gitlab, auth) {
$scope.$watch('project', function () {
$scope.labels = null;
$scope.milestone = null;
$scope.state = null;
localStorage.removeItem('labels');
localStorage.removeItem('milestone');
localStorage.removeItem('state');
$scope.resetFilter();
if ($scope.project) {
$scope.loadProjectLabels();
$scope.loadProjectMilestones();
......@@ -89,9 +83,22 @@
$rootScope.$broadcast('local-storage-updated');
};
$scope.resetFilter = function () {
$scope.labels = null;
$scope.project_labels = null;
$scope.milestone = null;
$scope.project_milestone = null;
$scope.state = 'all';
localStorage.removeItem('labels');
localStorage.removeItem('milestone');
localStorage.removeItem('state');
};
$rootScope.show_detailed_report = false;
$rootScope.show_comulative_time_estimate = true;
$rootScope.show_comulative_time_spent = true;
$rootScope.show_show_logo_print = true;
$rootScope.show_hyperlinks = true;
$rootScope.table_columns = [
'srno',
'iid',
......@@ -103,6 +110,7 @@
'total_time_spent',
'state'
];
$scope.loadProjects();
}
......
......@@ -4,8 +4,7 @@
function main($rootScope, $scope, gitlab, auth) {
$rootScope.$on('local-storage-updated', function () {
$scope.comulative_time_spent = 0;
$scope.comulative_time_estimate = 0;
$scope.filter_milestone = localStorage.getItem('milestone');
$scope.loadIssues();
});
......@@ -33,6 +32,7 @@
$scope.issues = gitlab.projects_issues.query(
params,
function () {
$scope.recalculateTimeStats();
},
function () {
auth.redirectToOauth();
......@@ -40,26 +40,16 @@
);
};
$scope.getIssuesTimeStats = function (issue_project_id, issue_iid) {
var stats = gitlab.issues_time_stats.get(
{
access_token: localStorage.getItem('access_token'),
id: issue_project_id,
issue_iid: issue_iid
},
function () {
$scope.comulative_time_estimate += stats.time_estimate;
$scope.comulative_time_spent += stats.total_time_spent;
console.log($scope.comulative_time_estimate, $scope.comulative_time_spent);
}
);
return stats;
$scope.recalculateTimeStats = function () {
$scope.comulative_time_spent = 0;
$scope.comulative_time_estimate = 0;
angular.forEach($scope.issues, function (issue) {
$scope.comulative_time_estimate += issue.time_stats.time_estimate;
$scope.comulative_time_spent += issue.time_stats.total_time_spent;
});
};
}
angular.module('GitLabReportApp').controller('TableController', main);
......
(function () {
'use strict';
angular.module('GitLabReportApp', ['ngMaterial', 'ngResource']);
angular.module('GitLabReportApp', ['ngSanitize', 'ngMaterial', 'ngResource', 'ng-showdown']);
})();
......
......@@ -5,5 +5,8 @@ $(document).ready(function(){
});
$(".filterbtn").click(function(){
$(".sidenavfilter").toggleClass("togglefiltershow");
});
$(".overlaytour").click(function(){
$(".darkbg.overlaytour").remove();
});
});
......@@ -4,11 +4,11 @@
<title>GitLab Project Reports, Milestone Reports, Time Tracking Reports</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="favico.ico" type="image/vnd.microsoft.icon" />
<link rel="shortcut icon" href="favico.ico" type="image/vnd.microsoft.icon"/>
<link rel="stylesheet" href="//ajax.googleapis.com/ajax/libs/angular_material/1.1.4/angular-material.min.css">
<link rel='stylesheet'
href='//fonts.googleapis.com/icon?family=Material+Icons|Roboto+Condensed:400,700|Source+Sans+Pro:300,400,600,700,900'>
<link rel='stylesheet' href='//fontawesome.io/assets/font-awesome/css/font-awesome.css'>
<link rel='stylesheet' href='//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'>
<link rel='stylesheet' href='style.css'>
<script src="//code.jquery.com/jquery-latest.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
......@@ -16,7 +16,10 @@
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-aria.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-messages.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-resource.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular-sanitize.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angular_material/1.1.4/angular-material.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/showdown/1.8.6/showdown.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ng-showdown/1.1.0/ng-showdown.min.js"></script>
<script src="app.js"></script>
<script src="app.service.auth.js"></script>
<script src="app.factory.gitlab.js"></script>
......@@ -24,9 +27,18 @@
<script src="app.controller.toolbar.js"></script>
<script src="app.controller.table.js"></script>
<script src="app.controller.filter.js"></script>
</head>
</head>
<body>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-87773565-1', 'auto');
ga('send', 'pageview');
</script>
<div ng-controller="SplashController" id="loader">
<div class="loader">
<img src="gr-loaders.gif">
......@@ -36,6 +48,20 @@
</p>
</div>
</div>
<div class="darkbg overlaytour">
<div role="dialog" class="gz tourtext">
<div class="textdiv">
Select a project and <strong>Apply Filters</strong> to generate report.
For more filters, use dropdown to select Milestone, Labels or State.
<button class="md-raised md-primary tourbtn md-button md-ink-ripple" type="button">
<md-icon class="ng-scope material-icons" role="img" aria-hidden="true">checked</md-icon>
GOT IT
<div class="md-ripple-container"></div>
</button>
</div>
</div>
</div>
<div class="tourblank"></div>
<md-content ng-cloak class="gitlab-reports-container">
<md-toolbar ng-controller="ToolbarController" layout="row"
class="md-toolbar-tools md-scroll-shrink md-whiteframe-4dp gr-header hide-print">
......@@ -45,7 +71,7 @@
<span><i>by</i> <a href="http://cosango.com" title="cosango.com">COSANGO.com</a></span>
</h1>
<div flex></div>
<md-button class="md-icon-button printpdf" aria-label="Download" onclick="javascript:window.print()">
<md-button class="md-icon-button printpdf" aria-label="Download" onclick="javascript:window.print()">
<md-tooltip md-direction="bottom">Print</md-tooltip>
<i class="material-icons">print</i>
</md-button>
......@@ -53,7 +79,7 @@
<md-tooltip md-direction="bottom">PDF Download</md-tooltip>
<i class="material-icons">picture_as_pdf</i>
</md-button>
<md-button class="md-icon-button downloadsource" aria-label="Source" ng-click="downloadsource()">
<md-button class="md-icon-button downloadsource" aria-label="Source" ng-click="downloadsource()">
<md-tooltip md-direction="bottom">Download Source</md-tooltip>
<i class="material-icons">file_download</i>
</md-button>
......@@ -72,6 +98,13 @@
<md-content flex layout="row" id="reportresult">
<div class="tablecontent" ng-controller="TableController">
<h1 ng-show="issues && $root.show_detailed_report" class="main-heading">Project Report</h1>
<h2 ng-show="issues && $root.show_detailed_report && filter_milestone" class="milestone">
Milestone: {{ filter_milestone }}
</h2>
<div ng-show="issues && $root.show_detailed_report" class="current_milestone orangetext">
<i class="material-icons">flag</i> <span>{{milestone}}</span>
</div>
<div ng-show="issues" class="totaltimevalue">
<md-content flex id="content" layout="column">
<ul>
......@@ -91,10 +124,43 @@
</md-content>
</div>
<div ng-hide="issues" class="hide-print">
Select a project from right.
<h2>Overview</h2>
<p class="para-text">
GitLab Reports is a small utility web app built to help developers (using GitLab
for their projects) to generate time tracking reports for their GitLab projects. After being
authenticated by GitLab, developers are taken back to GitLab Reports app where can select desired
project from right sidebar to create quick project reports showing all issues logged in select
project.
</p>
<p class="para-text">
If a project has milestones, app will also load them in a new list from where you
can select desired one to create a milestone report as well.
</p>
<p class="para-text">GitLab Reports supports calculating Total Estimate Time and Total Time Spent of all
issues including in the project report. This helps developers analyzing that how much total time
they have spent on specific project milestones which helps tracking progress.
</p>
<p class="para-text">
In next releases, we will add support of exporting report to different formats
specially PDF along with other enhancements and bug fixes.
</p>
<a class="md-raised orangebtn md-button md-ink-ripple" href="https://gitlab.com/cosango/gitlabreports"
target="_blank" aria-label="Download from GitLab">
<md-icon class="fa fa-gitlab fa-2x"></md-icon>
Gitlab
</a>
<a class="md-raised orangebtn md-button md-ink-ripple" href="https://github.com/cosango/gitlabreports"
target="_blank" aria-label="Download from GitHub">
<md-icon class="fa fa-github fa-2x"></md-icon>
GitHub
</a>
</div>
<table ng-show="issues" class="res-table" id="table">
<h4 ng-show="issues && $root.show_detailed_report" class="printheading">
<i class="material-icons">list</i>
Issues List
</h4>
<table ng-show="issues" class="res-table issue-list" id="table">
<thead>
<tr>
<th ng-show="$root.table_columns.indexOf('srno') >= 0">No</th>
......@@ -132,7 +198,9 @@
{{ $index + 1 }}
</td>
<td data-th="ID" ng-show="$root.table_columns.indexOf('iid') >= 0" data-title="useriid">
<a href="{{ issue.web_url }}" target="_blank">{{ issue.iid }}</a>
<a ng-show="$root.show_hyperlinks" href="{{ issue.web_url }}" target="_blank">{{ issue.iid
}}</a>
<span ng-hide="$root.show_hyperlinks">{{ issue.iid }}</span>
</td>
<td data-th="Title" ng-show="$root.table_columns.indexOf('title') >= 0" data-title="tasktitle">
{{ issue.title }}
......@@ -147,29 +215,57 @@
<td data-th="Assignee" ng-show="$root.table_columns.indexOf('assignee') >= 0">
{{ issue.assignee.name }}
</td>
<td data-th="Time Estimate" ng-show="$root.table_columns.indexOf('time_estimate') >= 0"
ng-init="stats = getIssuesTimeStats(issue.project_id, issue.iid)" data-title="taskestimate">
<span ng-hide="stats.time_estimate">Unspecified</span>
<span ng-show="stats.time_estimate">{{ stats.human_time_estimate }}</span>
<td data-th="Time Estimate" ng-show="$root.table_columns.indexOf('time_estimate') >= 0" data-title="taskestimate">
<span ng-hide="issue.time_stats.human_time_estimate">Unspecified</span>
<span ng-show="issue.time_stats.human_time_estimate">{{ issue.time_stats.human_time_estimate }}</span>
</td>
<td data-th="Time Spent" ng-show="$root.table_columns.indexOf('total_time_spent') >= 0"
date-title="timespent">
<span ng-hide="stats.total_time_spent">Unspecified</span>
<span ng-show="stats.total_time_spent">{{ stats.human_total_time_spent }}</span>
<span ng-hide="issue.time_stats.total_time_spent">Unspecified</span>
<span ng-show="issue.time_stats.total_time_spent">{{ issue.time_stats.human_total_time_spent }}</span>
</td>
<td data-th="State" ng-show="$root.table_columns.indexOf('state') >= 0">
<td data-th="State" class="state" ng-show="$root.table_columns.indexOf('state') >= 0">
{{ issue.state }}
</td>
</tr>
</tbody>
</table>
<h4 ng-show="issues && $root.show_detailed_report" class="printheading mt-5">
<i class="material-icons">library_books</i>
Issues Details
</h4>
<div class="issue-details-wrapper " ng-show="issues && $root.show_detailed_report">
<div ng-repeat="issue in issues" class="issuedetail_box">
<div class="issuedetail_title">{{ issue.title }}</div>
<div class="issuedetail_description" markdown-to-html="issue.description"></div>
<div class="issuedetail_url" ng-show="$root.show_hyperlinks">
URL:
<span><a href="{{ issue.web_url }}">{{ issue.web_url }}</a></span>
</div>
<div classs="issuedetail_bottom">
<div class="issuedetail_id">
ID:
<span>{{ issue.iid }}</span>
</div>
<div class="issuedetail_rdate">
Reporting Date:
<span>{{ issue.created_at | date:'yyyy-MM-dd' }}</span>
</div>
<div class="issuedetail_cdate">
Closing Date:
<span>{{ issue.closed_at | date:'yyyy-MM-dd' }}</span>
</div>
<div class="issuedetail_status">
State:
<span>{{ issue.state }}</span>
</div>
</div>
</div>
</div>
</div>
<md-sidenav class="md-whiteframe-1dp sidenavfilter hide-print" -md-is-locked-open="$mdMedia('gt-sm')" md-is-locked-open="true"
md-component-id="right">
<md-sidenav class="md-whiteframe-1dp sidenavfilter hide-print"
md-is-locked-open="true" md-component-id="right">
<div class="sidespacer">
<h4 class="orangetext">Filters</h4>
......@@ -188,28 +284,34 @@
<md-option ng-value="p" ng-repeat="p in projects">{{ p.name }}</md-option>
</md-select>
</div>
<div flex ng-show="project_milestones.length">
<md-select ng-model="milestone" placeholder="Milestone">
<div flex>
<md-select ng-model="milestone" placeholder="Milestone" ng-disabled="!project_milestones">
<md-option ng-value="m" ng-repeat="m in project_milestones">{{ m.title }}
</md-option>
</md-select>
</div>
<div flex ng-show="project_labels.length">
<md-select ng-model="labels" placeholder="Labels" multiple>
<div flex>
<md-select ng-model="labels" placeholder="Labels" multiple ng-disabled="!project_labels">
<md-option ng-value="l" ng-repeat="l in project_labels">{{ l.name }}
</md-option>
</md-select>
</div>
<div flex ng-show="project">
<div flex>
<md-select ng-model="state" placeholder="State">
<md-option value="all">All</md-option>
<md-option value="opened">Opened</md-option>
<md-option value="closed">Closed</md-option>
</md-select>
</div>
<md-button class="md-raised md-primary filterbtn" ng-click="applyFilter()" aria-label="Apply Filters" ng-disabled="!project">
<md-button class="md-raised md-primary filterbtn" ng-click="applyFilter()"
aria-label="Apply Filters" ng-disabled="!project">
<md-icon>filter_list</md-icon>
Apply Filters
</md-button>
<md-button class="md-raised md-primary filterbtn" ng-click="resetFilter()" aria-label="Reset">
<md-icon>clear_all</md-icon>
Reset
</md-button>
<div flex>
<div class="clearfix">&nbsp;</div>
<h3>Columns</h3>
......@@ -222,9 +324,13 @@
<md-option ng-value="'assignee'">Assignee</md-option>
<md-option ng-value="'time_estimate'">Time Estimate</md-option>
<md-option ng-value="'total_time_spent'">Time Spent</md-option>
<md-option ng-value="'state'">State</md-option>
</md-select>
</div>
<md-switch ng-model="$root.show_detailed_report" aria-label="Show detailed report">
Detailed report
</md-switch>
<md-switch ng-model="$root.show_comulative_time_estimate"
aria-label="Show total time estimate">
Total Estimate Time
......@@ -232,11 +338,14 @@
<md-switch ng-model="$root.show_comulative_time_spent"
aria-label="Show total time spent">
Total Time Spent
</md-switch>
</md-switch>
<md-switch ng-model="$root.show_logo_print"
aria-label="Show logo on Print">
Show Logo on Print
</md-switch>
aria-label="Show logo on Print">
Show Logo on Print
</md-switch>
<md-switch ng-model="$root.show_hyperlinks" aria-label="Show hyperlinks">
Show hyperlinks
</md-switch>
</div>
</div>
</div>
......@@ -251,28 +360,45 @@
<footer class="footer hide-print" id="colophon" role="contentinfo">
<div class="footercontent">
<div class="tl fl footerlogo">
<a href="http://cosango.com/"><img class="footer-logo" src="img/logo_dark.png"
alt="Cosango"></a>
<a href="http://cosango.com/">
<img class="footer-logo" src="img/logo_dark.png" alt="Cosango">
</a>
</div>
<div class="social-icons fr">
<ul>
<li><a class="animate" href="https://www.facebook.com/cosango"><i class="fa fa-facebook"></i></a></li>
<li><a class="animate" href="https://twitter.com/cosangodotcom"><i class="fa fa-twitter"></i></a></li>
<li><a class="animate" href="https://www.pinterest.com/cosangodotcom"><i
class="fa fa-pinterest"></i></a></li>
<li><a class="animate" href="https://www.linkedin.com/company/cosango"><i
class="fa fa-linkedin"></i></a></li>
<li><a class="animate" href="https://plus.google.com/104221792987400160410"><i
class="fa fa-google-plus"></i></a></li>
<li>
<a class="animate" href="https://www.facebook.com/cosango">
<i class="fa fa-facebook"></i>
</a>
</li>
<li>
<a class="animate" href="https://twitter.com/cosangodotcom">
<i class="fa fa-twitter"></i>
</a>
</li>
<li>
<a class="animate" href="https://www.pinterest.com/cosangodotcom">
<i class="fa fa-pinterest"></i>
</a>
</li>
<li>
<a class="animate" href="https://www.linkedin.com/company/cosango">
<i class="fa fa-linkedin"></i>
</a>
</li>
<li>
<a class="animate" href="https://plus.google.com/104221792987400160410">
<i class="fa fa-google-plus"></i>
</a>
</li>
</ul>
</div>
<p class="uppercase semibold copyright">
Copyright &copy; 2017
Copyright &copy; 2018
<a href="http://cosango.com/">COSANGO.com</a>
</p>
</div>
</footer>
<script src="custom.js"></script>
<!-- Download PDF file Courtesy "MrRio jsPDF" -->
</body>
</html>
body, html {
height: 100%
height: 100%
}
body {
font-family: Lato, sans-serif
font-family: Lato, sans-serif
}
@media screen {
.hide-screen {
display: none;
}
@media screen {
.hide-screen {
display: none;
}
@media print {
@page {
size: landscape;
}
.hide-print {
display: none;
}
.print_spacer{ margin-top:35px}
@media print {
@page {
size: 297mm 210mm; /* landscape */
/* you can also specify margins here: */
margin: 10mm;
margin-right: 20mm; /* for compatibility with both A4 and Letter */
}
.hide-print {
display: none;
}
.print_spacer {
margin-top: 35px
}
}
#loader {
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: #fff;
z-index: 9999;
opacity: 1;
position: fixed;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: #fff;
z-index: 9999;
opacity: 1;
}
.loader {
position: absolute;
width: 250px;
margin: 0 auto;
top: 50%;
left: 0px;
margin-top: -60px;
right: 0px;
text-align: center
position: absolute;
width: 250px;
margin: 0 auto;
top: 50%;
left: 0px;
margin-top: -60px;
right: 0px;
text-align: center
}
.loader p {
margin: 10px 0;
margin: 10px 0;
}
.loader p b {
font-weight: 700;
font-weight: 700;
}
.loader p span {
font-size: 12px;
font-size: 12px;
}
.gr-logo span a, footer p a {
text-decoration: none !important
text-decoration: none !important
}
.gr-header {
background: #f58220 !important;
box-shadow: none
background: #f58220 !important;
box-shadow: none
}
.gr-logo {
font-size: 26px !important;
font-weight: 400 !important;
font-style: normal;
text-align: right;
color: #171818;
margin-bottom: 10px !important;
font-family: 'Source Sans Pro', sans-serif
font-size: 26px !important;
font-weight: 400 !important;
font-style: normal;
text-align: right;
color: #171818;
margin-bottom: 10px !important;
font-family: 'Source Sans Pro', sans-serif
}
div.logoicon {
color: #FFF;
margin: 0;
display: inline-block;
font-size: 18px
color: #FFF;
margin: 0;
display: inline-block;
font-size: 18px
}
.gr-logo em {
font-weight: 700 !important;
font-style: normal;
color: #FEFEFE
font-weight: 700 !important;
font-style: normal;
color: #FEFEFE
}
.gr-logo span {
clear: both;
display: block;
color: #171818;
line-height: 0
clear: both;
display: block;
color: #171818;
line-height: 0
}
.gr-logo i {
font-size: 12px !important;
font-weight: 400 !important;
font-style: normal !important
font-size: 12px !important;
font-weight: 400 !important;
font-style: normal !important
}
.gr-logo span a {
font-size: 12px !important;
font-weight: 700 !important;
font-style: normal
font-size: 12px !important;
font-weight: 700 !important;
font-style: normal
}
a:hover {
opacity: .9
opacity: .9
}
.tokeninput .flex-gt-sm {
display: flex !important;
margin: 10px 0 0 !important
display: flex !important;
margin: 10px 0 0 !important
}
.tokeninput input {
color: #FEFEFE;
border-color: #FFF !important
color: #FEFEFE;
border-color: #FFF !important
}
.tokeninput label {
color: #efefef !important
color: #efefef !important
}
md-select {
margin: 5px 0 !important;
color: #666;
width: 100%;
margin: 5px 0 !important;
color: #666;
width: 100%;
}
.sidespacer {
display: block;
padding: 0px 10px !important
display: block;
padding: 0px 10px !important
}
.sidespacer h3 {
margin: 0 !important;
font-weight: 700;
font-size: 14px;
color: #666666;
margin: 0 !important;
font-weight: 700;
font-size: 14px;
color: #666666;
}
.sidespacer h3:hover {
color: #171818;
color: #171818;
}
.sidespacer h3 span .material-icons {
color: #f58220 !important
color: #f58220 !important
}
footer p a, footer ul li a {
color: #FFF;
transition: .3s all ease-in-out
color: #FFF;
transition: .3s all ease-in-out
}
.sidespacer h5 {
text-transform: uppercase;
margin-bottom: 5px
text-transform: uppercase;
margin-bottom: 5px
}
md-input-container input {
color: #666;
border-color: #ddd !important
color: #666;
border-color: #ddd !important
}
.tablecontent table th,.tablecontent table td{
border:1px solid #DDDDDD
.tablecontent table th, .tablecontent table td {
border: 1px solid #DDDDDD
}
.tablecontent table, .tablecontent table td, .tablecontent table th {
border-collapse: collapse
border-collapse: collapse
}
md-input-container label {
color: #666 !important
color: #666 !important
}
.sidespacer .md-errors-spacer {
display: none !important
display: none !important
}
.sidespacer md-input-container {
margin: 0 !important
margin: 0 !important
}
.sidespacer .md-button {
margin: 6px 0px;