Merge pull request #14 from OpenStackweb/development

Development product release
This commit is contained in:
Arturo Nunez 2017-01-30 13:58:28 -06:00 committed by GitHub
commit 04d17ef9bf
11 changed files with 310 additions and 316 deletions

View File

@ -8,35 +8,42 @@ module.exports = function(UserStory) {
var async = require("async"); var async = require("async");
var htmlparser = require("htmlparser"); var htmlparser = require("htmlparser");
var cheerio = require('cheerio'); var cheerio = require('cheerio');
const SPEC_URL = "http://specs.openstack.org/openstack/openstack-user-stories/user-stories/proposed/";
var blueprintsResume = []; var blueprintsResume = [];
var getAllfiles = function(getPercentage){ var getAllfiles = function(){
return fs.readdirSync(route) return fs.readdirSync(route)
.map(function(file){ .map(function(file){
var route = "../tracker/" + file ; var route = "../tracker/" + file ;
var data = JSON.parse(fs.readFileSync(route)); var userStory = JSON.parse(fs.readFileSync(route));
var parsedData; return userStory;
if(getPercentage){
parsedData = getTotalPercentage(data);
data.blueprintList = parsedData;
}
return data;
}); });
} }
//get completed percentage of all the userstories var getFileById = function(id){
var getTotalPercentage = function(userStory){
blueprintsResume.completed = 0; //get all files
blueprintsResume.total = 0; var userStories = getAllfiles();
var blueprintList = []; //filter by Id
var file = userStories.filter(function(item){
return item.id == id;
})
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) { userStory.tasks.forEach(function (taskName, index, array) {
@ -46,7 +53,6 @@ module.exports = function(UserStory) {
var blueprints = task.projects_status[projectName].blueprints; var blueprints = task.projects_status[projectName].blueprints;
var blueprintNames = Object.keys(blueprints); var blueprintNames = Object.keys(blueprints);
blueprintList.push(blueprintNames);
blueprintNames.forEach(function (blueprintName, index, array) { blueprintNames.forEach(function (blueprintName, index, array) {
@ -61,201 +67,64 @@ module.exports = function(UserStory) {
}) })
var finalPercentage = Math.round((blueprintsResume.completed*100)/blueprintsResume.total) blueprintsResume.percentage = (blueprintsResume.completed/blueprintsResume.total)*100;
blueprintList.push([{percentage:(finalPercentage)}]);
return blueprintList;
return blueprintsResume;
} }
//get completed percentage of a User story //get the field lastupdated for a usterStory
var getPercentage = function(blueprints){ var getLastUpdated = function(userStory, cb){
var total = blueprints.length;
var complete = 0;
blueprints.forEach(function(element, index, array) {
if(element == 'completed')
complete = complete + 1;
})
return Math.round((complete*100)/total);
}
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; var Patch = app.models.Patch;
Patch.latestUpdate(file.source, function(err, response) {
var lastUpdate = ''; var lastUpdate = '';
if(response){ Patch.latestUpdate(userStory.source, function (err, response, next) {
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);
})
}
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']);
originalTask.xp_status = originalTask.xp_status;
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 = JSON.parse(response.substring(5));
response.forEach(function each(element) { if(response.length > 0){
data.push( lastUpdate = response.map( function each (element){
{ return element.updated
url: "https://review.openstack.org/#/c/" + element._number, }).sort().pop();
name: element.subject var arrayLastUpdate = lastUpdate.split(' ');
}); lastUpdate = arrayLastUpdate[0];
});
originalBlueprint = {
name: blueprintName.replace(/-/g, " "),
uri: uri,
status: status,
review_link:data
} }
callback(null, originalBlueprint)
}) cb(null, lastUpdate)
}) })
} }
//TODO: APPLY async.waterfall // Parse data from userStory
var parseUserStory = function(userStory, callback){
async.waterfall([function(cb){
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);
},function(userStory, cb){
var tasksName = userStory.tasks;
var tasks = userStory.tasks_status;
parseTasks(userStory, tasksName, tasks, cb)
}],function(err,userStory){
callback(null, userStory);
})
}
//??
var parseTasks = function(userStory, tasksNames, tasks, callback) { //get tasks var parseTasks = function(userStory, tasksNames, tasks, callback) { //get tasks
var tmpTasks = {}; var tmpTasks = {};
@ -308,7 +177,7 @@ module.exports = function(UserStory) {
userStory.tasks = tasksNames; userStory.tasks = tasksNames;
userStory.tasks_status = tmpTasks; userStory.tasks_status = tmpTasks;
userStory.percentageComplete = getPercentage(blueprintsResume) //userStory.percentageComplete = getPercentage(blueprintsResume)
callback(null, userStory); 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.on('attached',function(){
UserStory.findById = function(id, params, cb){ UserStory.findById = function(id, params, cb){
async.waterfall([ var userStory = getFileById(id);
parseUserStory(id),
parseTasks
], function (err, result) {
cb(null,result)
});
}; if(userStory){
parseUserStory(userStory, cb);
}else{
cb('File does not exist', null);
}
};//end find by id
UserStory.find = function(params, cb){ UserStory.find = function(params, cb){
var Patch = app.models.Patch; var userStories = getAllfiles();
//get all files
var userStories = getAllfiles(true);
var percentage;
var blueprintKeys;
var dateList = [];
//var response = userStories;
var result; async.mapSeries(userStories, parse, cb);
// looking dates function parse(userStory, callback) {
var lookingDates = function (id, blueprintKeys) {
blueprintKeys.forEach(function each (key) {
var data = [];
Patch.latestUpdate(key, function (err, response, next) {
response = JSON.parse(response.substring(5)); async.waterfall([function(cb){
async.waterfall([ getLastUpdated(userStory, cb)
function createOutput(next){ },function(lastUpdated, cb){
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);
});
});
}
function keysrt(key,asc) { var itemResult = {
return function(a,b){ completed: getbluePrintResume(userStory),
return asc ? ~~(a[key] < b[key]) : ~~(a[key] > b[key]); dateCreated: userStory.date,
} lastUpdate: lastUpdated,
} userStory: userStory.description,
id:userStory.id
};
async.waterfall ([ cb(null, itemResult);
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(err,result){
}, callback( err, result);
function sendResult(next){ })
//cb(null,userStories);
next;
} }
]); };//End find
}
}) })
}; };

View File

@ -314,7 +314,7 @@ module.exports = function (grunt) {
development: { development: {
constants: function() { constants: function() {
var config = require('./' + grunt.config.get('ngconstant.options.configPath')) var config = require('./' + grunt.config.get('ngconstant.options.configPath'))
config.apiBaseUrl = 'http://localhost:3004'; config.apiBaseUrl = 'http://localhost:3004/api';
return { appConfig : config}; return { appConfig : config};
} }
}, },

View File

@ -2,8 +2,8 @@
(function () { (function () {
angular.module('dashboardProjectApp') angular.module('dashboardProjectApp')
.controller('projectDetailController', ['$scope','$state', 'UserStory'/*, 'tasksService', 'tasks', 'task'*/, .controller('projectDetailController', ['$scope','$state', 'UserStory', '$location',
function($scope, $state, UserStory/*, tasksService, tasks, task*/) { function($scope, $state, UserStory, $location) {
$scope.taskId = $state.params.id; $scope.taskId = $state.params.id;
$scope.openTasks = {}; $scope.openTasks = {};
@ -43,19 +43,32 @@
function success(userStory) { function success(userStory) {
$scope.userStory = userStory; $scope.userStory = userStory;
$scope.userStory.updatedOn = moment($scope.userStory.updatedOn).format("MM-DD-YYYY"); // Formating User Story name
for(var key in $scope.userStory.tasks_status) { if ((userStory.description).length > 100) {
$scope.actualProject[key] = $scope.userStory.tasks_status[key].projects[0] $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.selectProject = function(keyProject, idTask){
$scope.actualProject[idTask] = keyProject $scope.actualProject[idTask] = keyProject
} }
getFile();
$scope.showMore = function(key){ $scope.showMore = function(key){
$scope.showText[key] = true; $scope.showText[key] = true;
@ -69,13 +82,16 @@
window.location.href = "mailto:" + email + "?subject=Mail to " + email; window.location.href = "mailto:" + email + "?subject=Mail to " + email;
} }
getFile();
}]) }])
.filter('capitalize', function() { .filter('capitalize', function() {
return function(input) { 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) { return function(string) {
if (!angular.isString(string)) { if (!angular.isString(string)) {

View File

@ -1,19 +1,25 @@
'use strict'; 'use strict';
angular.module('dashboardProjectApp') angular.module('dashboardProjectApp')
.config(function ($stateProvider) { .config(function ($stateProvider, $urlRouterProvider) {
$stateProvider $stateProvider
.state('error', {
url: '/projectDetail/notFound/:id',
templateUrl: 'app/projectDetail/views/notFound.html',
controller: function($scope, $state){
$scope.id = $state.params.id;
}
})
.state('projectDetail', { .state('projectDetail', {
url: '/projectDetail/:id', url: '/projectDetail/:id',
/* resolve: {
userStory: ['userStoryService',
function(userStoryService) {
return userStoryService.getTasks();
}]
},*/
templateUrl: 'app/projectDetail/views/projectDetail.html', templateUrl: 'app/projectDetail/views/projectDetail.html',
controller: 'projectDetailController' controller: 'projectDetailController'
}) })
}); });

View File

@ -42,3 +42,29 @@
table tr.separator { height: 30px; } 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;
}

View File

@ -0,0 +1,18 @@
<div class="views-details">
<div class="big-title">
404
</div>
<div class="big-subtitle">
The Tracker with Id <span class="focus-title">{{id}}</span> does not exist. Are you sure it is spelled correctly?
</div>
<div class="middle-subtitle">
You can try return to the <a href="/projectList">list of trackers</a> and look for it
</div>
</div>

View File

@ -3,8 +3,8 @@
<div class="well frm-well"> <div class="well frm-well">
<div class="row"> <div class="row">
<div class="col-xs-11"> <div class="col-xs-11">
<div class="dp-title "> <div class="dp-title" data-toggle="tooltip" title="{{userStory.description}}">
{{userStory.title}} {{userStory.shortDescription}}
<span class="dp-label label label-primary dp-label-{{userStory.status}}"> <span class="dp-label label label-primary dp-label-{{userStory.status}}">
{{userStory.status | removeDashes | capitalize}} {{userStory.status | removeDashes | capitalize}}
</span> </span>
@ -30,8 +30,8 @@
<div class="col-xs-3"> <div class="col-xs-3">
<i class="fa fa-user" aria-hidden="true"></i> <i class="fa fa-user" aria-hidden="true"></i>
<strong>Submitted by</strong> <strong>Submitted by</strong>
<a href ng-click="mailTo(userStory.submittedByEmail)"> <a href ng-click="mailTo(userStory.submitted_by.email)">
{{userStory.submittedBy}} {{userStory.submitted_by.name}}
</a> </a>
</div> </div>
@ -48,8 +48,8 @@
<div class="col-xs-12"> <div class="col-xs-12">
<div class="progress"> <div class="progress">
<div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" <div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar"
aria-valuenow="{{userStory.percentageComplete}}" aria-valuemin="0" aria-valuemax="100" style="width: {{userStory.percentageComplete}}%"> aria-valuenow="{{userStory.completed.percentage}}" aria-valuemin="0" aria-valuemax="100" style="width: {{userStory.completed.percentage}}%">
<span class="sr-only">{{userStory.percentageComplete}}% Complete</span> <span class="sr-only">{{userStory.completed.completed}} / {{userStory.completed.total}} Blueprints completed</span>
</div> </div>
</div> </div>
</div> </div>

View File

@ -12,20 +12,26 @@ angular.module('dashboardProjectApp').controller('projectListCtrl', function(
new Promise(function(resolve, reject) { new Promise(function(resolve, reject) {
var stringDate, realDate; var stringDate, realDate;
userStories.forEach(function each (story) { 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 { } 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( data.push(
{ {
userStory: story.id+'-'+story.description, userStory: story.id+'-'+story.userStory,
dateCreated: moment(story.date, "DD-MM-YYYY").format("MM-DD-YYYY"), dateCreated: moment(story.dateCreated, "DD-MM-YYYY").format("MM-DD-YYYY"),
lastUpdate: lastUpdate, lastUpdate: lastUpdate,
completed: story.percentage progressPercentage: story.completed.percentage,
progressLabel: story.completed.completed + ' / ' + story.completed.total
} }
) )
console.log('data', data);
resolve(data); resolve(data);
}); });
}) })

View File

@ -23,3 +23,9 @@
.fixed-table-toolbar .bars, .fixed-table-toolbar .search, .fixed-table-toolbar .columns{ .fixed-table-toolbar .bars, .fixed-table-toolbar .search, .fixed-table-toolbar .columns{
margin-bottom: 25px; margin-bottom: 25px;
} }
.fixed-table-container tbody td {
max-width:200px; /* Customise it accordingly */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@ -18,7 +18,7 @@
<th data-field="userStory" data-sortable="true" data-formatter="nameFormatter">User Story</th> <th data-field="userStory" data-sortable="true" data-formatter="nameFormatter">User Story</th>
<th data-field="dateCreated" data-sortable="true">Date Created</th> <th data-field="dateCreated" data-sortable="true">Date Created</th>
<th data-field="lastUpdate" data-sortable="true">Last Update</th> <th data-field="lastUpdate" data-sortable="true">Last Update</th>
<th data-field="completed" data-sortable="true" data-formatter="progressBar">% Completed</th> <th data-field="progressPercentage" label="progressLabel" data-sortable="true" data-formatter="progressBar"> Blueprints Completed</th>
</tr> </tr>
</thead> </thead>
</table> </table>

View File

@ -65,13 +65,13 @@
var story = value.split("-"); var story = value.split("-");
return '<a class="dp-link-title" href="projectDetail/'+ story[0] +'">' + story[1] + '</a>'; return '<a class="dp-link-title" href="projectDetail/'+ story[0] +'">' + story[1] + '</a>';
} }
function progressBar(value, row) { function progressBar(percentage, row) {
return '<div class="progress dp-progress">'+ return '<div class="progress dp-progress">'+
'<div class="progress-bar progress-bar-success progress-bar-striped"'+ '<div class="progress-bar progress-bar-success progress-bar-striped"'+
'role="progressbar" aria-valuenow="'+value+'" aria-valuemin="0"' + 'role="progressbar" aria-valuenow="'+percentage+'" aria-valuemin="0"' +
'aria-valuemax="100" style="width: '+value+'%">'+ 'aria-valuemax="100" style="width: '+percentage+'%">'+
'<span class="sr-only">'+value+'%</span>' + '<span class="sr-only">'+row.progressLabel+'</span>' +
'</div>' + '</div>' +
'</div>'; '</div>';
} }