diff --git a/src/JiraTimeBot/JiraIntegration/JiraApi.cs b/src/JiraTimeBot/JiraIntegration/JiraApi.cs index 4c5f1ab..fad2782 100644 --- a/src/JiraTimeBot/JiraIntegration/JiraApi.cs +++ b/src/JiraTimeBot/JiraIntegration/JiraApi.cs @@ -127,7 +127,7 @@ namespace JiraTimeBot.JiraIntegration foreach (var workLog in userWorklogs) { var timeSpent = TimeSpan.FromSeconds(workLog.TimeSpentInSeconds); - var timeDiff = Math.Abs((timeSpent - taskTimeItem.Time).TotalMinutes); + var timeDiff = Math.Abs((timeSpent - taskTimeItem.TimeSpent).TotalMinutes); if (timeDiff > 1 || userWorklogs.Count > 1 || userWorklogs.First().Comment != comment) { if (timeDiff > 1) @@ -165,7 +165,7 @@ namespace JiraTimeBot.JiraIntegration if (!hasTodayWorklog) { - var timeSpentJira = $"{taskTimeItem.Time.TotalMinutes}m"; + var timeSpentJira = $"{taskTimeItem.TimeSpent.TotalMinutes}m"; Worklog workLogToAdd = new Worklog(timeSpentJira, date.Value, comment); if (!dummy) diff --git a/src/JiraTimeBot/JiraIntegration/JiraCommitEmulator.cs b/src/JiraTimeBot/JiraIntegration/JiraCommitEmulator.cs index db31fcf..bda9c93 100644 --- a/src/JiraTimeBot/JiraIntegration/JiraCommitEmulator.cs +++ b/src/JiraTimeBot/JiraIntegration/JiraCommitEmulator.cs @@ -5,7 +5,8 @@ using System.Threading; using Atlassian.Jira; using JiraTimeBot.Configuration; using JiraTimeBot.Mercurial; -using JiraTimeBot.Mercurial.Objects; +using JiraTimeBot.TaskTime.Objects; + namespace JiraTimeBot.JiraIntegration { @@ -20,19 +21,19 @@ namespace JiraTimeBot.JiraIntegration _jiraApi = jiraApi; } - public List GetMercurialLog(Settings settings, + public List GetMercurialLog(Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken)) { date = date.GetValueOrDefault(DateTime.Now.Date); - var workTasks = new List(); + var workTasks = new List(); var issues = _jiraApi.GetIssuesByJQL(settings.JiraQuery, settings, date, cancellationToken); foreach (var issue in issues) { if (cancellationToken.IsCancellationRequested) { - return new List(); + return new List(); } var comments = issue.GetCommentsAsync(cancellationToken).Result?.ToList() ?? new List(); @@ -43,25 +44,27 @@ namespace JiraTimeBot.JiraIntegration if (userComments.Any()) { - foreach (var comment in userComments) + foreach (var comment in userComments.OrderBy(f=>f.CreatedDate)) { - workTasks.Add(new MercurialCommitItem + workTasks.Add(new TaskTimeItem { + StartTime = issue.Updated.GetValueOrDefault(date.Value), Description = issue.Summary, - Time = issue.Updated.GetValueOrDefault(date.Value), Branch = issue.Key.Value, - FilesAffected = 1 + FilesAffected = 1, + Commits = 1 }); } } else { - workTasks.Add(new MercurialCommitItem + workTasks.Add(new TaskTimeItem { + StartTime = issue.Updated.GetValueOrDefault(date.Value), Description = issue.Summary, - Time = issue.Updated.GetValueOrDefault(date.Value), Branch = issue.Key.Value, - FilesAffected = 1 + FilesAffected = 1, + Commits = 1 }); } } diff --git a/src/JiraTimeBot/JiraTimeBot.csproj b/src/JiraTimeBot/JiraTimeBot.csproj index e1e7eb1..4675d8f 100644 --- a/src/JiraTimeBot/JiraTimeBot.csproj +++ b/src/JiraTimeBot/JiraTimeBot.csproj @@ -99,7 +99,6 @@ frmMain.cs - diff --git a/src/JiraTimeBot/Job.cs b/src/JiraTimeBot/Job.cs index 17bb22e..dfb5832 100644 --- a/src/JiraTimeBot/Job.cs +++ b/src/JiraTimeBot/Job.cs @@ -5,7 +5,6 @@ using System.Threading; using System.Threading.Tasks; using JiraTimeBot.Configuration; using JiraTimeBot.Mercurial; -using JiraTimeBot.Mercurial.Objects; using JiraTimeBot.TasksProcessors; using JiraTimeBot.TaskTime; using JiraTimeBot.TaskTime.Objects; @@ -79,7 +78,7 @@ namespace JiraTimeBot _log.Info("Использую Jira как источник информации."); mercurial = _mercurialProviders.JiraCommitEmulator; } - List commits = mercurial.GetMercurialLog(settings, realDate, cancellationToken); + List commits = mercurial.GetMercurialLog(settings, realDate, cancellationToken); List taskTimes = _taskTimeDiscoverer.CalculateTaskTime(commits, settings, cancellationToken); if (cancellationToken.IsCancellationRequested) diff --git a/src/JiraTimeBot/Mercurial/IMercurialLog.cs b/src/JiraTimeBot/Mercurial/IMercurialLog.cs index 1b91a89..1459319 100644 --- a/src/JiraTimeBot/Mercurial/IMercurialLog.cs +++ b/src/JiraTimeBot/Mercurial/IMercurialLog.cs @@ -2,12 +2,12 @@ using System.Collections.Generic; using System.Threading; using JiraTimeBot.Configuration; -using JiraTimeBot.Mercurial.Objects; +using JiraTimeBot.TaskTime.Objects; namespace JiraTimeBot.Mercurial { public interface IMercurialLog { - List GetMercurialLog(Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken)); + List GetMercurialLog(Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/JiraTimeBot/Mercurial/MercurialLog.cs b/src/JiraTimeBot/Mercurial/MercurialLog.cs index 52aa623..fab1bf2 100644 --- a/src/JiraTimeBot/Mercurial/MercurialLog.cs +++ b/src/JiraTimeBot/Mercurial/MercurialLog.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading; using JiraTimeBot.Configuration; using JiraTimeBot.Mercurial.Modifiers; -using JiraTimeBot.Mercurial.Objects; +using JiraTimeBot.TaskTime.Objects; using Mercurial; namespace JiraTimeBot.Mercurial @@ -23,23 +23,23 @@ namespace JiraTimeBot.Mercurial _commitSkipper = commitSkipper; _technicalInfoSkipper = technicalInfoSkipper; } - - public List GetMercurialLog(Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken)) + + public List GetMercurialLog(Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrEmpty(settings.RepositoryPath) || !Directory.Exists(settings.RepositoryPath)) { _log.Error("Папка с репо не сушествует."); - return new List(); + return new List(); } date = date.GetValueOrDefault(DateTime.Now.Date); - var workTasks = new List(); + var workTasks = new List(); foreach (var repoDirectory in Directory.GetDirectories(settings.RepositoryPath)) { if (cancellationToken.IsCancellationRequested) { - return new List(); + return new List(); } //var project = new DirectoryInfo(repoDirectory).Name; @@ -62,7 +62,7 @@ namespace JiraTimeBot.Mercurial { if (cancellationToken.IsCancellationRequested) { - return new List(); + return new List(); } var commitMessage = FixEncoding(changeset.CommitMessage); @@ -72,24 +72,25 @@ namespace JiraTimeBot.Mercurial } commitMessage = _technicalInfoSkipper.StripTechnicalInfo(commitMessage); - workTasks.Add(new MercurialCommitItem + workTasks.Add(new TaskTimeItem { + StartTime = changeset.Timestamp, Description = commitMessage, Branch = changeset.Branch, - Time = changeset.Timestamp, - FilesAffected = changeset.PathActions.Count + FilesAffected = changeset.PathActions.Count, + Commits = 1, + TimeSpent = TimeSpan.Zero }); _log?.Trace($" - Найден changeset: {changeset.Timestamp} - {changeset.Branch} - {changeset.AuthorEmailAddress} - {commitMessage}"); } } if (!workTasks.Any()) { - return new List(); + return new List(); } return workTasks; } - private string FixEncoding(string source) { diff --git a/src/JiraTimeBot/Mercurial/Objects/MercurialCommitItem.cs b/src/JiraTimeBot/Mercurial/Objects/MercurialCommitItem.cs deleted file mode 100644 index 0ff8f0f..0000000 --- a/src/JiraTimeBot/Mercurial/Objects/MercurialCommitItem.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace JiraTimeBot.Mercurial.Objects -{ - public class MercurialCommitItem - { - public string Description { get; set; } - public string Branch { get; set; } - public DateTime Time { get; set; } - public int FilesAffected { get; set; } - } -} diff --git a/src/JiraTimeBot/TaskTime/ITaskTimeCalculator.cs b/src/JiraTimeBot/TaskTime/ITaskTimeCalculator.cs index a17b6aa..d2c512a 100644 --- a/src/JiraTimeBot/TaskTime/ITaskTimeCalculator.cs +++ b/src/JiraTimeBot/TaskTime/ITaskTimeCalculator.cs @@ -1,13 +1,13 @@ using System.Collections.Generic; using System.Threading; using JiraTimeBot.Configuration; -using JiraTimeBot.Mercurial.Objects; + using JiraTimeBot.TaskTime.Objects; namespace JiraTimeBot.TaskTime { public interface ITaskTimeCalculator { - List CalculateTaskTime(List commitItems, Settings settings, CancellationToken cancellationToken = default(CancellationToken)); + List CalculateTaskTime(List commitItems, Settings settings, CancellationToken cancellationToken = default(CancellationToken)); } } \ No newline at end of file diff --git a/src/JiraTimeBot/TaskTime/Objects/TaskTimeItem.cs b/src/JiraTimeBot/TaskTime/Objects/TaskTimeItem.cs index 8c8a452..dd91adf 100644 --- a/src/JiraTimeBot/TaskTime/Objects/TaskTimeItem.cs +++ b/src/JiraTimeBot/TaskTime/Objects/TaskTimeItem.cs @@ -6,10 +6,9 @@ namespace JiraTimeBot.TaskTime.Objects { public string Description { get; set; } public string Branch { get; set; } - public TimeSpan Time { get; set; } - public int Commits { get; set; } - public DateTime StartTime { get; set; } - public DateTime EndTime { get; set; } + public TimeSpan TimeSpent { get; set; } + public int Commits { get; set; } + public int FilesAffected { get; set; } } } diff --git a/src/JiraTimeBot/TaskTime/TaskTimeByCommitsCalculator.cs b/src/JiraTimeBot/TaskTime/TaskTimeByCommitsCalculator.cs index f517aa1..de1558b 100644 --- a/src/JiraTimeBot/TaskTime/TaskTimeByCommitsCalculator.cs +++ b/src/JiraTimeBot/TaskTime/TaskTimeByCommitsCalculator.cs @@ -4,7 +4,7 @@ using System.Linq; using System.Text; using System.Threading; using JiraTimeBot.Configuration; -using JiraTimeBot.Mercurial.Objects; + using JiraTimeBot.TaskTime.Objects; namespace JiraTimeBot.TaskTime @@ -18,96 +18,119 @@ namespace JiraTimeBot.TaskTime _log = log; } - public List CalculateTaskTime(List commits, Settings settings, CancellationToken cancellationToken = default(CancellationToken)) + private List SpreadTime(List source, int minutes, int roundToMinutes, bool up = false) + { + if (source == null || !source.Any()) + { + return new List(); + } + + List newList = new List(); + int remainMinutes = minutes; + + //Нам нужно раскидать 480 минут в день. + foreach (var taskGroup in source.GroupBy(f => f.Branch).OrderByDescending(f => f.Count())) + { + int currentTaskCommits = taskGroup.Count(); + int currentTaskTime = (int)RoundTo(minutes / source.Count * currentTaskCommits, roundToMinutes, up); + remainMinutes = remainMinutes - currentTaskTime; + + var orderedTasks = taskGroup.OrderBy(f => f.StartTime).ToArray(); + StringBuilder sb = new StringBuilder(); + foreach (var task in orderedTasks) + { + sb.AppendLine($"- {task.Description}"); + } + var taskTimeItem = new TaskTimeItem + { + Branch = taskGroup.Key, + TimeSpent = TimeSpan.FromMinutes(taskGroup.Sum(f=>f.TimeSpent.TotalMinutes)) + TimeSpan.FromMinutes(currentTaskTime), + Commits = taskGroup.Sum(f=>f.Commits), + Description = sb.ToString(), + StartTime = taskGroup.Min(f=>f.StartTime), + FilesAffected = taskGroup.Sum(f=>f.FilesAffected) + }; + + newList.Add(taskTimeItem); + } + newList = newList.OrderByDescending(f => f.TimeSpent).ToList(); + + return newList; + } + + public List CalculateTaskTime(List commits, Settings settings, CancellationToken cancellationToken = default(CancellationToken)) { int minutesPerWorkDay = settings.MinuterPerWorkDay + GetRandomMinutes(settings); int workHours = (settings.MinuterPerWorkDay / 60); int totalCommitsCount = commits.Count; + if (cancellationToken.IsCancellationRequested) + { + return new List(); + } if (!commits.Any()) { return new List(); } + FixTooBigInterval(settings, workHours, totalCommitsCount); + + int remainMinutes = minutesPerWorkDay; + var timeControlTask = GetTimeControlTask(settings, ref remainMinutes); + var workTimeItems = SpreadTime(commits, remainMinutes, settings.RoundToMinutes); + + if (timeControlTask != null) + { + workTimeItems.Add(timeControlTask); + } + + remainMinutes = minutesPerWorkDay - (int) workTimeItems.Sum(f => f.TimeSpent.TotalMinutes); + if (remainMinutes != 0) + { + _log.Trace($"Погрешность распределения времени: {remainMinutes}. Добавляю к первой задаче."); + } + + if ((workTimeItems.First().TimeSpent.TotalMinutes > Math.Abs(remainMinutes) && remainMinutes < 0) || remainMinutes > 0) + { + //если переборщили или не достаточно добавили до 8 часов - скореектируем остаток в первой задаче (она самая трудозатратная). + workTimeItems.First().TimeSpent += TimeSpan.FromMinutes(remainMinutes); + } + + PrintTotal(workTimeItems); + + return workTimeItems; + } + + private TaskTimeItem GetTimeControlTask(Settings settings, ref int minutesPerWorkDay) + { + //Если указана задача контроля времени - то спишем туда 30 минут и вычеркнем их из общего рабочего времени. + if (!string.IsNullOrEmpty(settings.TimeControlTask)) + { + minutesPerWorkDay = minutesPerWorkDay - 30; + return new TaskTimeItem + { + TimeSpent = TimeSpan.FromMinutes(30), + Branch = settings.TimeControlTask, + Commits = 1, + Description = "Ведение учета времени" + }; + } + + return null; + } + + private void FixTooBigInterval(Settings settings, int totalCommitsCount, int workHours) + { //если кол-во коммитов более чем кол-во интервалов - то уменьшим интервал вдвое. while (totalCommitsCount > (workHours * (60.0 / settings.RoundToMinutes))) { - settings.RoundToMinutes = (int)RoundTo((decimal)(settings.RoundToMinutes / 2.0), 5); + settings.RoundToMinutes = (int) RoundTo((decimal) (settings.RoundToMinutes / 2.0), 5); _log.Info($"Слишком много задач - уменьшаю интервал до {settings.RoundToMinutes}."); if (settings.RoundToMinutes == 5) { break; } } - - List workTimeItems = new List(); - //Если указана задача контроля времени - то спишем туда 30 минут и вычеркнем их из общего рабочего времени. - if (!string.IsNullOrEmpty(settings.TimeControlTask)) - { - minutesPerWorkDay = minutesPerWorkDay - 30; - workTimeItems.Add(new TaskTimeItem - { - Time = TimeSpan.FromMinutes(30), - Branch = settings.TimeControlTask, - Commits = 1, - Description = "Ведение учета времени", - StartTime = DateTime.Now.Date - }); - } - - int remainMinutes = minutesPerWorkDay; - //Нам нужно раскидать 480 минут в день. - foreach (var taskGroup in commits.GroupBy(f => f.Branch).OrderByDescending(f=>f.Count())) - { - if (cancellationToken.IsCancellationRequested) - { - return new List(); - } - - int currentTaskCommits = taskGroup.Count(); - int currentTaskTime = (int)RoundTo(minutesPerWorkDay / totalCommitsCount * currentTaskCommits, settings.RoundToMinutes, false); - remainMinutes = remainMinutes - currentTaskTime; - - var orderedTasks = taskGroup.OrderBy(f => f.Time).ToArray(); - StringBuilder sb = new StringBuilder(); - foreach (var task in orderedTasks) - { - sb.AppendLine($"- {task.Description}"); - } - - var taskTimeItem = new TaskTimeItem - { - Branch = taskGroup.Key, - Time = TimeSpan.FromMinutes(currentTaskTime), - Commits = taskGroup.Count(), - Description = sb.ToString(), - - StartTime = orderedTasks.First().Time, - EndTime = orderedTasks.Last().Time - }; - - workTimeItems.Add(taskTimeItem); - } - if (!workTimeItems.Any()) - { - return new List(); - } - workTimeItems = workTimeItems.OrderByDescending(f => f.Time).ToList(); - - if (remainMinutes != 0) - { - _log.Trace($"Погрешность распределения времени: {remainMinutes}. Добавляю к первой задаче."); - } - - if ((workTimeItems.First().Time.TotalMinutes > Math.Abs(remainMinutes) && remainMinutes < 0) || remainMinutes > 0) - { - //если переборщили или не достаточно добавили до 8 часов - скореектируем остаток в первой задаче (она самая трудозатратная). - workTimeItems.First().Time += TimeSpan.FromMinutes(remainMinutes); - } - - PrintTotal(workTimeItems); - - return workTimeItems; } private int GetRandomMinutes(Settings settings) @@ -123,7 +146,7 @@ namespace JiraTimeBot.TaskTime var totalTime = TimeSpan.Zero; foreach (var workTimeItem in workTimeItems) { - totalTime += workTimeItem.Time; + totalTime += workTimeItem.TimeSpent; } _log.Trace($"Итоговое реально проставляемое время: {totalTime}"); diff --git a/src/JiraTimeBot/TasksProcessors/MeetingProcessor.cs b/src/JiraTimeBot/TasksProcessors/MeetingProcessor.cs index 4d3c0cf..51fcba4 100644 --- a/src/JiraTimeBot/TasksProcessors/MeetingProcessor.cs +++ b/src/JiraTimeBot/TasksProcessors/MeetingProcessor.cs @@ -23,10 +23,10 @@ namespace JiraTimeBot.TasksProcessors { _log.Trace($"На реальную дату {realDate:dd.MM.yyyy} распределение по задачам:"); - foreach (var taskTime in taskTimes.OrderByDescending(f => f.Time)) + foreach (var taskTime in taskTimes.OrderByDescending(f => f.TimeSpent)) { var taskName = _jiraApi.GetTaskName(taskTime.Branch, settings); - _log.Trace($" - [{taskTime.Branch}, коммитов {taskTime.Commits}]: {taskName} - {taskTime.Time}"); + _log.Trace($" - [{taskTime.Branch}, коммитов {taskTime.Commits}]: {taskName} - {taskTime.TimeSpent}"); } } } diff --git a/src/JiraTimeBot/TasksProcessors/WorkLogTasksProcessor.cs b/src/JiraTimeBot/TasksProcessors/WorkLogTasksProcessor.cs index 4e7b2ff..a97544f 100644 --- a/src/JiraTimeBot/TasksProcessors/WorkLogTasksProcessor.cs +++ b/src/JiraTimeBot/TasksProcessors/WorkLogTasksProcessor.cs @@ -23,7 +23,7 @@ namespace JiraTimeBot.TasksProcessors _log.Trace($"На реальную дату {realDate:dd.MM.yyyy} распределение по задачам:"); foreach (var taskTime in taskTimes) { - _log.Trace($"- {taskTime.Branch} (коммитов {taskTime.Commits}): {taskTime.Time}"); + _log.Trace($"- {taskTime.Branch} (коммитов {taskTime.Commits}): {taskTime.TimeSpent}"); } _jiraApi.SetTodayWorklog(taskTimes, settings, date: setForDate, dummy: dummyMode, addCommentsToWorklog: settings.AddCommentsToWorklog, cancellationToken: cancellationToken);