Edit on
<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 = 'chevobbe.nicolas@gmail.com';

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');
}
Rerun