<h1>2016 : One bug a week</h1>
<p class="initial">If you did not, <a href="http://nicolaschevobbe.com/2016/01/02/One-bug-a-week.html" target="blank">read the initial blog post announcing the challenge</a></p>
<div class="stats">
<div class="stat-item">Current week<span class="value" id="currentWeek"></span></div>
<div class="stat-item">Failed weeks<span class="value" id="failedWeek"></span></div>
<div class="stat-item">Bugs fixed<span class="value" id="bugsFixed"></span></div>
<div class="stat-item">Overall success<span class="value" id="successPercentage"></span></div>
<button id="esc" type="button">✕</button>
<div class="bug-title"></div>
</div>
<svg viewBox="0 0 400 70" preserveAspectRatio="xMidYMin" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 757.8 534.8" style="enable-background:new 0 0 757.8 534.8;" xml:space="preserve">
</svg>
<h2>Blog posts</h2>
<ol class="posts">
<li><a href="http://nicolaschevobbe.com/2016/01/10/Week-1.html" target="blank">Week #1</a></li>
<li><a href="http://nicolaschevobbe.com/2016/01/31/Week-2-3-4.html" target="blank">Weeks #2, #3 & #4</a></li>
<li><a href="http://nicolaschevobbe.com/2016/02/07/Week-5.html" target="blank">Week #5</a></li>
<li><a href="http://nicolaschevobbe.com/2016/02/15/Week-6.html" target="blank">Week #6</a></li>
<li><a href="http://nicolaschevobbe.com/2016/02/22/Week-7.html" target="blank">Week #7</a></li>
<li><a href="http://nicolaschevobbe.com/2016/02/28/Week-8.html" target="blank">Week #8</a></li>
<li><a href="http://nicolaschevobbe.com/2016/03/06/Week-9.html" target="blank">Week #9</a></li>
</ol>
<footer>
<p>Heavily inspired by <a href="http://cushionapp.com/">Cushion</a> made by <a href="https://twitter.com/destroytoday">Jonnie Hallman ( @destroytoday )</a></p>
<p><a href="https://twitter.com/nicolaschevobbe">You can follow me on Twitter (@nicolaschevobbe)</a></p>
</footer>
@import url(https://fonts.googleapis.com/css?family=Libre+Baskerville:400italic);
@import url(https://fonts.googleapis.com/css?family=Signika:700);
html, body {
height: 100vh;
}
body {
display: flex;
flex-direction: column;
}
* {
box-sizing: border-box;
}
h1 {
padding: 0.25em 0 0.1em 0;
font-size: 2.5em;
line-height; 1.5em;
text-align: center;
background-color: #1976D2;
color: #FFF;
font-family: 'Signika';
font-variant-numeric: oldstyle-nums;
}
.stats {
font-size: 0.9em;
font-family: sans;
color: #212121;
background-color: #FFC107;
display: flex;
opacity: 0;
transition: opacity 0.5s;
height: 4em;
justify-content: center;
align-items: center;
}
.stats.ready {
opacity: 1;
}
.bug-title {
display: block;
text-align: center;
padding: 0 0.5em;
color: rgba(0,0,0,0.8);
font-family: 'Libre Baskerville', serif;
line-height: 1.3em;
display: none;
margin-right: auto;
font-size: 1.2em;
}
.bug-title a, .bug-title a:visited {
color: rgba(0,0,0,0.8);
}
.zoomed .bug-title {
display: block;
animation: .5s linear appears;
}
.stats .stat-item {
flex: 1;
text-align: center;
padding: 0.5em;
}
.stat-item .value {
font-family: 'Signika';
display: block;
text-align: center;
font-size: 1.5em;
}
.stats #esc {
display: none;
font-size: 1.5em;
border: none;
background : none;
margin-right: auto;
}
.zoomed .stats #esc {
align-self: start;
display: block;
}
.initial {
text-align: center;
padding: 1em;
font-size: 0.9em;
background-color: #1976D2;
color: #BBDEFB;
font-family: 'Libre Baskerville', serif;
line-height: 1.3em;
}
.initial a,.initial a:visited{
color: #BBDEFB;
}
svg {
width: 100%;
max-width: 100%;
min-height: 200px;
flex: 1;
}
.zoomed .bug-line:not(.detail),
.zoomed .stat-item,
.zoomed .weeks,
.zoomed .holidays,
.zoomed h2,
.zoomed .posts {
display: none;
}
.bug-line.detail .resolved {
display:none;
}
.bug-line {
cursor: pointer;
animation: .5s linear appears;
}
.mine {
background: transparent url("http://subtlepatterns2015.subtlepatterns.netdna-cdn.com/patterns/dark_embroidery.png") repeat scroll 0% 0%;
}
@keyframes appears {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
h2 {
text-align: center;
font-size: 2em;
font-family: 'Signika';
}
.posts {
font-size: 1.5em;
font-family: 'Libre Baskerville', serif;
display: flex;
padding: 1em;
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
.posts li {
margin: 0.2em 0.5em;
background-color: #607D8B;
padding: 0.2em 0.5em;
color: white;
}
.posts a {
color: white;
}
.posts a:visited {
color: rgba(255,255,255,0.5);
}
footer {
background-color: #212121;
color: white;
padding: 1em;
font-family: 'Libre Baskerville', serif;
margin-top: auto;
}
footer p {
text-align: center;
}
footer p + p{
margin-top: 1.2em;
}
footer a {
color: #BBDEFB;
}
const COLORS = ["rgb(244, 67, 54)","rgb(0, 150, 136)","rgb(96, 125, 139)","rgb(156, 39, 176)","rgb(103, 58, 183)","rgb(63, 81, 181)","rgb(33, 150, 243)","rgb(3, 169, 244)","rgb(0, 188, 212)","rgb(76, 175, 80)","rgb(139, 195, 74)","rgb(255, 193, 7)","rgb(255, 152, 0)","rgb(255, 87, 34)","rgb(233, 30, 99)","rgb(121, 85, 72)"];
var userColor = {};
var svg = document.querySelector('svg');
const jan4 = new Date('2016-01-04');
const X_PADDING = 17;
const LINE_HEIGHT = 10;
const MILLISECOND_A_DAY = (1000*60*60*24);
const BUGZILLA_API_URL = 'https://bugzilla.mozilla.org/rest/';
const EMAIL = '[email protected]';
const HOLIDAYS = [{
name: 'Skiing',
start : new Date('2016-01-16'),
end: new Date('2016-01-23')
}];
drawHolidays();
var lanes = [];
var bugs = [];
const weekNumber = Math.ceil(((new Date()) - jan4)/MILLISECOND_A_DAY/7);
const todayNumber = (((new Date()) - jan4)/MILLISECOND_A_DAY);
var params = {
"include_fields": "id,summary,status,cf_last_resolved,target_milestone,creation_time,resolution, assigned_to",
"email1": EMAIL,
"emailassigned_to1":1
};
if(window.URLSearchParams){
var searchParams = new URLSearchParams();
Object.keys(params).forEach(function(key){
searchParams.append(key, params[key]);
});
searchParams = searchParams.toString();
} else {
var searchParams = [];
Object.keys(params).forEach(function(key){
searchParams.push(key+"="+params[key]);
});
searchParams = searchParams.join('&');
}
var url = `${BUGZILLA_API_URL}bug?${searchParams}`;
var myHeaders = new Headers();
myHeaders.append('Accept', 'application/json');
fetch(url, {
mode: 'cors',
method: 'GET',
headers: myHeaders
})
.then((response) => response.json())
.then(function(data){
var promises = [];
data.bugs.forEach(function(bug){
if(bug.cf_last_resolved && new Date(bug.cf_last_resolved) < jan4){
return;
}
var historyPromise = getBugHistory(bug).then(function(history){
bug.history = history;
drawBug(bug);
return bug
});
/* var commentsPromise = getBugComments(bug);
commentsPromise.then(function(comments){
bug.comments = comments;
});
var infoPromise = Promise.all([historyPromise, commentsPromise]).then(function(results){
return bug;
});*/
promises.push(historyPromise);
});
return Promise.all(promises);
})
.then(function(data){
bugs = data;
updateDashboard(bugs);
drawWeeks(bugs);
})
.catch((e) => console.error(e));
svg.addEventListener('click',function(e){
if(document.body.classList.contains('zoomed') === false){
if(
e.target.classList.contains('bug-line') ||
e.target.parentElement.classList.contains('bug-line')
) {
var el = e.target;
if(!el.classList.contains('bug-line')){
el = e.target.parentElement;
}
zoomInBug(el);
}
}
}, false);
document.getElementById('esc').addEventListener('click', zoomOut);
document.body.addEventListener('keydown', function(evt){
if(evt.key === 'Escape'){
zoomOut();
}
});
function getBugHistory(bugData){
var url = `${BUGZILLA_API_URL}bug/${bugData.id}/history`;
return fetch(url, {
mode: 'cors',
method: 'GET',
headers: myHeaders
})
.then((response) => response.json())
.then(function(data){
data.bugs[0].history.some(function(activity){
var hasAssignement = activity.changes.some(function(change){
return (change.field_name === 'assigned_to' && change.added === EMAIL);
});
if(hasAssignement === true){
bugData.assign_time = activity.when;
return true;
}
});
if(!bugData.assign_time && bugData.assigned_to == EMAIL){
bugData.assign_time = bugData.creation_time;
}
return data.bugs[0].history;
}).catch((e) => console.error(e));
}
function getBugComments(bugData){
var url = `${BUGZILLA_API_URL}bug/${bugData.id}/comment`;
return fetch(url, {
mode: 'cors',
method: 'GET',
headers: myHeaders
})
.then((response) => response.json())
.then(function(data){
return data.bugs[bugData.id].comments;
}).catch((e) => console.error(e));
}
function drawBug(bug){
if(bug.assign_time !== null ){
var colorIndex = (bug.id % (COLORS.length - 1));
var bugColor = COLORS[colorIndex];
var startDayNumber = 0;
if(new Date(bug.assign_time) > jan4){
startDayNumber = (new Date(bug.assign_time) - jan4)/MILLISECOND_A_DAY;
}
var endDate = new Date();
if(bug.cf_last_resolved){
endDate = new Date(bug.cf_last_resolved);
}
var endDayNumber = ((endDate - jan4)/MILLISECOND_A_DAY);
var startPoint = X_PADDING + startDayNumber;
var endPoint = X_PADDING + endDayNumber;
var laneNumber = findLane(startPoint,endPoint);
if(!lanes[laneNumber]){
lanes[laneNumber] = [];
}
lanes[laneNumber].push([startPoint,endPoint]);
var y = (laneNumber + 1 ) * LINE_HEIGHT;
var bugGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
bugGroup.classList.add('bug-line');
bugGroup.setAttribute('title', `Bug ${bug.id} - ${bug.summary}`);
bugGroup.setAttribute('data-bug-id', bug.id);
if(bug.cf_last_resolved && bug.resolution == 'FIXED'){
var endCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
endCircle.classList.add('resolved');
endCircle.setAttribute('cx',endPoint);
endCircle.setAttribute('cy', y);
endCircle.setAttribute('r', 2);
endCircle.setAttribute('fill', bugColor);
bugGroup.appendChild(endCircle);
}
var assignStartDayNumber = ((new Date(bug.assign_time) - jan4)/MILLISECOND_A_DAY);
var assignStartPoint = X_PADDING + assignStartDayNumber;
var bugAssignedLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
bugAssignedLine.setAttribute('x1', assignStartPoint);
bugAssignedLine.setAttribute('y1', y);
bugAssignedLine.setAttribute('x2', endPoint);
bugAssignedLine.setAttribute('y2', y);
bugAssignedLine.setAttribute('stroke', bugColor);
bugAssignedLine.setAttribute('stroke-width', 2);
bugAssignedLine.setAttribute('stroke-linecap', "round");
bugGroup.appendChild(bugAssignedLine);
svg.appendChild(bugGroup);
}
}
function drawWeeks(bugs){
var weekGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
weekGroup.classList.add('weeks');
for(var i = 0; i <= 52; i++){
var weekLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
var x = X_PADDING + (i * 7);
weekLine.setAttribute('x1', x);
weekLine.setAttribute('y1', 0);
weekLine.setAttribute('x2', x);
weekLine.setAttribute('y2', 10000);
weekLine.setAttribute('stroke', 'rgba(0,0,0,0.3)');
weekLine.setAttribute('stroke-width', 0.1);
weekGroup.appendChild(weekLine);
if(todayNumber >= i * 7){
var weekStatus = document.createElementNS("http://www.w3.org/2000/svg", "rect");
weekStatus.setAttribute('x', x);
weekStatus.setAttribute('y', 0);
weekStatus.setAttribute('width', 7);
weekStatus.setAttribute('height', 2);
var hasResolved = bugs.some(function(bug){
if(bug.cf_last_resolved){
var resolvedDayNumber = (new Date(bug.cf_last_resolved) - jan4)/MILLISECOND_A_DAY;
return (Math.floor(resolvedDayNumber /7) == i);
}
});
var fillColor = hasResolved?'#8BC34A':'#F44336';
if(!hasResolved && i + 1 === weekNumber){
fillColor = '#607D8B';
}
weekStatus.setAttribute('fill', fillColor);
weekGroup.appendChild(weekStatus);
}
}
svg.insertBefore(weekGroup,svg.firstChild);
}
function drawHolidays(){
var holidaysGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
holidaysGroup.classList.add('holidays');
HOLIDAYS.forEach(function(holiday){
var holidayRect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
holidayRect.setAttribute('title', holiday.name);
holidayRect.setAttribute('x', getPositionFromDate(holiday.start));
holidayRect.setAttribute('y', 2);
holidayRect.setAttribute('width', getPositionFromDate(holiday.end) - getPositionFromDate(holiday.start));
holidayRect.setAttribute('height', 10000);
holidayRect.setAttribute('fill', '#77B6EC');
holidayRect.setAttribute('fill-opacity', 0.3);
holidaysGroup.appendChild(holidayRect);
});
svg.appendChild(holidaysGroup);
}
function zoomInBug(el){
var id = el.getAttribute("data-bug-id");
var bugData = bugs.find(function(item){
return (item.id == id);
});
var line = el.querySelector('line');
var duration = 500;
var x1 = parseFloat(line.getAttribute("x1"));
var x2 = parseFloat(line.getAttribute("x2"));
var y = parseFloat(line.getAttribute("y1"));
var strokeWidth = parseFloat(line.getAttribute("stroke-width"));
line.setAttribute("data-x1",x1);
line.setAttribute("data-x2",x2);
line.setAttribute("data-y",y);
line.setAttribute("data-stroke-width",strokeWidth);
var x1FromStart = x1 - X_PADDING;
var x1MoveByMs = x1FromStart/duration;
var x2FromStart = 400 - x2 - X_PADDING;
var x2MoveByMs = x2FromStart/duration;
var yFromMiddle = 50 - y;
var yMoveByMs = yFromMiddle/duration;
var finalStrokeWidth = 20;
var strokeWidthMoveByMs = (finalStrokeWidth - strokeWidth)/duration;
var start = 0;
function zoom(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
if (progress < duration) {
line.setAttribute('x1',x1 - (x1MoveByMs * progress));
line.setAttribute('x2',x2 + (x2MoveByMs * progress));
line.setAttribute('y1',y + (yMoveByMs * progress));
line.setAttribute('y2',y + (yMoveByMs * progress));
line.setAttribute('stroke-width',strokeWidth + (strokeWidthMoveByMs*progress));
requestAnimationFrame(zoom);
} else {
line.setAttribute('x1',X_PADDING);
line.setAttribute('x2',400 - X_PADDING);
line.setAttribute('y1',50);
line.setAttribute('y2',50);
line.setAttribute('stroke-width',finalStrokeWidth);
document.querySelector('.bug-title').innerHTML =`<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=${id}" target="blank">Bug ${id} - ${bugData.summary}</a>`;
drawBugDetail(el, bugData);
}
}
requestAnimationFrame(zoom);
document.body.classList.add('zoomed');
el.classList.toggle('detail');
}
function drawBugDetail(el, bugData){
var assignDate = new Date(bugData.assign_time);
var endDate = new Date();
if(bugData.cf_last_resolved){
endDate = (new Date(bugData.cf_last_resolved));
}
var bugPeriod = [assignDate, endDate];
var bugGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
bugGroup.classList.add('detail-bug-line');
if(bugData.cf_last_resolved){
var endCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
endCircle.setAttribute('cx',getPositionFromDate(endDate,bugPeriod));
endCircle.setAttribute('cy', 50);
endCircle.setAttribute('r', 9);
endCircle.setAttribute('fill', 'rgba(0,0,0,0.3)');
endCircle.setAttribute('title',`RESOLVED ${bugData.cf_last_resolved}`)
bugGroup.appendChild(endCircle);
}
var bugLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
bugLine.setAttribute('title', `Bug ${bugData.id} - ${bugData.summary}`);
bugLine.setAttribute('x1', getPositionFromDate(new Date(bugData.creation_time),bugPeriod));
bugLine.setAttribute('y1', 50);
bugLine.setAttribute('x2', getPositionFromDate(endDate,bugPeriod));
bugLine.setAttribute('y2', 50);
bugLine.setAttribute('stroke', 'rgba(0,0,0,1)');
bugLine.setAttribute('stroke-width', 2);
bugLine.setAttribute('stroke-linecap', 'round');
bugGroup.appendChild(bugLine);
var groups = [];
var historyEntries = [];
bugData.history.forEach(function(entry){
if(! userColor[entry.who]){
userColor[entry.who] = COLORS.shift();
}
var entryColor = userColor[entry.who];
historyEntries.push({
x : getPositionFromDate(new Date(entry.when),bugPeriod),
title: `${entry.when} - ${entry.who} : ${entry.changes.map((item) => (item.field_name + " " + item.added)).join(' - ')}`,
color: entryColor,
mine: entry.who === EMAIL
});
});
/*bugData.comments.forEach(function(entry){
if(! userColor[entry.creator]){
userColor[entry.creator] = COLORS.shift();
}
var entryColor = userColor[entry.creator];
historyEntries.push({
x : getPositionFromDate(new Date(entry.creation_time),bugPeriod),
title: `${entry.creation_time} - ${entry.creator} : ${entry.text.substr(0,100)}`,
color: entryColor,
mine: entry.creator === EMAIL
});
});*/
historyEntries.sort(function(a, b){
return a.x > b.x ? 1 : -1;
});
historyEntries.reduce(function(previousValue, currentValue, currentIndex, array){
if(groups.length > 0 && previousValue && currentValue.x - previousValue.x < 10){
groups[groups.length - 1].push(currentValue);
} else {
groups.push([currentValue]);
}
return currentValue;
},null);
groups.forEach(function(group, index){
var groupGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
var circleR = 10;
var strokeWidth = 2;
var x1 = group[0].x;
var x2 = group[group.length - 1].x;
var groupLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
groupLine.setAttribute('x1',x1);
groupLine.setAttribute('x2',x2);
groupLine.setAttribute('y1', 50);
groupLine.setAttribute('y2', 50);
groupLine.setAttribute('stroke', 'rgba(0,0,0,1)');
groupLine.setAttribute('stroke-width', 12 );
groupLine.setAttribute('stroke-linecap', 'round');
groupGroup.appendChild(groupLine);
var clipId = 'group-'+index;
var groupClipath = document.createElementNS("http://www.w3.org/2000/svg", "clipPath");
groupClipath.setAttribute('id',clipId);
var startCircleClip = document.createElementNS("http://www.w3.org/2000/svg", "circle");
startCircleClip.setAttribute('cx',x1);
startCircleClip.setAttribute('cy',50);
startCircleClip.setAttribute('r',circleR / 2);
var endCircleClip = document.createElementNS("http://www.w3.org/2000/svg", "circle");
endCircleClip.setAttribute('cx',x2);
endCircleClip.setAttribute('cy',50);
endCircleClip.setAttribute('r',circleR / 2);
var rectClip = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rectClip.setAttribute("x",x1);
rectClip.setAttribute("y",50 - (circleR/2) );
rectClip.setAttribute("height",circleR );
rectClip.setAttribute("width", x2-x1);
groupClipath.appendChild(startCircleClip);
groupClipath.appendChild(endCircleClip);
groupClipath.appendChild(rectClip);
groupGroup.appendChild(groupClipath);
var groupStart = x1 - circleR;
var groupEnd = x2 + circleR;
var entryWidth = (groupEnd - groupStart)/group.length;
group.forEach(function(groupEntry, idx){
var entryLine = document.createElementNS("http://www.w3.org/2000/svg", "rect");
entryLine.setAttribute('x', groupStart + (entryWidth * idx) );
entryLine.setAttribute('width', entryWidth );
entryLine.setAttribute('fill', groupEntry.color);
entryLine.setAttribute('stroke', 'black');
entryLine.setAttribute('title', groupEntry.title);
entryLine.setAttribute('y', 50 - circleR );
entryLine.setAttribute('height', circleR * 2);
entryLine.setAttribute('stroke-width', .5);
entryLine.setAttribute('clip-path',`url(#${clipId})`);
groupGroup.appendChild(entryLine);
})
bugGroup.appendChild(groupGroup);
});
el.appendChild(bugGroup);
for(var i = 0; i <= (endDate.getTime() - jan4.getTime()) / (MILLISECOND_A_DAY * 7); i++){
var weekLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
var firstWeekDay = new Date(jan4.getTime() + ( i * MILLISECOND_A_DAY * 7));
firstWeekDay.setHours(0,0,0);
if(firstWeekDay > (new Date(bugData.assign_time) - (MILLISECOND_A_DAY * 7)) && firstWeekDay <= endDate){
weekLine.setAttribute('x1', getPositionFromDate(firstWeekDay,bugPeriod));
weekLine.setAttribute('y1', 0);
weekLine.setAttribute('x2', getPositionFromDate(firstWeekDay,bugPeriod));
weekLine.setAttribute('y2', 100);
weekLine.setAttribute('stroke', 'black');
weekLine.setAttribute('stroke-opacity', 0.4);
weekLine.setAttribute('stroke-width', 0.2);
bugGroup.insertBefore(weekLine,bugGroup.firstChild);
var weekText = document.createElementNS("http://www.w3.org/2000/svg", "text");
weekText.textContent = 'Week #'+ (i +1);
var pos = getPositionFromDate(firstWeekDay,bugPeriod);
if(pos < 0){
pos = 0;
}
console.log(weekText.textContent, pos, 'next week',getPositionFromDate(new Date(firstWeekDay.getTime() + (7 * MILLISECOND_A_DAY)),bugPeriod))
if(getPositionFromDate(new Date(firstWeekDay.getTime() + (7 * MILLISECOND_A_DAY)),bugPeriod) - pos < 40){
weekText.textContent = '#'+ (i +1);
}
weekText.setAttribute('x', pos + 5);
weekText.setAttribute('y', 20 );
weekText.setAttribute('font-size',10);
weekText.setAttribute('font-family','Signika');
weekText.setAttribute('fill', '#666');
bugGroup.insertBefore(weekText, bugGroup.firstChild);
}
}
for(var i = 0; i <= (endDate.getTime() - bugData.assign_time ) / MILLISECOND_A_DAY ; i++){
var day = new Date(bugData.assign_time + ( i * MILLISECOND_A_DAY));
day.setHours(0,0,0);
var dayLine = document.createElementNS("http://www.w3.org/2000/svg", "line");
var pos = getPositionFromDate(day,bugPeriod);
dayLine.setAttribute('x1',pos);
dayLine.setAttribute('y1', 25);
dayLine.setAttribute('x2', pos);
dayLine.setAttribute('y2', 75);
dayLine.setAttribute('stroke', 'black');
dayLine.setAttribute('stroke-opacity', 0.1);
dayLine.setAttribute('stroke-width', .5);
bugGroup.insertBefore(dayLine,bugGroup.firstChild);
}
}
function zoomOut(){
var zoomedEl = document.querySelector('.detail');
if(zoomedEl){
var detail = zoomedEl.querySelector('.detail-bug-line');
if(detail){
detail.remove();
}
var line = zoomedEl.querySelector('line');
var duration = 500;
var x1 = parseFloat(line.getAttribute("x1"));
var x2 = parseFloat(line.getAttribute("x2"));
var y = parseFloat(line.getAttribute("y1"));
var strokeWidth = parseFloat(line.getAttribute("stroke-width"));
var initialX1 = parseFloat(line.getAttribute("data-x1"));
var initialX2 = parseFloat(line.getAttribute("data-x2"));
var initialY = parseFloat(line.getAttribute("data-y"));
var initialStrokeWidth = parseFloat(line.getAttribute("data-stroke-width"));
var x1Move = x1 - initialX1;
var x1MoveByMs = x1Move/duration;
var x2Move = x2 - initialX2;
var x2MoveByMs = x2Move/duration;
var yMove = y - initialY;
var yMoveByMs = yMove/duration;
var strokeWidthMoveByMs = (strokeWidth - initialStrokeWidth)/duration;
var start = 0;
function zoomOut(timestamp) {
if (!start) start = timestamp;
var progress = timestamp - start;
if (progress < duration) {
line.setAttribute('x1',x1 - (x1MoveByMs*progress));
line.setAttribute('x2',x2 - (x2MoveByMs*progress));
line.setAttribute('y1',y - (yMoveByMs*progress));
line.setAttribute('y2',y - (yMoveByMs*progress));
line.setAttribute('stroke-width',strokeWidth - (strokeWidthMoveByMs*progress));
requestAnimationFrame(zoomOut);
} else {
line.setAttribute('x1',initialX1);
line.setAttribute('x2',initialX2);
line.setAttribute('y1',initialY);
line.setAttribute('y2',initialY);
line.setAttribute('stroke-width',initialStrokeWidth);
document.querySelector('.bug-title').innerHTML ="";
zoomedEl.classList.remove('detail');
document.body.classList.remove('zoomed');
}
}
requestAnimationFrame(zoomOut);
}
}
function getPositionFromDate(date, period){
if(period){
var start = period[0];
var end = period[1];
var length = (end - start);
var percent = (date - start)/length;
return X_PADDING + ((400 - (X_PADDING*2)) * percent);
}
var dayNumber = ((date - jan4)/MILLISECOND_A_DAY);
if(dayNumber < 0){
dayNumber = 0;
}
return X_PADDING + dayNumber;
}
function findLane(start, end){
var lane = 0;
var safe_space = 4;
start = start - safe_space;
end = end + safe_space;
for(;lane < lanes.length;lane++){
var fit = lanes[lane].every(function(xs){
return !(
( xs[0] >= start && xs[0] <= end ) ||
( start >= xs[0] && start <= xs[1] )
);
});
if(fit === true){
break;
}
}
return lane;
}
function updateDashboard(bugs){
var fixedBugs = bugs.filter(function(bug){
return (bug.resolution === "FIXED");
});
var resolutionWeeks = fixedBugs.map((bug) => Math.ceil(((new Date(bug.cf_last_resolved)) - jan4)/MILLISECOND_A_DAY/7));
var missedWeeks = [];
for(var i = 1; i <=weekNumber; i++){
if(resolutionWeeks.indexOf(i) === -1){
missedWeeks.push(i);
}
}
document.getElementById('currentWeek').textContent = weekNumber;
document.getElementById('failedWeek').textContent = missedWeeks.length;
document.getElementById('failedWeek').setAttribute("title", missedWeeks.map((x) => `#${x}`).join(' - '));
document.getElementById('bugsFixed').textContent = fixedBugs.length;
document.getElementById('successPercentage').textContent = Math.round((fixedBugs.length / weekNumber) * 100)+"%";
document.querySelector('.stats').classList.toggle('ready');
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.