Compare commits

...
11 Commits
8 changed files with 113 additions and 61 deletions
+1 -1
View File
@@ -1 +1 @@
1.0.21
1.0.25
+13
View File
@@ -5,5 +5,18 @@
- Предполагается что все Ваши репозитории находятся в одной папке (к которой вы указываете путь в настройках).
- Предполагается что ветки в JIRA имеют название эквивалентное имени бранча в Mercurial.
# Настройки
- Режим работы:
- Mercurial - по коммитам в Mercurial, можем определять множественные коммиты в одну задачу (ветку) и пропорционально списывать время.
- Jira - выполняем JQL и равномерно распределяем время.
- Путь до репо (Режим Mercurial) - путь до папки, в которую клонируются исходники.
- Логин Jira - Логин в Jira (от этого пользователя будем выполнять запросы и вносить время).
- Пароль Jira - шифруется через DPAPI.
- Mercurial Name (Режим Mercurial) - обязателен к указанию для режима Mercurial - имя пользователя Mercurial, от которого проходят коммиты.
- Округление времени - Для красоты распределения времени тут устанавливается округление. По умолчанию - 15 минут, получим время 1:15, 0:30 итд. Остаток нераспределенного времени будет внесен в самую долгую задачу или же если это невозможно - количество рабочего времени будет увеличено или уменьшено.
- JQL (Режим Jira) - можно задать свой JQL для поиска задач. Возможные замены: %USER% - Логин Jira, %DATE% дата на которую формируем список. Значнеие по умолчанию: status changed by '%USER%' during ("%DATE%","%DATE%")
- Время запуска - время автоматического запуска бота. Если была установлена галочка "тестовый прогон" - она будет снята.
- Добавлять описание (Режим Mercurial) - добавлять ли в описание CommitMessage, если не установлено - добавляются стандартные фразы программистов (типа "написание кода").
# Скачать
Скачать актуальную версию можно тут: https://github.com/monster1025/JiraTimeBot/releases/latest
+20 -13
View File
@@ -34,14 +34,13 @@ namespace JiraTimeBot.JiraIntegration
}
}
public List<Issue> GetTodayIssues(Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken))
public List<Issue> GetIssuesByJQL(string jql, Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken))
{
var jira = Jira.CreateRestClient(settings.JiraUrl, settings.JiraUserName, settings.JiraPassword);
date = date.GetValueOrDefault(DateTime.Now.Date);
var userName = settings.JiraUserName;
var jql = settings.JiraQuery;
if (string.IsNullOrEmpty(jql))
{
jql = $"status changed by '%USER%' during (\"%DATE%\",\"%DATE%\")";
@@ -53,7 +52,7 @@ namespace JiraTimeBot.JiraIntegration
try
{
List<Issue> affectedIssues =
jira.Issues.GetIssuesFromJqlAsync(jql, 50, 0, cancellationToken).Result.ToList();
jira.Issues.GetIssuesFromJqlAsync(jql, 100, 0, cancellationToken).Result.ToList();
return affectedIssues;
}
catch (Exception ex)
@@ -92,26 +91,34 @@ namespace JiraTimeBot.JiraIntegration
var hasTodayWorklog = false;
var workLogs = issue.GetWorklogsAsync().Result;
foreach (var workLog in workLogs)
var userWorklogs = workLogs.Where(w =>
w.CreateDate.GetValueOrDefault().Date == date && w.Author.Equals(settings.JiraUserName,
StringComparison.InvariantCultureIgnoreCase)).ToList();
foreach (var workLog in userWorklogs)
{
var timeSpent = TimeSpan.FromSeconds(workLog.TimeSpentInSeconds);
if (workLog.CreateDate.GetValueOrDefault().Date == date && workLog.Author == settings.JiraUserName)
var timeDiff = Math.Abs((timeSpent - taskTimeItem.Time).TotalMinutes);
if (timeDiff > 1 || userWorklogs.Count > 1)
{
var timeDiff = Math.Abs((timeSpent - taskTimeItem.Time).TotalMinutes);
if (timeDiff > 1)
{
_log.Trace($"Время отличается на {timeDiff} минут, удаляю worklog: {taskTimeItem.Branch} {workLog.Author} {workLog.CreateDate}: {workLog.TimeSpent}");
if (!dummy)
{
issue.DeleteWorklogAsync(workLog);
}
hasTodayWorklog = false;
}
else
{
_log.Trace($"По задаче {taskTimeItem.Branch} уже есть аналогичный worklog. Пропускаю.");
hasTodayWorklog = true;
_log.Trace($"Найдено более одного ворклога на задачу, удаляю worklog: {taskTimeItem.Branch} {workLog.Author} {workLog.CreateDate}: {workLog.TimeSpent}");
}
if (!dummy)
{
issue.DeleteWorklogAsync(workLog);
}
hasTodayWorklog = false;
}
else
{
_log.Trace($"По задаче {taskTimeItem.Branch} уже есть аналогичный worklog. Пропускаю.");
hasTodayWorklog = true;
}
}
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Atlassian.Jira;
using JiraTimeBot.Configuration;
using JiraTimeBot.Mercurial;
using JiraTimeBot.Mercurial.Objects;
@@ -24,17 +26,43 @@ namespace JiraTimeBot.JiraIntegration
{
date = date.GetValueOrDefault(DateTime.Now.Date);
var workTasks = new List<MercurialCommitItem>();
var issues = _jiraApi.GetTodayIssues(settings, date, cancellationToken);
var issues = _jiraApi.GetIssuesByJQL(settings.JiraQuery, settings, date, cancellationToken);
foreach (var issue in issues)
{
workTasks.Add(new MercurialCommitItem
if (cancellationToken.IsCancellationRequested)
{
Description = issue.Summary,
Time = issue.Updated.GetValueOrDefault(date.Value),
Branch = issue.Key.Value,
FilesAffected = 1
});
return new List<MercurialCommitItem>();
}
var comments = issue.GetCommentsAsync(cancellationToken).Result?.ToList() ?? new List<Comment>();
var user = settings.JiraUserName;
var userComments = comments.Where(f => f.Author.Equals(user, StringComparison.InvariantCultureIgnoreCase)).ToList();
_log.Trace($"Получены комментарии по задаче {issue.Key} ({issue.Type?.Name}): {userComments.Count}.");
if (userComments.Any())
{
foreach (var comment in userComments)
{
workTasks.Add(new MercurialCommitItem
{
Description = issue.Summary,
Time = issue.Updated.GetValueOrDefault(date.Value),
Branch = issue.Key.Value,
FilesAffected = 1
});
}
}
else
{
workTasks.Add(new MercurialCommitItem
{
Description = issue.Summary,
Time = issue.Updated.GetValueOrDefault(date.Value),
Branch = issue.Key.Value,
FilesAffected = 1
});
}
}
return workTasks;
+5 -3
View File
@@ -1,5 +1,7 @@
using JiraTimeBot.UI;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
@@ -7,19 +9,19 @@ namespace JiraTimeBot
{
static class Program
{
private static Mutex _mutex = new Mutex(true, "{44426B3E-B901-4792-ACEA-1385D79DBAD1}");
private static readonly Mutex Mutex = new Mutex(true, "{44426B3E-B901-4792-ACEA-1385D79DBAD1}");
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
if (_mutex.WaitOne(TimeSpan.Zero, true))
if (Mutex.WaitOne(TimeSpan.Zero, true))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
_mutex.ReleaseMutex();
Mutex.ReleaseMutex();
}
else
{
+31 -31
View File
@@ -1,31 +1,31 @@
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("JiraTimeBot")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("JiraTimeBot")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f3b89316-c38c-4ce6-9d88-0b1373cfba1a")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.21")]
[assembly: AssemblyFileVersion("1.0.21")]
[assembly: AssemblyInformationalVersion("1.0.21")]
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("JiraTimeBot")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("JiraTimeBot")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f3b89316-c38c-4ce6-9d88-0b1373cfba1a")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.25")]
[assembly: AssemblyFileVersion("1.0.25")]
[assembly: AssemblyInformationalVersion("1.0.25")]
@@ -27,7 +27,7 @@ namespace JiraTimeBot.TaskTime
while (totalCommitsCount > (8 * (60.0 / settings.RountToMinutes)))
{
settings.RountToMinutes = (int)RoundTo((decimal)(settings.RountToMinutes / 2.0), 5);
_log.Info($"Слишком много задачь - уменьшаю интервал до {settings.RountToMinutes}.");
_log.Info($"Слишком много задач - уменьшаю интервал до {settings.RountToMinutes}.");
if (settings.RountToMinutes == 5)
{
break;
@@ -78,7 +78,7 @@ namespace JiraTimeBot.TaskTime
_log.Trace($"Погрешность распределения времени: {remainMinutes}. Добавляю к первой задаче.");
}
if (workTimeItems.First().Time.TotalMinutes > Math.Abs(remainMinutes))
if (workTimeItems.First().Time.TotalMinutes > Math.Abs(remainMinutes) && remainMinutes < 0)
{
//если переборщили или не достаточно добавили до 8 часов - скореектируем остаток в первой задаче (она самая трудозатратная).
workTimeItems.First().Time += TimeSpan.FromMinutes(remainMinutes);
+6 -4
View File
@@ -29,6 +29,7 @@ namespace JiraTimeBot.UI
private Settings _settings;
private readonly Action _settingsWindowShow;
private readonly Action<string> _settingsErrorReporter;
private bool _updateChecked;
private CancellationTokenSource GetTokenSource()
{
@@ -61,7 +62,6 @@ namespace JiraTimeBot.UI
var frmSettings = new frmSettings();
frmSettings.ShowDialog(this);
};
_settingsErrorReporter = msg => MessageBox.Show(msg, "Загрузка настроек", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);;
}
@@ -200,7 +200,9 @@ namespace JiraTimeBot.UI
{
try
{
tmrUpdate.Interval = 8 * 60 * 60 * 1000;
var firstTime = !_updateChecked;
_updateChecked = true;
tmrUpdate.Interval = 1 * 60 * 60 * 1000;
var checker = new UpdateChecker();
var release = checker.GetRelease();
@@ -231,9 +233,9 @@ namespace JiraTimeBot.UI
tmrUpdate.Enabled = false;
}
}
else
else if (firstTime)
{
_log.Info($"Вы используете последнюю версию.");
_log.Info($"Вы используете актуальную версию.");
}
}
catch (Exception ex)