Skip to content

Instantly share code, notes, and snippets.

@rhysgodfrey
Created January 30, 2013 22:30
Show Gist options
  • Save rhysgodfrey/4677847 to your computer and use it in GitHub Desktop.
Save rhysgodfrey/4677847 to your computer and use it in GitHub Desktop.
The Activity Repository takes a list of updates and maps this to a card entering and leaving a board (some of this logic is a bit hacky). This then gives the set of Lists on the board, with when each card was added to the list (and when it left, if it has). The Cumulative Flow Model then takes this information to decide how many cards are in eac…
using Chello.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using TrelloCFD.Factories;
using Chello.ScrumExtensions;
namespace TrelloCFD.Domain
{
public class ActivityRepository
{
private string _boardId;
private IDictionary<string, ActivityList> _lists;
public ActivityRepository(string boardId)
{
_boardId = boardId;
Initialize();
}
private void Initialize()
{
ChelloClient client = TrelloClientFactory.Get();
_lists = client.Lists.ForBoard(_boardId).ToDictionary(l => l.Id, l => new ActivityList(l.Id, l.Name));
IEnumerable<CardUpdateAction> updates = new CardUpdateAction[0];
int pageNumber = 0;
int perPage = 1000;
int lastPageCount = 0;
while (pageNumber == 0 || lastPageCount == perPage)
{
var updatesPage = client.CardUpdates.ForBoard(_boardId, new { limit = 1000, page = pageNumber, filter = "createCard,updateCard,moveCardFromBoard,moveCardToBoard,updateBoard,createBoard" });
pageNumber++;
lastPageCount = updatesPage.Count();
updates = updates.Concat(updatesPage);
}
// HACK: Board closing doesn't come back properly - assume if last activity was a board update it is board closing
// may give strange results for some cases
if (updates.FirstOrDefault().Type.Equals("updateBoard", StringComparison.OrdinalIgnoreCase))
{
BoardClosed = DateTime.Parse(updates.FirstOrDefault().Date);
}
foreach (var update in updates.Reverse())
{
switch (update.Type.ToUpperInvariant())
{
case "CREATECARD":
_lists.AddCard(update.Data.List.Id, new ActivityCard(update.Data.Card, DateTime.Parse(update.Date)));
break;
case "UPDATECARD":
if (update.Data.ListAfter != null && update.Data.ListBefore != null)
{
_lists.FinishCard(update.Data.ListBefore.Id, update.Data.Card, DateTime.Parse(update.Date));
_lists.AddCard(update.Data.ListAfter.Id, new ActivityCard(update.Data.Card, DateTime.Parse(update.Date)));
}
else if (update.Data.Card.Closed)
{
foreach (string listId in _lists.Keys)
{
_lists.FinishCard(listId, update.Data.Card, DateTime.Parse(update.Date));
}
}
break;
case "MOVECARDFROMBOARD":
foreach (string listId in _lists.Keys)
{
_lists.FinishCard(listId, update.Data.Card, DateTime.Parse(update.Date));
}
break;
case "CREATEBOARD":
BoardOpened = DateTime.Parse(update.Date);
break;
}
if (update.Data.Card != null)
{
foreach (string listId in _lists.Keys)
{
_lists.UpdatePoints(listId, update.Data.Card);
}
}
}
}
public DateTime BoardOpened { get; private set; }
public DateTime? BoardClosed { get; private set; }
public IEnumerable<ActivityList> Lists
{
get
{
return _lists.Values.ToArray();
}
}
public class ActivityList
{
public ActivityList(string id, string name)
{
Id = id;
Name = name;
}
public string Id { get; private set; }
public string Name { get; private set; }
private IDictionary<string, IList<ActivityCard>> _cards = new Dictionary<string, IList<ActivityCard>>();
public IEnumerable<ActivityCard> Cards
{
get
{
return _cards.Values.SelectMany(c => c).ToArray();
}
}
public void AddCard(ActivityCard card)
{
if (_cards.ContainsKey(card.Id))
{
_cards[card.Id].Add(card);
}
else
{
IList<ActivityCard> newCards = new List<ActivityCard>();
newCards.Add(card);
_cards.Add(card.Id, newCards);
}
}
public void FinishCard(string cardId, DateTime endDate)
{
if (_cards.Keys.Contains(cardId))
{
foreach (var card in _cards[cardId])
{
if (!card.EndDate.HasValue)
{
card.EndCard(endDate);
}
}
}
}
public void UpdatePoints(string cardId, int? updatedPoints)
{
if (_cards.Keys.Contains(cardId))
{
foreach (var card in _cards[cardId])
{
card.UpdatePoints(updatedPoints);
}
}
}
}
public class ActivityCard
{
public ActivityCard(Card card, DateTime startDate)
: this(card.Id, startDate, card.HasPoints() ? (int?)card.Points() : null)
{
}
public ActivityCard(string id, DateTime startDate, int? points = null)
{
Id = id;
StartDate = startDate;
Points = points;
}
public string Id { get; private set; }
public DateTime StartDate { get; private set; }
public DateTime? EndDate { get; private set; }
public int? Points { get; private set; }
public void UpdatePoints(int? points)
{
Points = points;
}
public void EndCard(DateTime endDate)
{
EndDate = endDate;
}
}
}
public static class ActivityExtensions
{
public static void AddCard(this IDictionary<string, ActivityRepository.ActivityList> lists, string listId, ActivityRepository.ActivityCard card)
{
if (lists.ContainsKey(listId))
{
lists[listId].AddCard(card);
}
}
public static void FinishCard(this IDictionary<string, ActivityRepository.ActivityList> lists, string listId, Card card, DateTime endDate)
{
if (lists.ContainsKey(listId))
{
lists[listId].FinishCard(card.Id, endDate);
}
}
public static void UpdatePoints(this IDictionary<string, ActivityRepository.ActivityList> lists, string listId, Card card)
{
if (lists.ContainsKey(listId))
{
lists[listId].UpdatePoints(card.Id, card.HasPoints() ? (int?)card.Points() : null);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using TrelloCFD.Domain;
namespace TrelloCFD.Models
{
public class CumulativeFlowModel
{
private ActivityRepository _repository;
private DateTime _beginTime;
private DateTime _endTime;
private Func<IEnumerable<ActivityRepository.ActivityCard>, int> _calculation;
private string _title;
public CumulativeFlowModel(ActivityRepository repository)
: this(repository, (cards) => { return cards.Count(); }, "Cards")
{
}
public CumulativeFlowModel(ActivityRepository repository, Func<IEnumerable<ActivityRepository.ActivityCard>, int> calculation, string title)
{
_repository = repository;
_beginTime = repository.BoardOpened;
_endTime = repository.BoardClosed.HasValue ? repository.BoardClosed.Value : DateTime.UtcNow;
_calculation = calculation;
_title = title;
}
public string Title
{
get { return _title; }
}
public IEnumerable<DateTime> Periods
{
get
{
TimeSpan span = _endTime - _beginTime;
double minutesPerPeriod = span.TotalMinutes / 15;
DateTime current = _beginTime;
while (current < _endTime)
{
yield return current;
current = current.AddMinutes(minutesPerPeriod);
}
yield return _endTime;
}
}
public IEnumerable<ListData> Lists
{
get
{
DateTime[] periods = Periods.ToArray();
foreach (var list in _repository.Lists)
{
IList<int> counts = new List<int>();
for (int i = 0; i < periods.Count(); i++)
{
int count = _calculation(list.Cards.Where(c => periods[i] >= c.StartDate && c.EndDate.HasValue && periods[i] < c.EndDate.Value));
count += _calculation(list.Cards.Where(c => periods[i] >= c.StartDate && !c.EndDate.HasValue));
counts.Add(count);
}
yield return new ListData(list.Name, counts);
}
}
}
public class ListData
{
public ListData(string name, IEnumerable<int> counts)
{
Name = name;
Counts = counts;
}
public string Name { get; private set; }
public IEnumerable<int> Counts { get; private set; }
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment