public static void Main(string[] args) { TaiConfig config = new TaiConfig(@Grapple.GetThisFolder() + "tai.conf"); //muahahha anonymouse functions in c# !!! var options = new OptionSet() { { "iteration-report", "", _ => {SESSION_ACTION = TAI_COMMAND.REPORT;}}, { "auto-fill-time", "", _ => {SESSION_ACTION = TAI_COMMAND.BURNDOWN;}}, { "set-story-build", "", _ => {SESSION_ACTION = TAI_COMMAND.SETBUILDID;}}, { "create-task", "", _ => {SESSION_ACTION = TAI_COMMAND.CREATETASK;}}, { "?|h|help", "", _ => Echo.HelpText()}, {"api-url=", "", url => {config["apiUrl"] = url;}}, {"project-id=", "", proj => {config["projectId"] = proj;}}, {"target-user="******"", user => {config["targetUser"] = user;}}, {"story-id=", "", storyId => {config["storyId"] = storyId;}}, {"build-id=", "", buildId => {config["buildId"] = buildId;}}, {"burndown-date=", "", date => {config["burndownDate"] = date;}}, {"email-greeting=", "", hi => {config["emailGreeting"] = hi;}}, {"email-signature=", "", me => {config["emailSignature"] = me;}}, {"iteration-number=", "", num => {config["iterationNumber"] = num;}}, {"status-report-names=", "", names => {config["statusReportNames"] = names;}}, //test these arrays, i will need to serialize them right {"task-name=|task-names=", "", names => {config["taskNames"] = names;}}, //test these arrays, i will need to serialize them right {"hours-per-day=", "", hours => {config["hoursPerDay"] = hours;}}, {"v=|verbosity=", "", noise => {Echo.LOG_LEVEL = Convert.ToByte(noise);}}, {"no-interaction", "", _ => {Grapple.isAllowingHumanInteraction = false;}}, /* i don't like the idea of passing credentials, however added it for completion */ {"username="******"", user => {config["username"] = user;}}, {"password="******"", pass => {config["password"] = pass;}} }; var badInput = options.Parse(args); if(badInput.Count > 0) { Echo.ErrorReport(badInput.ToArray()); Echo.OffensiveGesture(); Echo.HelpText(); } Echo.WelcomeText(); config = Grapple.TryGetCredentialsManually(config); ApiWrapper.Initialize(config); TaiTakeCareOfThis[SESSION_ACTION](config); Echo.Out("done", 2); }
public static void Main(string[] args) { TaiConfig config = new TaiConfig(@Grapple.GetThisFolder() + "tai.conf"); var options = new OptionSet() { { "get-iteration-report", "", _ => {SESSION_ACTION = TAI_COMMAND.GET_ITERATION_REPORT;}}, { "update-rally-time", "", _ => {SESSION_ACTION = TAI_COMMAND.BURNDOWN;}}, { "update-story-build", "", _ => {SESSION_ACTION = TAI_COMMAND.UPDATE_STORY_BUILDID;}}, { "insert-task", "", _ => {SESSION_ACTION = TAI_COMMAND.INSERT_TASK;}}, { "insert-my-tasks-for-story", "", _ => {SESSION_ACTION = TAI_COMMAND.INSERT_MY_STORY_TASKS;}}, { "insert-dev-lead-boilerplate", "", _ => {SESSION_ACTION = TAI_COMMAND.INSERT_DEV_LEAD_BOILERPLATE;}}, { "insert-qa-boilerplate", "", _ => {SESSION_ACTION = TAI_COMMAND.INSERT_QA_BOILERPLATE;}}, { "get-iteration", "", _ => {SESSION_ACTION = TAI_COMMAND.GET_ITERATION;}}, { "get-my-storys", "", _ => {SESSION_ACTION = TAI_COMMAND.GET_MY_STORYS;}}, { "get-my-tasks", "", _ => {SESSION_ACTION = TAI_COMMAND.GET_MY_TASKS;}}, { "get-team-storys", "", _ => {SESSION_ACTION = TAI_COMMAND.GET_TEAM_STORYS;}}, { "get-team-story-urls", "", _ => {SESSION_ACTION = TAI_COMMAND.GET_TEAM_STORY_URLS;}}, { "?|h|help", "", _ => {SESSION_ACTION = TAI_COMMAND.NONE;}}, {"no-interaction", "", _ => {Grapple.isAllowingHumanInteraction = false;}}, {"username="******"", user => {config["username"] = user;}}, {"password="******"", pass => {config["password"] = pass;}}, {"api-url=", "", url => {config["apiUrl"] = url;}}, {"project-id=", "", proj => {config["projectId"] = proj;}}, {"target-user="******"", user => {config["targetUser"] = user;}}, {"story-id=", "", storyId => {config["storyId"] = storyId;}}, {"build-id=", "", buildId => {config["buildId"] = buildId;}}, {"burndown-date=", "", date => {config["burndownDate"] = date;}}, {"email-greeting=", "", hi => {config["emailGreeting"] = hi;}}, {"email-signature=", "", me => {config["emailSignature"] = me;}}, {"iteration-number=", "", num => {config["iterationNumber"] = num;}}, {"hours-per-day=", "", hours => {config["hoursPerDay"] = hours;}}, {"description=", "", desc => {config["description"] = desc;}}, {"note=|notes=", "", note => {config["notes"] = note;}}, {"block=|blocked=", "", isblock => {config["isBlocked"] = isblock;}}, {"story-size=", "", size => {config["storySize"] = size;}}, {"estimate-hours=", "", hours => {config["estimateHours"] = hours;}}, {"task-state=", "", state => {config["taskState"] = state;}}, /* "Defined", "In-Progress", "Completed" */ {"task-name=", "", name => {config["taskName"] = name;}}, {"attachment-type=", "", type => {config["attatchmentType"] = type;}}, //take in the mime-type. type == "base64" indicates that tai does not need to encode it {"status-report-names=", "", names => {config["statusReportNames"] = names.Split(',').ToJson();}}, {"task-names=", "", names => {config["taskNames"] = names.Split(',').ToJson();}}, {"attachment=|attachments=", "", files => {config["attachments"] = files.Split(',').ToJson();}}, //can be the path or raw file, but all types must be the same for this up and comming version {"d=|delimiter=", "", sep => {Echo.DELIMITER = sep;}}, {"l=|log-level=", "", noise => {Echo.LOG_LEVEL = Convert.ToByte(noise);}}, /* later... get-tasks team-name= output-type= json csv none(same as log-level 0) cli(default) */ }; var badInput = options.Parse(args); Echo.WelcomeText(); if(badInput.Count > 0 || SESSION_ACTION == TAI_COMMAND.NONE) { Echo.ErrorReport(badInput.ToArray()); Echo.OffensiveGesture(); Echo.HelpText(options); } config = Grapple.TryGetCredentialsManually(config); ApiWrapper.Initialize(config); TaiTakeCareOfThis[SESSION_ACTION](config); Echo.Out("done", 5); }
private static StringBuilder GenerateMicrosoftHtmlTabularReport(List<JToken> storys, TaiConfig config) { config = SetRequiredProperties(config, "emailGreeting", "emailSignature"); var emailBody = new StringBuilder(); var headerRow = string.Format("<tr><td width=125 valign=top style='width:93.5pt;border:solid windowtext 1.0pt;padding:0in 5.4pt 0in 5.4pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{0}<o:p></o:p></p></td><td width=125 valign=top style='width:93.5pt;border:solid windowtext 1.0pt;border-left:none;padding:0in 5.4pt 0in 5.4pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{1}<o:p></o:p></p></td><td width=125 valign=top style='width:93.5pt;border:solid windowtext 1.0pt;border-left:none;padding:0in 5.4pt 0in 5.4pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{2}<o:p></o:p></p></td><td width=125 valign=top style='width:93.5pt;border:solid windowtext 1.0pt;border-left:none;padding:0in 5.4pt 0in 5.4pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{3}<o:p></o:p></p></td><td width=125 valign=top style='width:93.5pt;border:solid windowtext 1.0pt;border-left:none;padding:0in 5.4pt 0in 5.4pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{4}<o:p></o:p></p></td></tr>", "Person", "Story", "Finished On", "Status", "Risks" ); emailBody.Append(string.Format("<p>{0},</p><br>Here is the latest status report.<br><br>", config["emailGreeting"])); emailBody.Append("<table class=MsoTableGrid border=1 cellspacing=0 cellpadding=0 align=left style='border-collapse:collapse;border:none;margin-left:6.75pt;margin-right:6.75pt'>"); emailBody.Append(headerRow); foreach (JToken story in storys) { var hero = story.Value<string>("HeroOfTime"); var name = story.Value<string>("Name"); var risk = story.Value<string>("BlockedReason"); var status = story.Value<string>("ScheduleState"); var date = story.Value<string>("AcceptedDate").GetPrettyDate(); var dataRow = string.Format("<tr style='height:14.35pt'><td width=125 valign=top style='width:93.5pt;border:solid windowtext 1.0pt;border-top:none;padding:0in 5.4pt 0in 5.4pt;height:14.35pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{0}<o:p></o:p></p></td><td width=125 valign=top style='width:93.5pt;border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0in 5.4pt 0in 5.4pt;height:14.35pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{1}<o:p></o:p></p></td><td width=125 valign=top style='width:93.5pt;border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0in 5.4pt 0in 5.4pt;height:14.35pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{2}<o:p></o:p></p></td><td width=125 valign=top style='width:93.5pt;border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0in 5.4pt 0in 5.4pt;height:14.35pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{3}<o:p></o:p></p></td><td width=125 valign=top style='width:93.5pt;border-top:none;border-left:none;border-bottom:solid windowtext 1.0pt;border-right:solid windowtext 1.0pt;padding:0in 5.4pt 0in 5.4pt;height:14.35pt'><p class=MsoNormal style='mso-element:frame;mso-element-frame-hspace:9.0pt;mso-element-wrap:around;mso-element-anchor-vertical:paragraph;mso-element-anchor-horizontal:margin;mso-element-top:-.65pt;mso-height-rule:exactly'>{4}<o:p></o:p></p></td></tr>", hero, name, date, status, risk ); emailBody.Append(dataRow); } emailBody.Append("</table>"); var signature = string.Format("<br><br><p>{0},</p><br><p>-{1}</p>", GetFarewell(), config["emailSignature"]); emailBody.Append(signature); return emailBody; }
private static TaiConfig SetRequiredProperties(TaiConfig conf, params string[] requiredProperties) { //BEWARE: default properties are processed in the order they are given and may have dependecies on another property! var defaults = new Dictionary<string, Func<string, string>> () { {"targetUser", val => {return val ?? conf["username"];}}, {"storyId", val => {return val ?? "US00000";}}, //todo: get most recent story by latest task update/modified {"taskName", val => {return val ?? "new task";}}, {"storySize", val => {return val ?? StorySize.digitToLetter[ApiWrapper.GetStorySize(conf["storyId"])];}}, {"estimateHours", val => {return val ?? StorySize.preferredEstimates[conf["storySize"]].ToString();}}, {"taskState", val => {return val ?? "Defined";}}, {"projectId", val => {return val ?? ApiWrapper.GetProjectId(conf["targetUser"]);}}, {"iterationNumber", val => {return val ?? ApiWrapper.GetIterationNumber(conf["projectId"]);}}, {"burndownDate", val => {return val ?? DateTime.Today.ToString("yyyy-MM-dd");}}, {"hoursPerDay", val => {return val ?? "8";}}, {"taskNames", val => {return val ?? "Administration,Regression,Iteration Planning,Deployment Planning,Environment Issue,User Stories".Split(',').ToJson();}}, {"emailGreeting", val => {return val ?? "Hi Boss";}}, {"emailSignature", val => {return val ?? "dev team";}}, {"humanName", val => {return val ?? ApiWrapper.GetTargetUserHumanName(conf["targetUser"]);}}, {"statusReportNames", val => { if(val != null) { return val;} var teams = ApiWrapper.GetTeamMembers(conf["projectId"]); List<string> statusReportNames = new List<string>(); foreach(JToken member in teams){ statusReportNames.Add(member.Value<string>("DisplayName"));} return statusReportNames.ToArray().ToJson(); }} }; foreach(string property in requiredProperties) { conf[property] = defaults[property](conf[property]);} return conf; }
private static void CreateEmptyTimeCardForTasks(List<Task> tasks, TaiConfig config) { var target_date = DateTime.Parse(config["burndownDate"]); var week_begin_date = target_date.AddDays(-(int)target_date.DayOfWeek); var my_id = ApiWrapper.GetTargetUserObjectId(config["targetUser"]); foreach(Task task in tasks) { if(!task.weeklyTime.ContainsKey(week_begin_date.Date)) { /* this can and will create an invisible time item for tasks that don't belong to this week. no damage will be done */ task.weeklyTime.Add( week_begin_date.Date, ApiWrapper.CreateNewTimeEntryItem(config["projectId"], my_id, task.taskObjectId, week_begin_date) ); } } }
private static StringBuilder GenerateGenerictHtmlTabularReport(List<JToken> storys, TaiConfig config) { config = SetRequiredProperties(config, "emailGreeting", "emailSignature"); var emailBody = new StringBuilder(); var headerRow = string.Format("<tr><td><p>{0}</p></td> <td><p>{1}</p></td> <td><p>{2}</p></td> <td><p>{3}</p></td> <td><p>{4}</p></td></tr>", "Person", "Story", "Finished On", "Status", "Risks" ); emailBody.Append(string.Format("<p>{0},</p><br>Here is the latest status report.<br><br>", config["emailGreeting"])); emailBody.Append("<table>"); emailBody.Append(headerRow); foreach (JToken story in storys) { var hero = story.Value<string>("HeroOfTime"); var name = story.Value<string>("Name"); var risk = story.Value<string>("BlockedReason"); var status = story.Value<string>("ScheduleState"); var date = story.Value<string>("AcceptedDate").GetPrettyDate(); var dataRow = string.Format("<tr><td><p>{0}</p></td> <td><p>{1}</p></td> <td><p>{2}</p></td> <td><p>{3}</p></td> <td><p>{4}</p></td></tr>", hero, name, date, status, risk ); emailBody.Append(dataRow); } emailBody.Append("</table>"); var signature = string.Format("<br><br><p>{0},</p><br><p>-{1}</p>", GetFarewell(), config["emailSignature"]); emailBody.Append(signature); return emailBody; }
internal static void AutomaticallyFillTaskTime(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "projectId", "burndownDate", "hoursPerDay", "taskNames"); var target_date = DateTime.Parse(config["burndownDate"]); var week_begin_date = target_date.AddDays(-(int)target_date.DayOfWeek); var my_id = ApiWrapper.GetTargetUserObjectId(config["targetUser"]); var tasks = ApiWrapper.GetTasks(my_id, week_begin_date); CreateEmptyTimeCardForTasks(tasks, config); int daily_min = Convert.ToInt32(config["hoursPerDay"]); int[] total_hours = SumTaskHoursByWeekStart(tasks, week_begin_date); var priority_chart = BuildTaskPriorityChart(tasks, config); int required_hours_this_week = (int)target_date.DayOfWeek * daily_min; Echo.Out(string.Format("burn down hours from {0} to {1} as role: {2}", week_begin_date.ToString("MMMM dd yyyy"), target_date.ToString("MMMM dd yyyy"), "dev"), 2); Echo.Out(my_id + " is my ObjectID", 5); Echo.Out(tasks.Count + " tasks since "+week_begin_date.ToString("yyyy MM dd"), 5); Echo.Out(". . . . . . . . . . .", 5); Echo.TaskReport(tasks, week_begin_date); Echo.Out("min hours needed to pass inspection: " + required_hours_this_week, 2); Echo.Out("You have " + total_hours.Sum() + " hours", 2); if (total_hours.Sum() < required_hours_this_week) { var workload = new List<JObject>(); for(int i = 0; i<total_hours.Length; ++i) { int this_days_time = total_hours[i]; string day_name = Enum.GetName(typeof(DayOfWeek), i); int daily_time_needed = daily_min - this_days_time; //this allows for overages since we are adding in increments of 8. should only allow for a maximum of (inspection_amount + increment-1) if(total_hours.Sum() < required_hours_this_week && daily_time_needed > 0 /**/ && i!=0 && i !=6) { //hard code ignore of sunday and saturaday for now Echo.Out(day_name + " FAILED inspection.", 2); int hoursToAdd = (int)Math.Ceiling(((double)daily_time_needed / (double)priority_chart.Count)); foreach(KeyValuePair<string, Task> priority in priority_chart) { var task = priority.Value; DateTime post_date = week_begin_date.AddDays(i); JObject newTimeEntryValuePost = new JObject(); if(task.weeklyTime[week_begin_date].dailyTime.ContainsKey(post_date)) { newTimeEntryValuePost["Verb"] = "update"; newTimeEntryValuePost["ObjectID"] = task.weeklyTime[week_begin_date].dailyTime[post_date].Value<string>("ObjectID"); newTimeEntryValuePost["Hours"] = task.weeklyTime[week_begin_date].dailyTime[post_date].Value<int>("Hours") + hoursToAdd; }else{ newTimeEntryValuePost["Verb"] = "insert"; newTimeEntryValuePost["DateVal"] = post_date.ToString("yyyy-MM-ddTHH:mm:ssZ"); newTimeEntryValuePost["TimeEntryItem"] = task.weeklyTime[week_begin_date].timeEntryObjectId; newTimeEntryValuePost["Hours"] = hoursToAdd; } Echo.Out(newTimeEntryValuePost.ToString(), 6); Echo.Out("..........", 6); workload.Add(newTimeEntryValuePost); total_hours[i] += hoursToAdd; daily_time_needed -= hoursToAdd; if(daily_time_needed <= 0){break;} } }else{ Echo.Out(day_name + " PASSED inspection", 2); } } ApiWrapper.SubmitTaskTimeValue(workload); }else { Echo.Out("Autofilling is not needed since you have already filled out the minimum necessary time", 2); } } internal static void CreateDevLeadBoilerplate(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "projectId", "iterationNumber", "taskState"); List<JToken> iterations = ApiWrapper.GetIteration(config["projectId"]); List<JToken> storys = ApiWrapper.GetUserStories(config["projectId"], iterations); string[] devTaskNames = new string[] { (new Random(RandomSeed.seed).Next(0, 2) == 0 ? "Code Review" : "Harsh Code Review"), }; foreach(JToken story in storys) { foreach(string logicalName in devTaskNames) { JObject newTask = new JObject(); newTask["Name"] = logicalName; newTask["Description"] = config["description"] ?? ""; newTask["Notes"] = config["notes"] ?? ""; newTask["Owner"] = ApiWrapper.GetTargetUserObjectId(config["targetUser"]); newTask["Estimate"] = 1; newTask["State"] = config["taskState"]; newTask["TaskIndex"] = 1; newTask["WorkProduct"] = story.Value<string>("ObjectID"); JToken createResult = ApiWrapper.CreateNewTask(newTask); Echo.Out(newTask.ToString(Newtonsoft.Json.Formatting.Indented), 3); Echo.Out(createResult.ToString(Newtonsoft.Json.Formatting.None), 9); Echo.Out((string)createResult["CreateResult"]["Object"]["ObjectID"], 1); } } } internal static void CreateMyStoryTasks(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "storyId", "storySize", "estimateHours", "taskState"); JToken story = ApiWrapper.GetUserStory(config["storyId"]); string[] taskNames = GetLogicalTaskNamesForStory(story.Value<string>("Description")); var splitEstimate = Math.Floor(Convert.ToDecimal(Convert.ToInt32(config["estimateHours"]) / taskNames.Length)); var targetUserObjectId = ApiWrapper.GetTargetUserObjectId(config["targetUser"]); foreach(string logicalName in taskNames) { JObject newTask = new JObject(); newTask["Name"] = logicalName; newTask["Description"] = config["description"] ?? ""; newTask["Notes"] = config["notes"] ?? ""; newTask["Owner"] = targetUserObjectId; newTask["Estimate"] = splitEstimate; newTask["State"] = config["taskState"]; newTask["TaskIndex"] = 1; newTask["WorkProduct"] = story.Value<string>("ObjectID"); JToken createResult = ApiWrapper.CreateNewTask(newTask); Echo.Out(newTask.ToString(Newtonsoft.Json.Formatting.Indented), 3); Echo.Out(createResult.ToString(Newtonsoft.Json.Formatting.None), 9); Echo.Out((string)createResult["CreateResult"]["Object"]["ObjectID"], 1); } } internal static void CreateQABoilerplate(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "projectId", "iterationNumber", "taskState"); List<JToken> iterations = ApiWrapper.GetIteration(config["projectId"]); List<JToken> storys = ApiWrapper.GetUserStories(config["projectId"], iterations); string[] qaTaskNames = new string[] { "QA User Story Analysis", "QA Test Case creation", "QA Test Case Execution", }; var targetUserObjectId = ApiWrapper.GetTargetUserObjectId(config["targetUser"]); foreach(JToken story in storys) { foreach(string logicalName in qaTaskNames) { JObject newTask = new JObject(); newTask["Name"] = logicalName; newTask["Description"] = config["description"] ?? ""; newTask["Notes"] = config["notes"] ?? ""; newTask["Owner"] = targetUserObjectId; newTask["Estimate"] = 1; //hardcode 1 on purpose newTask["State"] = config["taskState"]; newTask["TaskIndex"] = 1; newTask["WorkProduct"] = story.Value<string>("ObjectID"); JToken createResult = ApiWrapper.CreateNewTask(newTask); Echo.Out(newTask.ToString(Newtonsoft.Json.Formatting.Indented), 3); Echo.Out(createResult.ToString(Newtonsoft.Json.Formatting.None), 9); Echo.Out((string)createResult["CreateResult"]["Object"]["ObjectID"], 1); } } } internal static void CreateTaskForStory(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "storyId", "taskName", "estimateHours", "taskState"); JObject newTask = new JObject(); newTask["Name"] = config["taskName"];//just create one for now. later iterate over these and create one for each name newTask["Description"] = config["description"] ?? ""; newTask["Notes"] = config["notes"] ?? ""; newTask["Owner"] = ApiWrapper.GetTargetUserObjectId(config["targetUser"]); newTask["Estimate"] = config["estimateHours"]; newTask["State"] = config["taskState"]; newTask["TaskIndex"] = 1; newTask["WorkProduct"] = ApiWrapper.GetUserStory(config["storyId"]).Value<string>("ObjectID"); JToken createResult = ApiWrapper.CreateNewTask(newTask); Echo.Out(newTask.ToString(Newtonsoft.Json.Formatting.Indented), 3); Echo.Out(createResult.ToString(Newtonsoft.Json.Formatting.None), 9); Echo.Out((string)createResult["CreateResult"]["Object"]["ObjectID"], 1); } internal static void GetCurrentIterationNumber(TaiConfig config) { config = SetRequiredProperties(config, "targetUser"); var project_id = ApiWrapper.GetProjectId(config["targetUser"]); Echo.Out(ApiWrapper.GetIterationNumber(project_id), 1); } internal static void GetStorysForUser(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "projectId", "humanName"); var humanName = config["humanName"]; var iterations = ApiWrapper.GetIteration(config["projectId"]); var storys = ApiWrapper.GetUserStories(config["projectId"], iterations); var tasks = ApiWrapper.GetTasks(storys); storys = AssignTaskMastersToStorys(storys, tasks, new string[]{humanName}); var sorted = GetStorysSortedByProgrammer(storys); if(sorted.ContainsKey(humanName)){ var sb = new StringBuilder(); sb.AppendFormat("{0} has {1} stories assigned. \n", humanName, sorted[humanName].Count ); foreach(JToken story in sorted[humanName]) { var cleanedCriteria = story.Value<string>("c_AcceptanceCriteria"); cleanedCriteria = Regex.Replace(cleanedCriteria, @"(<br />)", "\n"); cleanedCriteria = Regex.Replace(cleanedCriteria, @"(<div>)|(</\w+>)",""); cleanedCriteria = Regex.Replace(cleanedCriteria, @"(<ul><li>)|(<ol><li>)|(<li>)", "\n-"); sb.AppendFormat(@" _________________________________________________________________________ |{0}: {1} | |justification: {2} | |criteria: {3} | |expert(s): {4} | |link: {5} __________________________________________________________________________ ", story.Value<string>("FormattedID"), story.Value<string>("Name"), story.Value<string>("c_Benefit"), cleanedCriteria, story.Value<string>("c_ResponsibleParty"), story.Value<string>("_ref")); } Echo.Out(sb.ToString(), 1); } } internal static void GetTeamStoryIds(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "projectId", "iterationNumber"); var iterations = ApiWrapper.GetIteration(config["projectId"], config["iterationNumber"]); var storys = ApiWrapper.GetUserStories(config["projectId"], iterations); var sb = new StringBuilder(); foreach(JToken story in storys) { sb.AppendFormat("{0}{1}", story.Value<string>("FormattedID"), Echo.DELIMITER);} Echo.Out(sb.ToString().Substring(0, sb.Length-Echo.DELIMITER.Length), 1); } internal static void GetTeamStoryUrls(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "projectId", "iterationNumber"); var iterations = ApiWrapper.GetIteration(config["projectId"], config["iterationNumber"]); var storys = ApiWrapper.GetUserStories(config["projectId"], iterations); var sb = new StringBuilder(); foreach(JToken story in storys) { sb.AppendFormat("{0}{1}", story.Value<string>("_ref"), Echo.DELIMITER);} Echo.Out(sb.ToString().Substring(0, sb.Length-Echo.DELIMITER.Length), 1); } internal static void SetStoryBuildId(TaiConfig config) { if(config.ContainsKey("storyId") && config.ContainsKey("buildId")) { Echo.Out("User story ID: " + config["storyId"], 5); Echo.Out("Build ID: " + config["buildId"], 5); Echo.Out("..........", 5); string UserStoryURL = ApiWrapper.GetUserStoryReferenceUrl(config["storyId"]); JObject postJson = new JObject(); postJson["c_BuildID"] = config["buildId"]; ApiWrapper.UpdateStoryBuildId(postJson, UserStoryURL); Echo.Out("success", 1); }else{ Echo.Out("you must have both a storyId and buildId in order to perform action: SetStoryBuildId", 1); } } internal static void WriteStatusReportForAnIteration(TaiConfig config) { config = SetRequiredProperties(config, "targetUser", "projectId", "iterationNumber", "statusReportNames"); var team = ApiWrapper.GetTeamMembers(config["projectId"]); var iterations = ApiWrapper.GetIteration(config["projectId"], config["iterationNumber"]); var storys = ApiWrapper.GetUserStories(config["projectId"], iterations); var tasks = ApiWrapper.GetTasks(storys); storys = AssignTaskMastersToStorys(storys, tasks, JArray.Parse(config["statusReportNames"]).ToArray()); var emailBody = GenerateMicrosoftHtmlTabularReport(storys, config); System.IO.File.WriteAllText(Grapple.GetThisFolder() + "email_body.txt", emailBody.ToString()); Echo.IterationReport(team, iterations, storys, tasks); } private static List<JToken> AssignTaskMastersToStorys(List<JToken> storys, List<JToken> tasks, string[] includeNames) { var timeTracker = new Dictionary<string, Dictionary<string, int>>(); /* <story guid, <username, time>> */ foreach(JToken task in tasks) { if(task["Owner"].Type == JTokenType.Null){ continue;} string user_name = task["Owner"].Value<string>("_refObjectName"); if (!user_name.Contains(includeNames)) { continue;} string story_guid = task["WorkProduct"].Value<string>("_refObjectUUID"); if (!timeTracker.ContainsKey(story_guid)) { timeTracker.Add(story_guid, new Dictionary<string, int>());} int estimate = task["Estimate"].Type != JTokenType.Null ? task.Value<int>("Estimate") : 0; if (timeTracker[story_guid].ContainsKey(user_name)){ timeTracker[story_guid][user_name] += estimate; }else { timeTracker[story_guid].Add(user_name, estimate); } } var mutated_storys = new List<JToken>(); foreach(JToken story in storys) { string story_guid = story.Value<string>("ObjectUUID"); story["HeroOfTime"] = "DreamTeam";//todo:un-hardcode use the discovered team name story["HeroValue"] = 0; if(timeTracker.ContainsKey(story_guid)) { foreach (KeyValuePair<string, int> dev_time in timeTracker[story_guid]) { if (dev_time.Value > story.Value<int>("HeroValue")) { story["HeroOfTime"] = dev_time.Key; story["HeroValue"] = dev_time.Value; } } } mutated_storys.Add(story); } return mutated_storys; }
private static Dictionary<string, Task> BuildTaskPriorityChart(List<Task> tasks, TaiConfig config) { string[] priority_strings = JArray.Parse(config["taskNames"]).ToArray(); var priority_chart = new Dictionary<string, Task>(); foreach(Task task in tasks) { string priority_name = task.taskName.ContainsMatch(priority_strings); if(priority_name != string.Empty) { if(!priority_chart.ContainsKey(priority_name)) { priority_chart.Add(priority_name, task); } } } return priority_chart; }
public static bool Initialize(TaiConfig config) { BASE_URL = config["apiUrl"] ?? BASE_URL; try{ CACHED_AUTH = GetCachedAuthentication(config["username"], config["password"]); }catch{ Console.WriteLine("No Internet Connection");//meah Environment.Exit(0); } return true; }
private static TaiConfig PrepareConfigForTaskTimeAutoFill(TaiConfig conf) { conf["targetUser"] = conf["targetUser"] ?? conf["username"]; conf["projectId"] = conf["projectId"] ?? ApiWrapper.GetProjectId(conf["targetUser"]); conf["burndownDate"] = conf["burndownDate"] ?? DateTime.Today.ToString("yyyy-MM-dd"); conf["hoursPerDay"] = conf["hoursPerDay"] ?? "8"; conf["taskNames"] = conf["taskNames"] ?? new string[]{"Administration", "Regression", "Iteration Planning", "Deployment Planning", "Environment Issue", "User Stories"}.ToJson(); return conf; }
private static TaiConfig PrepareConfigForTaskCreation(TaiConfig conf) { conf["targetUser"] = conf["targetUser"] ?? conf["username"]; conf["storyId"] = conf["storyId"] ?? "US00000"; conf["taskNames"] = conf["taskNames"] ?? "new task for" + conf["targetUser"]; //have a flag that auto gens task name based on story requirements. rexgex split a sentence or something return conf; }
private static TaiConfig FixMissingEmailProperties(TaiConfig conf) { conf["emailGreeting"] = conf["emailGreeting"] ?? "Hi Boss"; conf["emailSignature"] = conf["emailSignature"] ?? "-dev team"; return conf; }
internal static void AutomaticallyFillTaskTime(TaiConfig config) { config = PrepareConfigForTaskTimeAutoFill(config); var target_date = DateTime.Parse(config["burndownDate"]); var week_begin_date = target_date.AddDays(-(int)target_date.DayOfWeek); var my_id = ApiWrapper.GetTargetUserObjectId(config["targetUser"]); var tasks = ApiWrapper.GetTasks(my_id, week_begin_date); CreateEmptyTimeCardForTasks(tasks, config); int daily_min = Convert.ToInt32(config["hoursPerDay"]); int[] total_hours = SumTaskHoursByWeekStart(tasks, week_begin_date); var priority_chart = BuildTaskPriorityChart(tasks, config); int required_hours_this_week = (int)target_date.DayOfWeek * daily_min; Echo.Out(string.Format("burn down hours from {0} to {1} as role: {2}", week_begin_date.ToString("MMMM dd yyyy"), target_date.ToString("MMMM dd yyyy"), "dev"), 2); Echo.Out(my_id + " is my ObjectID", 5); Echo.Out(tasks.Count + " tasks since "+week_begin_date.ToString("yyyy MM dd"), 5); Echo.Out(". . . . . . . . . . .", 5); Echo.TaskReport(tasks, week_begin_date); Echo.Out("min hours needed to pass inspection: " + required_hours_this_week, 2); Echo.Out("You have " + total_hours.Sum() + " hours", 2); if (total_hours.Sum() < required_hours_this_week) { var workload = new List<JObject>(); for(int i = 0; i<total_hours.Length; ++i) { int this_days_time = total_hours[i]; string day_name = Enum.GetName(typeof(DayOfWeek), i); int daily_time_needed = daily_min - this_days_time; //this allows for overages since we are adding in increments of 8. should only allow for a maximum of (inspection_amount + increment-1) if(total_hours.Sum() < required_hours_this_week && daily_time_needed > 0 /**/ && i!=0 && i !=6) { //hard code ignore of sunday and saturaday for now Echo.Out(day_name + " FAILED inspection. Tai says, 'add some time m**********r!'", 5); int hoursToAdd = (int)Math.Ceiling(((double)daily_time_needed / (double)priority_chart.Count)); foreach(KeyValuePair<string, Task> priority in priority_chart) { var task = priority.Value; DateTime post_date = week_begin_date.AddDays(i); JObject newTimeEntryValuePost = new JObject(); if(task.weeklyTime[week_begin_date].dailyTime.ContainsKey(post_date)) { newTimeEntryValuePost["Verb"] = "update"; newTimeEntryValuePost["ObjectID"] = task.weeklyTime[week_begin_date].dailyTime[post_date].Value<string>("ObjectID"); newTimeEntryValuePost["Hours"] = task.weeklyTime[week_begin_date].dailyTime[post_date].Value<int>("Hours") + hoursToAdd; }else{ newTimeEntryValuePost["Verb"] = "insert"; newTimeEntryValuePost["DateVal"] = post_date.ToString("yyyy-MM-ddTHH:mm:ssZ"); newTimeEntryValuePost["TimeEntryItem"] = task.weeklyTime[week_begin_date].timeEntryObjectId; newTimeEntryValuePost["Hours"] = hoursToAdd; } Echo.Out(newTimeEntryValuePost.ToString(), 6); Echo.Out("..........", 6); workload.Add(newTimeEntryValuePost); total_hours[i] += hoursToAdd; daily_time_needed -= hoursToAdd; if(daily_time_needed <= 0){break;} } }else{ Echo.Out(day_name + " PASSED inspection"); } } ApiWrapper.SubmitTaskTimeValue(workload); }else { Echo.Out("Autofilling is not needed since you have already filled out the minimum necessary time"); } } internal static void CreateTaskForStory(TaiConfig config) { config = PrepareConfigForTaskCreation(config); JObject newTask = new JObject(); newTask["Name"] = config["taskNames"];//just create one for now. later iterate over these and create one for each name newTask["Description"] = ""; newTask["Owner"] = ApiWrapper.GetTargetUserObjectId(config["targetUser"]); newTask["Estimate"] = "10"; newTask["State"] = "Defined"; newTask["TaskIndex"] = 1; newTask["WorkProduct"] = ApiWrapper.GetUserStory(config["storyId"]).Value<string>("ObjectID"); Echo.Out(newTask.ToString(Newtonsoft.Json.Formatting.Indented), 1); Echo.Out(ApiWrapper.CreateNewTask(newTask).ToString(Newtonsoft.Json.Formatting.Indented)); } internal static void SetStoryBuildId(TaiConfig config) { if(config.ContainsKey("storyId") && config.ContainsKey("buildId")) { Echo.Out("User story ID: " + config["storyId"], 5); Echo.Out("Build ID: " + config["buildId"], 5); string UserStoryURL = ApiWrapper.GetUserStoryReferenceUrl(config["storyId"]); JObject postJson = new JObject(); postJson["c_BuildID"] = config["buildId"]; ApiWrapper.UpdateStoryBuildId(postJson, UserStoryURL); Echo.Out("..........", 5); }else{ Echo.Out("you must have both a storyId and buildId in order to perform action: SetStoryBuildId", 1); } } internal static void WriteStatusReportForAnIteration(TaiConfig config) { config["targetUser"] = config["targetUser"] ?? config["username"]; config["projectId"] = config["projectId"] ?? ApiWrapper.GetProjectId(config["targetUser"]); config["iterationNumber"] = config["iterationNumber"] ?? ApiWrapper.GetIterationNumber(config["projectId"]); var team = ApiWrapper.GetTeamMembers(config["projectId"]); var iterations = ApiWrapper.GetIteration(config["projectId"], config["iterationNumber"]); var storys = ApiWrapper.GetUserStories(config["projectId"], iterations); var tasks = ApiWrapper.GetTasks(storys); storys = AssignTaskMastersToStorys(storys, tasks, JArray.Parse(config["statusReportNames"]).ToArray()); var emailBody = GenerateMicrosoftHtmlTabularReport(storys, config); System.IO.File.WriteAllText(Grapple.GetThisFolder() + "email_body.txt", emailBody.ToString()); Echo.IterationReport(team, iterations, storys, tasks); } private static List<JToken> AssignTaskMastersToStorys(List<JToken> storys, List<JToken> tasks, string[] includeNames) { var timeTracker = new Dictionary<string, Dictionary<string, int>>(); /* <story guid, <username, time>> */ foreach(JToken task in tasks) { if(task["Owner"].Type == JTokenType.Null){ continue;} string user_name = task["Owner"].Value<string>("_refObjectName"); if (!user_name.Contains(includeNames)) { continue;} string story_guid = task["WorkProduct"].Value<string>("_refObjectUUID"); if (!timeTracker.ContainsKey(story_guid)) { timeTracker.Add(story_guid, new Dictionary<string, int>());} int estimate = task["Estimate"].Type != JTokenType.Null ? task.Value<int>("Estimate") : 0; if (timeTracker[story_guid].ContainsKey(user_name)){ timeTracker[story_guid][user_name] += estimate; }else { timeTracker[story_guid].Add(user_name, estimate); } } var mutated_storys = new List<JToken>(); foreach(JToken story in storys) { string story_guid = story.Value<string>("ObjectUUID"); story["HeroOfTime"] = "DreamTeam";//todo:un-hardcode use the discovered team name story["HeroValue"] = 0; if(timeTracker.ContainsKey(story_guid)) { foreach (KeyValuePair<string, int> dev_time in timeTracker[story_guid]) { if (dev_time.Value > story.Value<int>("HeroValue")) { story["HeroOfTime"] = dev_time.Key; story["HeroValue"] = dev_time.Value; } } } mutated_storys.Add(story); } return mutated_storys; }