diff --git a/dashboard-project-api/common/models/userStory.js b/dashboard-project-api/common/models/userStory.js index 1bf804f..064f5d9 100644 --- a/dashboard-project-api/common/models/userStory.js +++ b/dashboard-project-api/common/models/userStory.js @@ -8,37 +8,44 @@ module.exports = function(UserStory) { var async = require("async"); var htmlparser = require("htmlparser"); var cheerio = require('cheerio'); + const SPEC_URL = "http://specs.openstack.org/openstack/openstack-user-stories/user-stories/proposed/"; var blueprintsResume = []; - var getAllfiles = function(getPercentage){ - + var getAllfiles = function(){ return fs.readdirSync(route) .map(function(file){ var route = "../tracker/" + file ; - var data = JSON.parse(fs.readFileSync(route)); - var parsedData; - - if(getPercentage){ - parsedData = getTotalPercentage(data); - data.blueprintList = parsedData; - } - - return data; + var userStory = JSON.parse(fs.readFileSync(route)); + return userStory; }); - } - //get completed percentage of all the userstories - var getTotalPercentage = function(userStory){ + var getFileById = function(id){ - blueprintsResume.completed = 0; - blueprintsResume.total = 0; - var blueprintList = []; + //get all files + var userStories = getAllfiles(); + //filter by Id + var file = userStories.filter(function(item){ + return item.id == id; + }) - userStory.tasks.forEach(function (taskName, index, array) { + file = (file.length > 0)?file[0]:null; + + return file; + + }; + + //get all the completed blueprints + var getbluePrintResume = function(userStory){ + var blueprintsResume = { + completed: 0, + total: 0 + } + + userStory.tasks.forEach(function (taskName, index, array) { var task = userStory.tasks_status[taskName]; @@ -46,7 +53,6 @@ module.exports = function(UserStory) { var blueprints = task.projects_status[projectName].blueprints; var blueprintNames = Object.keys(blueprints); - blueprintList.push(blueprintNames); blueprintNames.forEach(function (blueprintName, index, array) { @@ -61,201 +67,64 @@ module.exports = function(UserStory) { }) - var finalPercentage = Math.round((blueprintsResume.completed*100)/blueprintsResume.total) - blueprintList.push([{percentage:(finalPercentage)}]); - return blueprintList; + blueprintsResume.percentage = (blueprintsResume.completed/blueprintsResume.total)*100; + return blueprintsResume; } - //get completed percentage of a User story - var getPercentage = function(blueprints){ - var total = blueprints.length; - var complete = 0; + //get the field lastupdated for a usterStory + var getLastUpdated = function(userStory, cb){ + var Patch = app.models.Patch; + var lastUpdate = ''; - blueprints.forEach(function(element, index, array) { + Patch.latestUpdate(userStory.source, function (err, response, next) { + response = JSON.parse(response.substring(5)); - if(element == 'completed') - complete = complete + 1; - }) - return Math.round((complete*100)/total); + if(response.length > 0){ + lastUpdate = response.map( function each (element){ + return element.updated + }).sort().pop(); + var arrayLastUpdate = lastUpdate.split(' '); + lastUpdate = arrayLastUpdate[0]; - } - - var getFileById = function(id){ - - //get all files - var userStories = getAllfiles(); - //filter by Id - var file = userStories.filter(function(item){ - return item.id == id; - }) - - if(file.length > 0){ - file = file[0]; - }else{ - file = 'The file with id: '+ id +' does not exist.' - } - return file; - - }; - - var getDetailedUri = function(source) { - var base = 'http://specs.openstack.org/openstack/openstack-user-stories/user-stories/proposed/'; - - return base + source + '.html'; - - } - - var parseUserStory = function(id){ - var data = []; - return function(callback){ - var file = getFileById(id); - - var Patch = app.models.Patch; - - Patch.latestUpdate(file.source, function(err, response) { - - var lastUpdate = ''; - - if(response){ - response = JSON.parse(response.substring(5)); - response.forEach( function each (element){ - data.push( - { - latestDate: element.updated, - }); - }); - - data = data.sort(); - lastUpdate = data.shift(); - - if(lastUpdate){ - lastUpdate = lastUpdate.latestDate; - } - } - - var userStory = { - title:file.description, - description:'', - status:file.status, - showDetailedUri:getDetailedUri(file.source), - submittedBy:file.submitted_by.name, - submittedByEmail:file.submitted_by.email, - createdOn:file.date, - updatedOn:lastUpdate, - id:file.id, - percentageComplete:'' - }; - callback(null, userStory, file.tasks, file.tasks_status); - - }) - - } - } - - - var getTaskDescription = function(task, callback){ - - var Rst = app.models.Rst; - var spec = task['cross-project spec'] + '.rst' - - Rst.list(spec, function(err, data){ - - var html_content = markdown.toHTML(data); - var $ = cheerio.load(html_content); - - var index = null; - //Find the title - var description = $('h1').each(function(i, elem) { - if(elem.children[0].data == 'Problem description'){ - index = i; - } - }); - - //get Text description - if(index != null){ - description = $($('h1')[index]).next().text() - }else{ - description = ''; } - callback(null, description); + cb(null, lastUpdate) }) } - var getUriTask = function(spec){ - var base = 'https://github.com/openstack/openstack-specs/blob/master/specs/'; - return base + spec + '.rst'; - } + // Parse data from userStory + var parseUserStory = function(userStory, callback){ - var parseTask = function(originalTask, callback){ + async.waterfall([function(cb){ - getTaskDescription(originalTask, function(err, description){ - originalTask.description = description; - originalTask.url = getUriTask(originalTask['cross-project spec']); - originalTask.xp_status = originalTask.xp_status; - callback(null, originalTask) - }) + getLastUpdated(userStory, cb) - } + },function(lastUpdated, cb){ + userStory.updatedOn = lastUpdated; + userStory.showDetailedUri = SPEC_URL + userStory.source + '.html'; + userStory.createdOn = userStory.date; + userStory.completed = getbluePrintResume(userStory); + cb(null, userStory); - var parseProject = function(originalProject, callback){ + },function(userStory, cb){ - var urlArray = originalProject.spec.split('/'); - var nameArray = urlArray[urlArray.length-1].split('.') - originalProject.spec_name = nameArray[0]; + var tasksName = userStory.tasks; + var tasks = userStory.tasks_status; - callback(null, originalProject) - } - - - var parseBlueprint = function(originalBlueprint, blueprintName, projectName, callback){ - var Blueprint = app.models.Blueprint; - var Patch = app.models.Patch; - - var status = originalBlueprint; - - Blueprint.url(projectName, blueprintName, function(err, uri){ - - blueprintsResume.push(status) - - blueprintsResume.complete = blueprintsResume.total +1 ; - - - Patch.list(blueprintName, function(err, response) { - - var data = []; - - response = JSON.parse(response.substring(5)); - - response.forEach(function each(element) { - data.push( - { - url: "https://review.openstack.org/#/c/" + element._number, - name: element.subject - }); - }); - - originalBlueprint = { - name: blueprintName.replace(/-/g, " "), - uri: uri, - status: status, - review_link:data - } - callback(null, originalBlueprint) + parseTasks(userStory, tasksName, tasks, cb) + }],function(err,userStory){ + callback(null, userStory); }) - - }) - } - //TODO: APPLY async.waterfall + //?? var parseTasks = function(userStory, tasksNames, tasks, callback) { //get tasks var tmpTasks = {}; @@ -308,7 +177,7 @@ module.exports = function(UserStory) { userStory.tasks = tasksNames; userStory.tasks_status = tmpTasks; - userStory.percentageComplete = getPercentage(blueprintsResume) + //userStory.percentageComplete = getPercentage(blueprintsResume) callback(null, userStory); @@ -316,104 +185,151 @@ module.exports = function(UserStory) { } + +//? + + var getTaskDescription = function(task, callback){ + + var Rst = app.models.Rst; + var spec = task['cross-project spec'] + '.rst' + + Rst.list(spec, function(err, data){ + + var html_content = markdown.toHTML(data); + var $ = cheerio.load(html_content); + + var index = null; + //Find the title + var description = $('h1').each(function(i, elem) { + if(elem.children[0].data == 'Problem description'){ + index = i; + } + }); + + //get Text description + if(index != null){ + description = $($('h1')[index]).next().text() + }else{ + description = ''; + } + + callback(null, description); + + }) + + } +//? + var getUriTask = function(spec){ + var base = 'https://github.com/openstack/openstack-specs/blob/master/specs/'; + + return base + spec + '.rst'; + } +//? + var parseTask = function(originalTask, callback){ + + getTaskDescription(originalTask, function(err, description){ + originalTask.description = description; + originalTask.url = getUriTask(originalTask['cross-project spec']); + callback(null, originalTask) + }) + + } +//? + + var parseProject = function(originalProject, callback){ + + var urlArray = originalProject.spec.split('/'); + var nameArray = urlArray[urlArray.length-1].split('.') + originalProject.spec_name = nameArray[0]; + + callback(null, originalProject) + } + +//? + var parseBlueprint = function(originalBlueprint, blueprintName, projectName, callback){ + var Blueprint = app.models.Blueprint; + var Patch = app.models.Patch; + + var status = originalBlueprint; + + Blueprint.url(projectName, blueprintName, function(err, uri){ + + blueprintsResume.push(status) + + blueprintsResume.complete = blueprintsResume.total +1 ; + + + Patch.list(blueprintName, function(err, response) { + + var data = []; + + response = JSON.parse(response.substring(5)); + + response.forEach(function each(element) { + data.push( + { + url: "https://review.openstack.org/#/c/" + element._number, + name: element.subject + }); + }); + + originalBlueprint = { + name: blueprintName.replace(/-/g, " "), + uri: uri, + status: status, + review_link:data + } + callback(null, originalBlueprint) + + }) + + }) + + } + + + UserStory.on('attached',function(){ UserStory.findById = function(id, params, cb){ - async.waterfall([ - parseUserStory(id), - parseTasks - ], function (err, result) { - cb(null,result) - }); + var userStory = getFileById(id); - }; + if(userStory){ + parseUserStory(userStory, cb); + }else{ + cb('File does not exist', null); + } + + };//end find by id UserStory.find = function(params, cb){ - var Patch = app.models.Patch; - //get all files - var userStories = getAllfiles(true); - var percentage; - var blueprintKeys; - var dateList = []; - //var response = userStories; + var userStories = getAllfiles(); - var result; + async.mapSeries(userStories, parse, cb); - // looking dates - var lookingDates = function (id, blueprintKeys) { - blueprintKeys.forEach(function each (key) { - var data = []; - Patch.latestUpdate(key, function (err, response, next) { + function parse(userStory, callback) { - response = JSON.parse(response.substring(5)); - async.waterfall([ - function createOutput(next){ - response.forEach( function each (element){ - data.push( - { - latestDate: element.updated, - }); - }); - next(); - }, - function sendData (next) { - data = data.sort(); - var lastUpdate = data.shift(); - if(lastUpdate !== undefined) { - dateList.push({ - id: id, - value: lastUpdate.latestDate - }); - } - next; - } - ], next); - }); - }); + async.waterfall([function(cb){ + getLastUpdated(userStory, cb) + },function(lastUpdated, cb){ + + var itemResult = { + completed: getbluePrintResume(userStory), + dateCreated: userStory.date, + lastUpdate: lastUpdated, + userStory: userStory.description, + id:userStory.id + }; + + cb(null, itemResult); + + }],function(err,result){ + callback( err, result); + }) } + };//End find - function keysrt(key,asc) { - return function(a,b){ - return asc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]); - } - } - - async.waterfall ([ - function readStories (next) { - userStories.forEach (function eachStory (story, x) { - //console.log(story.blueprintList); - //TODO change the array of percentage - percentage = (story.blueprintList).pop(); - blueprintKeys = story.blueprintList; - result = lookingDates((story.id), blueprintKeys); - story.percentage = percentage[0].percentage; - }); - next(); - }, - function resumeDate (next){ - setTimeout(function(){ - dateList = dateList.sort(keysrt('value')); - userStories.forEach (function eachStory (story) { - dateList.forEach (function eachDate (dateElement){ - if(dateElement.id === story.id){ - story.latestUpdate = dateList - } - }); - }); - cb(null,userStories); - }, 1000); - - next; - }, - function sendResult(next){ - //cb(null,userStories); - next; - } - ]); - - - } }) }; diff --git a/dashboard-project-app/Gruntfile.js b/dashboard-project-app/Gruntfile.js index 0d476bc..749bb6b 100755 --- a/dashboard-project-app/Gruntfile.js +++ b/dashboard-project-app/Gruntfile.js @@ -314,7 +314,7 @@ module.exports = function (grunt) { development: { constants: function() { var config = require('./' + grunt.config.get('ngconstant.options.configPath')) - config.apiBaseUrl = 'http://localhost:3004'; + config.apiBaseUrl = 'http://localhost:3004/api'; return { appConfig : config}; } }, diff --git a/dashboard-project-app/client/app/projectDetail/controllers/projectDetail.controller.js b/dashboard-project-app/client/app/projectDetail/controllers/projectDetail.controller.js index 48ab848..a4f0dc9 100755 --- a/dashboard-project-app/client/app/projectDetail/controllers/projectDetail.controller.js +++ b/dashboard-project-app/client/app/projectDetail/controllers/projectDetail.controller.js @@ -1,9 +1,9 @@ 'use strict'; -(function(){ +(function () { angular.module('dashboardProjectApp') - .controller('projectDetailController', ['$scope','$state', 'UserStory'/*, 'tasksService', 'tasks', 'task'*/, - function($scope, $state, UserStory/*, tasksService, tasks, task*/) { + .controller('projectDetailController', ['$scope','$state', 'UserStory', '$location', + function($scope, $state, UserStory, $location) { $scope.taskId = $state.params.id; $scope.openTasks = {}; @@ -39,23 +39,36 @@ $scope.actualProject = {}; function getFile() { - UserStory.findById({id:$scope.taskId}, - function success(userStory) { - $scope.userStory = userStory; + UserStory.findById({id:$scope.taskId}, + function success(userStory) { + $scope.userStory = userStory; - $scope.userStory.updatedOn = moment($scope.userStory.updatedOn).format("MM-DD-YYYY"); - for(var key in $scope.userStory.tasks_status) { - $scope.actualProject[key] = $scope.userStory.tasks_status[key].projects[0] + // Formating User Story name + if ((userStory.description).length > 100) { + $scope.userStory.shortDescription = userStory. + description.substr(0,50) + " ..."; + } + else { + $scope.userStory.shortDescription = userStory. + description; + } + + $scope.userStory.updatedOn = moment($scope.userStory. + updatedOn).format("MM-DD-YYYY"); + for(var key in $scope.userStory.tasks_status) { + $scope.actualProject[key] = $scope.userStory. + tasks_status[key].projects[0] + } + }, function onError(error){ + $location.path('/projectDetail/notFound/' + $scope.taskId); } - - }); + ); }; $scope.selectProject = function(keyProject, idTask){ $scope.actualProject[idTask] = keyProject } - getFile(); $scope.showMore = function(key){ $scope.showText[key] = true; @@ -69,13 +82,16 @@ window.location.href = "mailto:" + email + "?subject=Mail to " + email; } + getFile(); }]) .filter('capitalize', function() { return function(input) { - return (!!input) ? input.charAt(0).toUpperCase() + input.substr(1).toLowerCase() : ''; + return (!!input) ? input.charAt(0).toUpperCase() + input.substr(1). + toLowerCase() : ''; } - }).filter('removeDashes', function() { + }) + .filter('removeDashes', function() { return function(string) { if (!angular.isString(string)) { diff --git a/dashboard-project-app/client/app/projectDetail/projectDetail.js b/dashboard-project-app/client/app/projectDetail/projectDetail.js index eb6dfac..2984880 100755 --- a/dashboard-project-app/client/app/projectDetail/projectDetail.js +++ b/dashboard-project-app/client/app/projectDetail/projectDetail.js @@ -1,19 +1,25 @@ 'use strict'; angular.module('dashboardProjectApp') - .config(function ($stateProvider) { + .config(function ($stateProvider, $urlRouterProvider) { + $stateProvider - .state('projectDetail', { + + .state('error', { + url: '/projectDetail/notFound/:id', + templateUrl: 'app/projectDetail/views/notFound.html', + controller: function($scope, $state){ + $scope.id = $state.params.id; + } + }) + + + .state('projectDetail', { url: '/projectDetail/:id', - /* resolve: { - userStory: ['userStoryService', - function(userStoryService) { - return userStoryService.getTasks(); - }] - },*/ templateUrl: 'app/projectDetail/views/projectDetail.html', controller: 'projectDetailController' - }) + }) + }); diff --git a/dashboard-project-app/client/app/projectDetail/styles/projectDetail.css b/dashboard-project-app/client/app/projectDetail/styles/projectDetail.css index 7099c66..f0568b5 100755 --- a/dashboard-project-app/client/app/projectDetail/styles/projectDetail.css +++ b/dashboard-project-app/client/app/projectDetail/styles/projectDetail.css @@ -42,3 +42,29 @@ table tr.separator { height: 30px; } +.big-title{ + font-size: 80px; + text-align: center; + padding-top: 40px; + font-weight: bolder; + color: #767676; +} + +.big-subtitle{ + text-align: center; + font-size: 22px; + font-weight: normal; + color: #767676; +} + +.focus-title{ + font-weight: bolder; + color: #414141; +} + +.middle-subtitle{ + text-align: center; + font-size: 16px; + padding-top: 10px; + color: #767676; +} \ No newline at end of file diff --git a/dashboard-project-app/client/app/projectDetail/views/notFound.html b/dashboard-project-app/client/app/projectDetail/views/notFound.html new file mode 100644 index 0000000..9a4aba6 --- /dev/null +++ b/dashboard-project-app/client/app/projectDetail/views/notFound.html @@ -0,0 +1,18 @@ +
+ +
+ 404 +
+ +
+ The Tracker with Id {{id}} does not exist. Are you sure it is spelled correctly? +
+ +
+ You can try return to the list of trackers and look for it +
+ + +
+ + diff --git a/dashboard-project-app/client/app/projectDetail/views/quickResume.html b/dashboard-project-app/client/app/projectDetail/views/quickResume.html index 8d288d3..6d50670 100755 --- a/dashboard-project-app/client/app/projectDetail/views/quickResume.html +++ b/dashboard-project-app/client/app/projectDetail/views/quickResume.html @@ -3,8 +3,8 @@
-
- {{userStory.title}} +
+ {{userStory.shortDescription}} {{userStory.status | removeDashes | capitalize}} @@ -30,8 +30,8 @@ @@ -48,8 +48,8 @@
- {{userStory.percentageComplete}}% Complete + aria-valuenow="{{userStory.completed.percentage}}" aria-valuemin="0" aria-valuemax="100" style="width: {{userStory.completed.percentage}}%"> + {{userStory.completed.completed}} / {{userStory.completed.total}} Blueprints completed
diff --git a/dashboard-project-app/client/app/projectList/controllers/projectList.controller.js b/dashboard-project-app/client/app/projectList/controllers/projectList.controller.js index 514ea3a..2914c6c 100755 --- a/dashboard-project-app/client/app/projectList/controllers/projectList.controller.js +++ b/dashboard-project-app/client/app/projectList/controllers/projectList.controller.js @@ -12,20 +12,26 @@ angular.module('dashboardProjectApp').controller('projectListCtrl', function( new Promise(function(resolve, reject) { var stringDate, realDate; + userStories.forEach(function each (story) { - if(story.latestUpdate !== undefined) { - lastUpdate = moment(story.latestUpdate[0].value).format("MM-DD-YYYY"); + + console.log('userStories', story); + if(story.lastUpdate !== '') { + lastUpdate = moment(story.lastUpdate, "YYYY-MM-DD").format("MM-DD-YYYY"); } else { - lastUpdate = moment(story.date, "DD-MM-YYYY").format("MM-DD-YYYY"); + lastUpdate = moment(story.dateCreated, "DD-MM-YYYY").format("MM-DD-YYYY"); } data.push( { - userStory: story.id+'-'+story.description, - dateCreated: moment(story.date, "DD-MM-YYYY").format("MM-DD-YYYY"), + userStory: story.id+'-'+story.userStory, + dateCreated: moment(story.dateCreated, "DD-MM-YYYY").format("MM-DD-YYYY"), lastUpdate: lastUpdate, - completed: story.percentage + progressPercentage: story.completed.percentage, + progressLabel: story.completed.completed + ' / ' + story.completed.total } ) + + console.log('data', data); resolve(data); }); }) diff --git a/dashboard-project-app/client/app/projectList/styles/projectList.css b/dashboard-project-app/client/app/projectList/styles/projectList.css index 6f8a01c..4d94561 100755 --- a/dashboard-project-app/client/app/projectList/styles/projectList.css +++ b/dashboard-project-app/client/app/projectList/styles/projectList.css @@ -23,3 +23,9 @@ .fixed-table-toolbar .bars, .fixed-table-toolbar .search, .fixed-table-toolbar .columns{ margin-bottom: 25px; } +.fixed-table-container tbody td { + max-width:200px; /* Customise it accordingly */ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/dashboard-project-app/client/app/projectList/views/projectList.html b/dashboard-project-app/client/app/projectList/views/projectList.html index 814bca9..0fc1d47 100755 --- a/dashboard-project-app/client/app/projectList/views/projectList.html +++ b/dashboard-project-app/client/app/projectList/views/projectList.html @@ -18,7 +18,7 @@ User Story Date Created Last Update - % Completed + Blueprints Completed diff --git a/dashboard-project-app/client/index.html b/dashboard-project-app/client/index.html index 7183ec9..704cfda 100755 --- a/dashboard-project-app/client/index.html +++ b/dashboard-project-app/client/index.html @@ -65,13 +65,13 @@ var story = value.split("-"); return '' + story[1] + ''; } - function progressBar(value, row) { + function progressBar(percentage, row) { return '
'+ '
'+ - ''+value+'%' + + 'role="progressbar" aria-valuenow="'+percentage+'" aria-valuemin="0"' + + 'aria-valuemax="100" style="width: '+percentage+'%">'+ + ''+row.progressLabel+'' + '
' + '
'; }