public void IssuesTest() { var sprint = new ScrumSprint(); var lytRepos = new GitRepos(OAuthToken, ApiUrl, "Notalib", "LYT"); var lytSprint2Milestone = lytRepos.Milestones.FirstOrDefault(m => m.Title == "Sprint 2"); Assert.IsNotNull(lytSprint2Milestone); sprint.Milestones.Add(lytSprint2Milestone); var sprintIssues = new List<GitIssue>(sprint.Issues.OrderBy(i => i.Number)); var milestoneIssues = new List<GitIssue>(lytSprint2Milestone.Issues.OrderBy(i => i.Number)); Assert.AreEqual(sprintIssues.Count, milestoneIssues.Count); for (int i = 0; i < sprintIssues.Count; i++) Assert.AreEqual(sprintIssues[i].Number, milestoneIssues[i].Number); //var obRepos = new GitRepos(OAuthToken, ApiUrl, "Notalib", "Online-backend"); //var obSprint2Milestone = obRepos.Milestones.FirstOrDefault(m => m.Title == "Sprint 2"); //sprint.Milestones.Add(obSprint2Milestone); //var e17Repos = new GitRepos(OAuthToken, ApiUrl, "Notalib", "E17"); //var e17Sprint2Milestone = e17Repos.Milestones.FirstOrDefault(m => m.Title == "Sprint 2"); //sprint.Milestones.Add(e17Sprint2Milestone); //Assert.IsTrue(sprint.Issues.Count > milestoneIssues.Count); foreach(var i in sprint.Issues) { var points = i.GetScrumPoints(); Console.WriteLine("{0}\t{1:0}\t{2}\t{3:0.0}\t{4}", i.Repos.Name, i.Number, i.Title, points.HasValue ? points.Value : 0.0, i.IsOpen?"open":"closed"); } }
static XElement GetHtmlScrumBoardSection(ScrumSprint sprint, ScrumState state) { return new XElement( "div", new XAttribute("class", "scrum_board_section"), new XElement("h2", new XAttribute("id", sprint.States.IndexOf(state)), state.Name + " issues"), sprint.Issues.Where(i => state == sprint.GetStateOfIssue(i)).Select(GetHtmlScrumBoardIssue)); }
static XElement GetHtmlStatistics(ScrumSprint sprint) { return new XElement( "div", new XAttribute("class", "scrum_stats"), new XElement("h1", "Statistics"), new XElement("p", "Updated ", new XElement("strong", DateTime.Now.ToShortDateString(), " ", DateTime.Now.ToShortTimeString())), new XElement("p", "Open issues: ", new XElement("strong", sprint.Issues.Count(i => i.IsOpen))), new XElement("p", "Closed issues: ", new XElement("strong", sprint.Issues.Count(i => !i.IsOpen))), new XElement("p", "Points remaining: ", new XElement("strong", sprint.Points))); }
static XElement GetHtmlScrumBoard(ScrumSprint sprint) { return new XElement( "div", new XAttribute("class", "scrum_board"), new XElement("h1", "Scrum Board"), new XElement( "ul", sprint.States.Select(s => new XElement("li", new XElement("a", new XAttribute("href", "#"+sprint.States.IndexOf(s)), s.Name)))), sprint.States.Select(s => GetHtmlScrumBoardSection(sprint, s))); }
static XElement GetSprintHtml(ScrumSprint sprint, DateTime lastUpdated) { string cellWidthCss = ""; if (sprint.States.Count > 0) cellWidthCss = String.Format("width: {0}%;", 100 / sprint.States.Count); return new XElement( "html", new XElement( "head", new XElement("title", sprint.Name ?? "<sprint_name>"), new XElement( "style", "th {text-align: left;border: 1px solid black;margin: 2px;"+cellWidthCss+"} " + "td {vertical-align: top;border: 1px solid black;" + cellWidthCss + "} " + "div.scrum_board_issue {border-bottom: 1px solid black;margin: 2px;} " + "div.scrum_board_issue p,div.scrum_board_issue h3 {margin-top: 0px; margin-bottom: 0px;}" + "span.unestimated {background-color: yellow;}")), new XElement( "body", GetHtmlScrumBoard(sprint), GetHtmlStatistics(sprint, lastUpdated), GetHtmlScrumMilestioneList(sprint), new XElement( "div", new XAttribute("class", "nav"), new XElement( "a", new XAttribute("href", String.Format("{0}.htm?force_update=true", sprint.Identifier)), "Force reload")))); }
static XElement GetHtmlScrumMilestioneList(ScrumSprint sprint) { return new XElement( "div", new XAttribute("class", "milestone_list"), new XElement("h1", "Milestones"), new XElement( "ul", sprint.Milestones.Select( m => new XElement("li", String.Format("{0}/{1}/{2}: {3}", m.Repos.Organization, m.Repos.Name, m.Number, m.Title))))); }
static IEnumerable<XElement> GetHtmlScrumBoardIssues(ScrumSprint sprint, ScrumState state) { return sprint.Issues.Where(i => state == sprint.GetStateOfIssue(i)).Select(GetHtmlScrumBoardIssue); }
static XElement GetHtmlScrumBoard(ScrumSprint sprint) { return new XElement( "div", new XAttribute("class", "scrum_board"), new XElement("h1", sprint.Name+" Scrum Board"), new XElement( "table", new XElement( "tr", sprint.States.Select(s => new XElement( "th", String.Format( "{0} [{1} issues] [{2:0.0} points]", s.Name, sprint.Issues.Count(i => s == sprint.GetStateOfIssue(i)), sprint .Issues .Where(i => s == sprint.GetStateOfIssue(i)) .Select(i => i.GetScrumPoints()) .Select(p => p.HasValue?p.Value:0.0) .Aggregate(0.0, (t,n)=> t+n))))), new XElement( "tr", sprint.States.Select(s => new XElement("td", GetHtmlScrumBoardIssues(sprint, s)))))); }
static int Main(string[] args) { if (!ParseCommandLineArguments(args)) { Console.WriteLine(usage); return -1; } var sprint = new ScrumSprint(); sprint.Milestones.AddRange(GetMilestones()); sprint.Name = sprintName; Console.WriteLine("Estimated points remaining: {0}", sprint.Points); Console.WriteLine("Number of open issues: {0}", sprint.Issues.Count(i => i.IsOpen)); if (htmlOut!=null) { GetSprintHtml(sprint).Save(htmlOut); } Console.WriteLine(GetHtmlScrumBoard(sprint)); return 0; }
static XElement GetSprintHtml(ScrumSprint sprint) { return new XElement( "html", new XElement("head", new XElement("title", sprint.Name ?? "<sprint_name>")), new XElement( "body", GetHtmlStatistics(sprint), GetHtmlScrumBoard(sprint))); }
/// <summary> /// Loads a sprints from an xml document/element /// </summary> /// <param name="sprintElem">A <see cref="XElement"/> providing access to the xml document</param> /// <param name="token">A OAuth token providing access to the github api</param> /// <param name="apiUrl">The url of the github api</param> /// <returns>The loaded sprint - <c>null</c> if <paramref name="sprintElem"/> is <c>null</c></returns> public static ScrumSprint LoadSprint(XElement sprintElem, string token, string apiUrl) { if (sprintElem == null) return null; var milestoneRelUrls = sprintElem.Elements("Milestones").Elements("Milestone").Select(e => e.Value).Where( v => Regex.IsMatch(v, @"^[\w-]+/[\w-]+/\d+$")).ToList(); var repos = milestoneRelUrls .Select(mru => mru.Substring(0, mru.LastIndexOf('/'))) .Distinct() .Select(rru => new GitRepos(token, apiUrl, rru.Split('/')[0], rru.Split('/')[1])) .ToList(); var milestones = repos .SelectMany(r => r.Milestones) .Where(m => milestoneRelUrls.Contains(String.Format("{0}/{1}/{2}", m.Repos.Organization, m.Repos.Name, m.Number))).ToList(); var sprint = new ScrumSprint { Name = sprintElem.Attributes("name").Select(a => a.Value).FirstOrDefault() ?? "", Identifier = sprintElem.Attributes("identifier").Select(a => a.Value).FirstOrDefault() ?? "" }; sprint.Milestones.AddRange(milestones); sprint.SetStates( sprintElem.Elements("States").Elements("IntermediateStates").Elements("State").Select(e => e.Value).ToList(), sprintElem.Elements("States").Elements("OpenStateName").Select(e => e.Value).FirstOrDefault() ?? "", sprintElem.Elements("States").Elements("ClosedStateName").Select(e => e.Value).FirstOrDefault() ?? ""); return sprint; }