Compare commits

..
14 Commits
Author SHA1 Message Date
Danil Kislov fd350ef43d Чаще проверяем обновления, не спамим про актуальную версию в лог. 2019-03-20 09:31:12 +03:00
Danil Kislov 87ef6551e0 Merge branch 'master' of github.com:monster1025/JiraTimeBot 2019-03-20 09:23:54 +03:00
Danil Kislov 0c021a5b3c Дополнен алгоритм работы с JIRA-задачами 2019-03-20 09:23:39 +03:00
Danil Kislov 6bc2627c1b Дополнен алгоритм работы с JIRA-задачами 2019-03-20 09:23:28 +03:00
Danil 8ee71eca6e Update README.md 2019-03-19 21:21:00 +03:00
Danil 5227897010 Update README.md 2019-03-19 21:12:52 +03:00
Danil Kislov 53050ebed3 Исправление алгоритма удаления ворклогов 2019-03-19 20:35:11 +03:00
Danil Kislov 2f6d522636 При удалении ворклога правильно сравниваем юзернейм. 2019-03-19 20:21:05 +03:00
Danil Kislov da0968d003 Дополнительное условие фильтра 2019-03-19 20:05:47 +03:00
Danil Kislov 882ecf3a4b Исправление добавления отрицательного времени 2019-03-19 19:55:32 +03:00
Danil Kislov 9563b2220d Merge branch 'master' of github.com:monster1025/JiraTimeBot 2019-03-19 19:53:28 +03:00
Danil Kislov 9a3adc5059 Исправлено возможное добавление отрицательного времени 2019-03-19 19:53:05 +03:00
Anton Volkov 16b15c44a6 up 2019-03-19 16:03:06 +03:00
Anton Volkov 2afc189307 + mutex 2019-03-19 15:41:41 +03:00
11 changed files with 160 additions and 109 deletions
+1 -1
View File
@@ -1 +1 @@
1.0.20
1.0.24
+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
+14 -4
View File
@@ -1,5 +1,4 @@
using System.Windows.Forms;
using Autofac;
using Autofac;
using Autofac.Extras.AggregateService;
using JiraTimeBot.JiraIntegration;
using JiraTimeBot.JiraIntegration.Comments;
@@ -8,6 +7,8 @@ using JiraTimeBot.Mercurial.Modifiers;
using JiraTimeBot.TasksProcessors;
using JiraTimeBot.TaskTime;
using JiraTimeBot.UI.Tray;
using System;
using System.Windows.Forms;
namespace JiraTimeBot.DI
{
@@ -21,8 +22,17 @@ namespace JiraTimeBot.DI
public IContainer Build(TextBox logTextBox)
{
_builder.Register(f => new Logger(logTextBox)).As<ILog>().AsSelf();
_builder.Register(c => logTextBox).AsSelf();
_builder.Register(f =>
{
var txt = f.Resolve<TextBox>();
return new Logger(message =>
{
txt.Invoke(new Action<string>(text => txt.AppendText(text)), message + Environment.NewLine);
});
}).As<ILog>().AsSelf();
_builder.Register(f => new TrayMenu()).As<ITrayMenu>().AsSelf();
_builder.Register(c => new CommitSkipper()).As<ICommitSkipper>();
@@ -1,8 +1,8 @@
using System;
using JiraTimeBot.Configuration;
using JiraTimeBot.TaskTime.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using JiraTimeBot.Configuration;
using JiraTimeBot.TaskTime.Objects;
namespace JiraTimeBot.JiraIntegration.Comments
{
+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,52 @@ 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>();
}
List<Comment> comments = new List<Comment>();
if (issue.Type?.Name != "Дубликат")
{
_log.Trace($"Получаю комментарии по задаче {issue.Key} ({issue.Type?.Name}).");
comments = issue.GetCommentsAsync(cancellationToken).Result?.ToList() ?? new List<Comment>();
}
else
{
_log.Trace($"Пропускаю комментарии по задаче {issue.Key} ({issue.Type?.Name}).");
}
var user = settings.JiraUserName;
var userComments = comments.Where(f => f.Author.Equals(user, StringComparison.InvariantCultureIgnoreCase)).ToList();
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;
+4 -10
View File
@@ -1,29 +1,23 @@
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace JiraTimeBot
{
public class Logger : ILog
{
private readonly TextBox _txt;
private readonly AppendLog _safeAppender;
private readonly Action<string> _logAction;
public Logger(TextBox txt)
public Logger(Action<string> logAction)
{
_txt = txt;
_safeAppender = text => _txt.AppendText(text);
_logAction = logAction;
}
private void AppendText(string message)
{
message = $"{DateTime.Now:HH:mm} {message}";
_txt.Invoke(_safeAppender, message + Environment.NewLine);
_logAction(message);
}
private delegate void AppendLog(string text);
public void Info([Localizable(false)] string message)
{
AppendText(message);
+12 -27
View File
@@ -1,45 +1,30 @@
using System;
using System.Diagnostics;
using JiraTimeBot.UI;
using System;
using System.Threading;
using System.Windows.Forms;
using JiraTimeBot.UI;
namespace JiraTimeBot
{
static class Program
{
private static Mutex _mutex = new Mutex(true, "{44426B3E-B901-4792-ACEA-1385D79DBAD1}");
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
if (PriorProcess() != null)
if (_mutex.WaitOne(TimeSpan.Zero, true))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
_mutex.ReleaseMutex();
}
else
{
MessageBox.Show("Another instance of the app is already running.");
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
}
public static Process PriorProcess()
// Returns a System.Diagnostics.Process pointing to
// a pre-existing process with the same name as the
// current one, if any; or null if the current process
// is unique.
{
Process curr = Process.GetCurrentProcess();
Process[] procs = Process.GetProcessesByName(curr.ProcessName);
foreach (Process p in procs)
{
if ((p.Id != curr.Id) &&
(p.MainModule.FileName == curr.MainModule.FileName))
return p;
}
return null;
}
}
}
+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.20")]
[assembly: AssemblyFileVersion("1.0.20")]
[assembly: AssemblyInformationalVersion("1.0.20")]
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.24")]
[assembly: AssemblyFileVersion("1.0.24")]
[assembly: AssemblyInformationalVersion("1.0.24")]
@@ -78,8 +78,11 @@ namespace JiraTimeBot.TaskTime
_log.Trace($"Погрешность распределения времени: {remainMinutes}. Добавляю к первой задаче.");
}
//если переборщили или не достаточно добавили до 8 часов - скореектируем остаток в первой задаче (она самая трудозатратная).
workTimeItems.First().Time += TimeSpan.FromMinutes(remainMinutes);
if (workTimeItems.First().Time.TotalMinutes > Math.Abs(remainMinutes) && remainMinutes < 0)
{
//если переборщили или не достаточно добавили до 8 часов - скореектируем остаток в первой задаче (она самая трудозатратная).
workTimeItems.First().Time += TimeSpan.FromMinutes(remainMinutes);
}
return workTimeItems;
}
+13 -11
View File
@@ -1,16 +1,16 @@
using System;
using Autofac;
using JiraTimeBot.Configuration;
using JiraTimeBot.DI;
using JiraTimeBot.TasksProcessors;
using JiraTimeBot.UI.Tray;
using JiraTimeBot.Update;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Autofac;
using JiraTimeBot.Configuration;
using JiraTimeBot.DI;
using JiraTimeBot.TasksProcessors;
using JiraTimeBot.UI.Tray;
using JiraTimeBot.Update;
// This is the code for your desktop app.
// Press Ctrl+F5 (or go to Debug > Start Without Debugging) to run your app.
@@ -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)