ok
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28307.421
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JiraTimeBot", "JiraTimeBot\JiraTimeBot.csproj", "{F3B89316-C38C-4CE6-9D88-0B1373CFBA1A}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{F3B89316-C38C-4CE6-9D88-0B1373CFBA1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{F3B89316-C38C-4CE6-9D88-0B1373CFBA1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{F3B89316-C38C-4CE6-9D88-0B1373CFBA1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{F3B89316-C38C-4CE6-9D88-0B1373CFBA1A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {2132DC12-967F-4EA3-9E39-5500488F1288}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" />
|
||||
</startup>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="RestSharp" publicKeyToken="598062e77f915f75" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-106.6.9.0" newVersion="106.6.9.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Autofac" publicKeyToken="17863af14b0044da" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.9.1.0" newVersion="4.9.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace JiraTimeBotForm.Configuration
|
||||
{
|
||||
public class PasswordEncryptionClass
|
||||
{
|
||||
private byte[] GetEntropy(string entropyString)
|
||||
{
|
||||
|
||||
MD5 md5 = MD5.Create();
|
||||
return md5.ComputeHash(Encoding.UTF8.GetBytes(entropyString));
|
||||
}
|
||||
|
||||
public string Encrypt(string username, string password, string address)
|
||||
{
|
||||
byte[] entropy = GetEntropy(address + username);
|
||||
byte[] pass = Encoding.UTF8.GetBytes(password);
|
||||
byte[] crypted = ProtectedData.Protect(pass, entropy, DataProtectionScope.LocalMachine);
|
||||
var cryptedBase64 = Convert.ToBase64String(crypted);
|
||||
|
||||
return cryptedBase64;
|
||||
}
|
||||
|
||||
public string Decrypt(string username, string password, string address)
|
||||
{
|
||||
try
|
||||
{
|
||||
var pass = Convert.FromBase64String(password);
|
||||
byte[] entropy = GetEntropy(address + username);
|
||||
pass = ProtectedData.Unprotect(pass, entropy, DataProtectionScope.LocalMachine);
|
||||
return Encoding.UTF8.GetString(pass);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace JiraTimeBotForm.Configuration
|
||||
{
|
||||
public class Settings
|
||||
{
|
||||
public int MinuterPerWorkDay = 8 * 60;
|
||||
public int RountToMinutes { get; set; }
|
||||
|
||||
public string JiraUrl = "https://myteam.aeroclub.ru";
|
||||
|
||||
public string RepositoryPath { get; set; }
|
||||
public string MercurialAuthorEmail { get; set; }
|
||||
public string JiraUserName { get; set; }
|
||||
public string JiraPassword { get; set; }
|
||||
public TimeSpan ActivationTime { get; set; }
|
||||
public bool AddCommentsToWorklog { get; set; }
|
||||
public WorkType WorkType { get; set; }
|
||||
public string JiraQuery { get; set; }
|
||||
|
||||
public static Settings LoadAndCheck(Action settingsSource, Action<string> errorMessageReporter)
|
||||
{
|
||||
Settings settings;
|
||||
bool settingsValid;
|
||||
|
||||
do
|
||||
{
|
||||
settings = Load();
|
||||
settingsValid = SettingsIsValid(settings, out var message);
|
||||
|
||||
if (!settingsValid)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
errorMessageReporter(message);
|
||||
}
|
||||
|
||||
settingsSource();
|
||||
}
|
||||
|
||||
} while (!settingsValid);
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private static bool SettingsIsValid(Settings settings, out string message)
|
||||
{
|
||||
if (settings == null)
|
||||
{
|
||||
message = "Заполните настройки";
|
||||
return false;
|
||||
}
|
||||
|
||||
var errors = new List<string>();
|
||||
message = null;
|
||||
|
||||
if (settings.RountToMinutes == default(int))
|
||||
{
|
||||
errors.Add("Укажите округление времени, например, 10 минут");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(settings.JiraPassword))
|
||||
{
|
||||
errors.Add("Укажите Password от Jira");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(settings.JiraUserName))
|
||||
{
|
||||
errors.Add("Укажите UserName от Jira");
|
||||
}
|
||||
|
||||
if (settings.WorkType == WorkType.Mercurial && (string.IsNullOrEmpty(settings.RepositoryPath) || !Directory.Exists(settings.RepositoryPath)))
|
||||
{
|
||||
errors.Add("Укажите верный RepositoryPath");
|
||||
}
|
||||
|
||||
var hasErrors = errors.Any();
|
||||
if (hasErrors)
|
||||
{
|
||||
message = string.Join(Environment.NewLine, errors);
|
||||
}
|
||||
|
||||
return !hasErrors;
|
||||
}
|
||||
|
||||
public static Settings Load()
|
||||
{
|
||||
var settingsPath = Path.Combine(Application.UserAppDataPath, _settingsFileName);
|
||||
if (!File.Exists(settingsPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var settingsText = File.ReadAllText(settingsPath);
|
||||
|
||||
Settings settings;
|
||||
|
||||
try
|
||||
{
|
||||
settings = JsonConvert.DeserializeObject<Settings>(settingsText);
|
||||
if (settings == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var password = new PasswordEncryptionClass().Decrypt(settings.JiraUserName, settings.JiraPassword, settings.JiraUrl);
|
||||
settings.JiraPassword = password;
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
var password = new PasswordEncryptionClass().Encrypt(this.JiraUserName, this.JiraPassword, this.JiraUrl);
|
||||
this.JiraPassword = password;
|
||||
|
||||
var settingsPath = Path.Combine(Application.UserAppDataPath, _settingsFileName);
|
||||
var settingsString = JsonConvert.SerializeObject(this);
|
||||
File.WriteAllText(settingsPath, settingsString);
|
||||
}
|
||||
|
||||
private static string _settingsFileName = "settings.json";
|
||||
}
|
||||
|
||||
public enum WorkType
|
||||
{
|
||||
Mercurial = 0,
|
||||
JiraLogs = 1,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Windows.Forms;
|
||||
using Autofac;
|
||||
using Autofac.Extras.AggregateService;
|
||||
using JiraTimeBotForm.JiraIntegration;
|
||||
using JiraTimeBotForm.JiraIntegration.Comments;
|
||||
using JiraTimeBotForm.Mercurial;
|
||||
using JiraTimeBotForm.Mercurial.Modifiers;
|
||||
using JiraTimeBotForm.TasksProcessors;
|
||||
using JiraTimeBotForm.TaskTime;
|
||||
using JiraTimeBotForm.UI;
|
||||
using JiraTimeBotForm.UI.Tray;
|
||||
|
||||
namespace JiraTimeBotForm.DI
|
||||
{
|
||||
public class Bootstrapper
|
||||
{
|
||||
private readonly ContainerBuilder _builder;
|
||||
public Bootstrapper()
|
||||
{
|
||||
_builder = new ContainerBuilder();
|
||||
}
|
||||
|
||||
public IContainer Build(TextBox logTextBox)
|
||||
{
|
||||
_builder.Register(f => new Logger(logTextBox)).As<ILog>().AsSelf();
|
||||
|
||||
_builder.Register(f => new TrayMenu()).As<ITrayMenu>().AsSelf();
|
||||
_builder.Register(c => new CommitSkipper()).As<ICommitSkipper>();
|
||||
|
||||
_builder.RegisterType<TechnicalInfoSkipper>().As<ITechnicalInfoSkipper>().AsSelf();
|
||||
|
||||
_builder.RegisterType<MercurialLog>().As<IMercurialLog>().AsSelf();
|
||||
_builder.RegisterType<JiraCommitEmulator>().As<IMercurialLog>().AsSelf();
|
||||
_builder.RegisterAggregateService<IAllMercurialProviders>();
|
||||
|
||||
_builder.RegisterType<TaskTimeByCommitsCalculator>().As<ITaskTimeCalculator>().AsSelf();
|
||||
|
||||
_builder.RegisterType<WorkLogTasksProcessor>().AsSelf().As<ITasksProcessor>();
|
||||
_builder.RegisterType<MeetingProcessor>().AsSelf().As<ITasksProcessor>();
|
||||
_builder.RegisterAggregateService<IAllTasksProcessors>();
|
||||
|
||||
_builder.RegisterType<JiraDescriptionSource>().AsSelf().As<IJiraDescriptionSource>();
|
||||
_builder.RegisterType<JiraApi>().AsSelf().AsImplementedInterfaces();
|
||||
|
||||
_builder.Register(c => new Job(c.Resolve<IAllMercurialProviders>(), c.Resolve<ITaskTimeCalculator>(), c.Resolve<ILog>())).AsSelf();
|
||||
|
||||
var container = _builder.Build();
|
||||
return container;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.JiraIntegration.Comments
|
||||
{
|
||||
public interface IJiraDescriptionSource
|
||||
{
|
||||
string GetDescription(TaskTimeItem item, bool addCommentsToWorklog);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.JiraIntegration.Comments
|
||||
{
|
||||
public class JiraDescriptionSource : IJiraDescriptionSource
|
||||
{
|
||||
private readonly List<string> _dummyComments = new List<string>
|
||||
{
|
||||
"Написание кода", "написание кода", "программирование", "реализация задачи", "кодинг", "код + тесты",
|
||||
"кодинг", "написание кода и тестов"
|
||||
};
|
||||
|
||||
public string GetDescription(TaskTimeItem taskTimeItem, bool addCommentsToWorklog)
|
||||
{
|
||||
if (addCommentsToWorklog)
|
||||
{
|
||||
return taskTimeItem.Description;
|
||||
}
|
||||
|
||||
return _dummyComments.OrderBy(f => Guid.NewGuid()).FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Atlassian.Jira;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.JiraIntegration.Comments;
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.JiraIntegration
|
||||
{
|
||||
public class JiraApi
|
||||
{
|
||||
private readonly ILog _log;
|
||||
private readonly IJiraDescriptionSource _descriptionSource;
|
||||
|
||||
public JiraApi(ILog log, IJiraDescriptionSource descriptionSource)
|
||||
{
|
||||
_log = log;
|
||||
_descriptionSource = descriptionSource;
|
||||
}
|
||||
|
||||
public string GetTaskName(string branch, Settings settings)
|
||||
{
|
||||
var jira = Jira.CreateRestClient(settings.JiraUrl, settings.JiraUserName, settings.JiraPassword);
|
||||
try
|
||||
{
|
||||
var issue = jira.Issues.Queryable.FirstOrDefault(f => f.Key == branch);
|
||||
return issue?.Summary;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Issue> GetTodayIssues(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%\")";
|
||||
}
|
||||
|
||||
jql = jql.Replace("%USER%", userName);
|
||||
jql = jql.Replace("%DATE%", $"{date.Value:yyyy-MM-dd}");
|
||||
|
||||
try
|
||||
{
|
||||
List<Issue> affectedIssues =
|
||||
jira.Issues.GetIssuesFromJqlAsync(jql, 50, 0, cancellationToken).Result.ToList();
|
||||
return affectedIssues;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_log.Error($"Ошибка получения информации из JIRA: {ex.InnerException?.Message}");
|
||||
return new List<Issue>();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetTodayWorklog(List<TaskTimeItem> taskTimeItems, Settings settings, DateTime? date = null, bool dummy = false, bool addCommentsToWorklog = false)
|
||||
{
|
||||
var jira = Jira.CreateRestClient(settings.JiraUrl, settings.JiraUserName, settings.JiraPassword);
|
||||
|
||||
if (date == null)
|
||||
{
|
||||
date = DateTime.Now.Date;
|
||||
}
|
||||
date = date.Value.Date;
|
||||
|
||||
foreach (TaskTimeItem taskTimeItem in taskTimeItems)
|
||||
{
|
||||
Issue issue = null;
|
||||
try
|
||||
{
|
||||
issue = jira.Issues.Queryable.FirstOrDefault(f => f.Key == taskTimeItem.Branch);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
}
|
||||
if (issue == null)
|
||||
{
|
||||
_log.Error($"[!] Не могу найти ветку {taskTimeItem.Branch} в JIRA, пропускаю!");
|
||||
continue;
|
||||
}
|
||||
|
||||
var hasTodayWorklog = false;
|
||||
var workLogs = issue.GetWorklogsAsync().Result;
|
||||
foreach (var workLog in workLogs)
|
||||
{
|
||||
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)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasTodayWorklog)
|
||||
{
|
||||
var timeSpentJira = $"{taskTimeItem.Time.TotalMinutes}m";
|
||||
|
||||
var comment = _descriptionSource.GetDescription(taskTimeItem, addCommentsToWorklog);
|
||||
|
||||
Worklog workLogToAdd = new Worklog(timeSpentJira, date.Value, comment);
|
||||
if (!dummy)
|
||||
{
|
||||
workLogToAdd = issue.AddWorklogAsync(workLogToAdd).Result;
|
||||
}
|
||||
_log.Trace($"Добавили Worklog для {taskTimeItem.Branch}: {workLogToAdd.Author} {workLogToAdd.CreateDate}: {workLogToAdd.TimeSpent}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.Mercurial;
|
||||
using JiraTimeBotForm.Mercurial.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.JiraIntegration
|
||||
{
|
||||
public class JiraCommitEmulator : IMercurialLog
|
||||
{
|
||||
private readonly ILog _log;
|
||||
private readonly JiraApi _jiraApi;
|
||||
|
||||
public JiraCommitEmulator(ILog log, JiraApi jiraApi)
|
||||
{
|
||||
_log = log;
|
||||
_jiraApi = jiraApi;
|
||||
}
|
||||
|
||||
public List<MercurialCommitItem> GetMercurialLog(Settings settings,
|
||||
DateTime? date = null,
|
||||
CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
date = date.GetValueOrDefault(DateTime.Now.Date);
|
||||
var workTasks = new List<MercurialCommitItem>();
|
||||
var issues = _jiraApi.GetTodayIssues(settings, date, cancellationToken);
|
||||
|
||||
foreach (var issue in issues)
|
||||
{
|
||||
workTasks.Add(new MercurialCommitItem
|
||||
{
|
||||
Description = issue.Summary,
|
||||
Time = issue.Updated.GetValueOrDefault(date.Value),
|
||||
Branch = issue.Key.Value,
|
||||
FilesAffected = 1
|
||||
});
|
||||
}
|
||||
|
||||
return workTasks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{F3B89316-C38C-4CE6-9D88-0B1373CFBA1A}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>JiraTimeBotForm</RootNamespace>
|
||||
<AssemblyName>JiraTimeBotForm</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>app.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Atlassian.Jira, Version=10.4.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Atlassian.SDK.10.4.0\lib\net452\Atlassian.Jira.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Autofac, Version=4.9.1.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Autofac.4.9.1\lib\net45\Autofac.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Autofac.Extras.AggregateService, Version=4.1.0.0, Culture=neutral, PublicKeyToken=17863af14b0044da, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Autofac.Extras.AggregateService.4.1.0\lib\net45\Autofac.Extras.AggregateService.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Mercurial.Net, Version=1.1.1.607, Culture=neutral, PublicKeyToken=9e2fabf3af85dba9, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Mercurial.Net.1.1.1.607\lib\net35-Client\Mercurial.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.12.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.5.11\lib\net45\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="RestSharp, Version=106.6.9.0, Culture=neutral, PublicKeyToken=598062e77f915f75, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\RestSharp.106.6.9\lib\net452\RestSharp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.DirectoryServices" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Security" />
|
||||
<Reference Include="System.ServiceModel" />
|
||||
<Reference Include="System.Transactions" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="DI\ContainerBuilder.cs" />
|
||||
<Compile Include="JiraIntegration\Comments\IJiraDescriptionSource.cs" />
|
||||
<Compile Include="JiraIntegration\Comments\JiraDescriptionSource.cs" />
|
||||
<Compile Include="Job.cs" />
|
||||
<Compile Include="Logger.cs" />
|
||||
<Compile Include="Configuration\Settings.cs" />
|
||||
<Compile Include="JiraIntegration\JiraCommitEmulator.cs" />
|
||||
<Compile Include="Mercurial\IAllMercurialProviders.cs" />
|
||||
<Compile Include="Mercurial\Modifiers\ITechnicalInfoSkipper.cs" />
|
||||
<Compile Include="Mercurial\Modifiers\CommitSkipper.cs" />
|
||||
<Compile Include="Mercurial\Modifiers\ICommitSkipper.cs" />
|
||||
<Compile Include="Mercurial\IMercurialLog.cs" />
|
||||
<Compile Include="Mercurial\Modifiers\TechnicalInfoSkipper.cs" />
|
||||
<Compile Include="TasksProcessors\IAllTasksProcessors.cs" />
|
||||
<Compile Include="UI\frmMain.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="UI\frmMain.Designer.cs">
|
||||
<DependentUpon>frmMain.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="JiraIntegration\JiraApi.cs" />
|
||||
<Compile Include="Mercurial\Objects\MercurialCommitItem.cs" />
|
||||
<Compile Include="Mercurial\MercurialLog.cs" />
|
||||
<Compile Include="Configuration\PasswordEncryptionClass.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TasksProcessors\ITasksProcessor.cs" />
|
||||
<Compile Include="TasksProcessors\MeetingProcessor.cs" />
|
||||
<Compile Include="TasksProcessors\WorkLogTasksProcessor.cs" />
|
||||
<Compile Include="TaskTime\ITaskTimeCalculator.cs" />
|
||||
<Compile Include="TaskTime\TaskTimeByCommitsCalculator.cs" />
|
||||
<Compile Include="TaskTime\Objects\TaskTimeItem.cs" />
|
||||
<Compile Include="UI\frmSettings.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="UI\frmSettings.Designer.cs">
|
||||
<DependentUpon>frmSettings.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="UI\Tray\ITrayMenu.cs" />
|
||||
<Compile Include="UI\Tray\RestoreFromTray.cs" />
|
||||
<Compile Include="UI\Tray\TrayMenu.cs" />
|
||||
<EmbeddedResource Include="UI\frmMain.resx">
|
||||
<DependentUpon>frmMain.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<EmbeddedResource Include="UI\frmSettings.resx">
|
||||
<DependentUpon>frmSettings.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<None Include="packages.config" />
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="app.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.Mercurial;
|
||||
using JiraTimeBotForm.Mercurial.Objects;
|
||||
using JiraTimeBotForm.TasksProcessors;
|
||||
using JiraTimeBotForm.TaskTime;
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm
|
||||
{
|
||||
class Job
|
||||
{
|
||||
private readonly IAllMercurialProviders _mercurialProviders;
|
||||
private readonly ITaskTimeCalculator _taskTimeDiscoverer;
|
||||
private readonly ILog _log;
|
||||
|
||||
public Job(IAllMercurialProviders mercurialProviders, ITaskTimeCalculator taskTimeDiscoverer, ILog log)
|
||||
{
|
||||
_mercurialProviders = mercurialProviders;
|
||||
_taskTimeDiscoverer = taskTimeDiscoverer;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public Task DoTheJob(Settings settings, ITasksProcessor tasksProcessor, bool dummyMode, CancellationToken cancellationToken)
|
||||
{
|
||||
_log.Info("Начинаем работу");
|
||||
|
||||
return Task.Run(() => DoTheJobImpl(settings, tasksProcessor, dummyMode, cancellationToken), cancellationToken);
|
||||
}
|
||||
|
||||
private void DoTheJobImpl(Settings settings, ITasksProcessor tasksProcessor, bool dummyMode, CancellationToken cancellationToken)
|
||||
{
|
||||
int daysDiff = 0;
|
||||
if (tasksProcessor is MeetingProcessor)
|
||||
{
|
||||
daysDiff = -1;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
DateTime date = DateTime.Now.Date.AddDays(daysDiff);
|
||||
|
||||
IMercurialLog mercurial = _mercurialProviders.MercurialLog;
|
||||
if (settings.WorkType == WorkType.JiraLogs)
|
||||
{
|
||||
_log.Info("Использую Jira как источник информации.");
|
||||
mercurial = _mercurialProviders.JiraCommitEmulator;
|
||||
}
|
||||
List<MercurialCommitItem> commits = mercurial.GetMercurialLog(settings, date, cancellationToken);
|
||||
List<TaskTimeItem> taskTimes = _taskTimeDiscoverer.CalculateTaskTime(commits, settings, cancellationToken);
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!taskTimes.Any())
|
||||
{
|
||||
_log.Warn($"{date:dd.MM.yyyy} вы не сделали ничего полезного =) Использую предыдущий день.");
|
||||
daysDiff--;
|
||||
if (daysDiff < -7)
|
||||
{
|
||||
_log.Error("Не нашли ни одного коммита за предыдущие 7 дней. Возможно вы в отпуске? Выхожу.");
|
||||
return;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
tasksProcessor.Process(date, taskTimes, settings, dummyMode);
|
||||
|
||||
_log.Info("Готово.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace JiraTimeBotForm
|
||||
{
|
||||
public class Logger : ILog
|
||||
{
|
||||
private readonly TextBox _txt;
|
||||
private readonly AppendLog _safeAppender;
|
||||
|
||||
public Logger(TextBox txt)
|
||||
{
|
||||
_txt = txt;
|
||||
_safeAppender = text => _txt.AppendText(text);
|
||||
|
||||
}
|
||||
|
||||
private void AppendText(string message)
|
||||
{
|
||||
message = $"{DateTime.Now:HH:mm} {message}";
|
||||
_txt.Invoke(_safeAppender, message + Environment.NewLine);
|
||||
}
|
||||
|
||||
private delegate void AppendLog(string text);
|
||||
|
||||
public void Info([Localizable(false)] string message)
|
||||
{
|
||||
AppendText(message);
|
||||
}
|
||||
|
||||
public void Trace([Localizable(false)] string message)
|
||||
{
|
||||
AppendText(message);
|
||||
}
|
||||
|
||||
public void Warn([Localizable(false)] string message)
|
||||
{
|
||||
AppendText(message);
|
||||
}
|
||||
|
||||
public void Error([Localizable(false)] string message)
|
||||
{
|
||||
AppendText(message);
|
||||
}
|
||||
}
|
||||
|
||||
public interface ILog
|
||||
{
|
||||
void Info([Localizable(false)] string message);
|
||||
void Trace([Localizable(false)] string message);
|
||||
void Warn([Localizable(false)] string message);
|
||||
void Error([Localizable(false)] string message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using JiraTimeBotForm.JiraIntegration;
|
||||
|
||||
namespace JiraTimeBotForm.Mercurial
|
||||
{
|
||||
public interface IAllMercurialProviders
|
||||
{
|
||||
MercurialLog MercurialLog { get; }
|
||||
JiraCommitEmulator JiraCommitEmulator { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.Mercurial.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.Mercurial
|
||||
{
|
||||
public interface IMercurialLog
|
||||
{
|
||||
List<MercurialCommitItem> GetMercurialLog(Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.Mercurial.Modifiers;
|
||||
using JiraTimeBotForm.Mercurial.Objects;
|
||||
using Mercurial;
|
||||
|
||||
namespace JiraTimeBotForm.Mercurial
|
||||
{
|
||||
public class MercurialLog: IMercurialLog
|
||||
{
|
||||
private readonly ILog _log;
|
||||
private readonly ICommitSkipper _commitSkipper;
|
||||
private readonly ITechnicalInfoSkipper _technicalInfoSkipper;
|
||||
|
||||
public MercurialLog(ILog log, ICommitSkipper commitSkipper, ITechnicalInfoSkipper technicalInfoSkipper)
|
||||
{
|
||||
_log = log;
|
||||
_commitSkipper = commitSkipper;
|
||||
_technicalInfoSkipper = technicalInfoSkipper;
|
||||
}
|
||||
|
||||
public List<MercurialCommitItem> GetMercurialLog(Settings settings, DateTime? date = null, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (string.IsNullOrEmpty(settings.RepositoryPath) || !Directory.Exists(settings.RepositoryPath))
|
||||
{
|
||||
_log.Error("Папка с репо не сушествует.");
|
||||
return new List<MercurialCommitItem>();
|
||||
}
|
||||
|
||||
date = date.GetValueOrDefault(DateTime.Now.Date);
|
||||
|
||||
var workTasks = new List<MercurialCommitItem>();
|
||||
foreach (var repoDirectory in Directory.GetDirectories(settings.RepositoryPath))
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return new List<MercurialCommitItem>();
|
||||
}
|
||||
|
||||
//var project = new DirectoryInfo(repoDirectory).Name;
|
||||
if (!Directory.Exists(Path.Combine(repoDirectory, ".hg")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var repo = new Repository(repoDirectory);
|
||||
var logCommand = new LogCommand
|
||||
{
|
||||
Date = date,
|
||||
Users = { settings.MercurialAuthorEmail },
|
||||
AdditionalArguments = { "--encoding=utf-8"},
|
||||
};
|
||||
|
||||
var log = repo.Log(logCommand);
|
||||
|
||||
foreach (var changeset in log)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return new List<MercurialCommitItem>();
|
||||
}
|
||||
|
||||
var commitMessage = FixEncoding(changeset.CommitMessage);
|
||||
if (_commitSkipper.IsNeedToSkip(changeset.Branch, commitMessage))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
commitMessage = _technicalInfoSkipper.StripTechnicalInfo(commitMessage);
|
||||
|
||||
workTasks.Add(new MercurialCommitItem
|
||||
{
|
||||
Description = commitMessage,
|
||||
Branch = changeset.Branch,
|
||||
Time = changeset.Timestamp,
|
||||
FilesAffected = changeset.PathActions.Count
|
||||
});
|
||||
_log?.Trace($" - Найден changeset: {changeset.Timestamp} - {changeset.Branch} - {changeset.AuthorEmailAddress} - {commitMessage}");
|
||||
}
|
||||
}
|
||||
if (!workTasks.Any())
|
||||
{
|
||||
return new List<MercurialCommitItem>();
|
||||
}
|
||||
|
||||
return workTasks;
|
||||
}
|
||||
|
||||
|
||||
private string FixEncoding(string source)
|
||||
{
|
||||
//перекодируем сообщение - ибо оно криво забирается в 1252
|
||||
Encoding srcEncodingFormat = Encoding.GetEncoding("windows-1252");
|
||||
byte[] originalByteString = srcEncodingFormat.GetBytes(source);
|
||||
var commitMessage = Encoding.UTF8.GetString(originalByteString);
|
||||
return commitMessage;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace JiraTimeBotForm.Mercurial.Modifiers
|
||||
{
|
||||
public class CommitSkipper : ICommitSkipper
|
||||
{
|
||||
public bool IsNeedToSkip(string branch, string commitMessage)
|
||||
{
|
||||
if (!branch.Contains("-"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
//Пропускаем Close и Merge коммиты
|
||||
if (commitMessage.StartsWith($"Close {branch} ", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
commitMessage.StartsWith($"Merge with ", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace JiraTimeBotForm.Mercurial.Modifiers
|
||||
{
|
||||
public interface ICommitSkipper
|
||||
{
|
||||
bool IsNeedToSkip(string branch, string commitMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace JiraTimeBotForm.Mercurial.Modifiers
|
||||
{
|
||||
public interface ITechnicalInfoSkipper
|
||||
{
|
||||
string StripTechnicalInfo(string commitMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Text;
|
||||
|
||||
namespace JiraTimeBotForm.Mercurial.Modifiers
|
||||
{
|
||||
public class TechnicalInfoSkipper : ITechnicalInfoSkipper
|
||||
{
|
||||
public string StripTechnicalInfo(string commitMessage)
|
||||
{
|
||||
if (!commitMessage.Contains("Signed-by:"))
|
||||
{
|
||||
return commitMessage;
|
||||
}
|
||||
|
||||
var arr = commitMessage.Split('\n');
|
||||
var sb = new StringBuilder();
|
||||
foreach (var itm in arr)
|
||||
{
|
||||
if (string.IsNullOrEmpty(itm))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (itm.StartsWith("Signed-by"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (itm.StartsWith("Jira:"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.AppendLine(itm);
|
||||
}
|
||||
|
||||
var message = sb.ToString();
|
||||
message = message.TrimEnd('\n');
|
||||
message = message.TrimEnd('\r');
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
|
||||
namespace JiraTimeBotForm.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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows.Forms;
|
||||
using JiraTimeBotForm.UI;
|
||||
|
||||
namespace JiraTimeBotForm
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
if (PriorProcess() != null)
|
||||
{
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
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("JiraTimeBotForm")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("JiraTimeBotForm")]
|
||||
[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.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace JiraTimeBotForm.Properties
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("JiraTimeBotForm.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace JiraTimeBotForm.Properties
|
||||
{
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.Mercurial;
|
||||
using JiraTimeBotForm.Mercurial.Objects;
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.TaskTime
|
||||
{
|
||||
public interface ITaskTimeCalculator
|
||||
{
|
||||
List<TaskTimeItem> CalculateTaskTime(List<MercurialCommitItem> commitItems, Settings settings, CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
|
||||
namespace JiraTimeBotForm.TaskTime.Objects
|
||||
{
|
||||
public class TaskTimeItem
|
||||
{
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.Mercurial;
|
||||
using JiraTimeBotForm.Mercurial.Objects;
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.TaskTime
|
||||
{
|
||||
public class TaskTimeByCommitsCalculator: ITaskTimeCalculator
|
||||
{
|
||||
private readonly ILog _log;
|
||||
|
||||
public TaskTimeByCommitsCalculator(ILog log)
|
||||
{
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public List<TaskTimeItem> CalculateTaskTime(List<MercurialCommitItem> commits, Settings settings, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
int remainMinutes = settings.MinuterPerWorkDay;
|
||||
int totalCommitsCount = commits.Count;
|
||||
|
||||
List<TaskTimeItem> workTimeItems = new List<TaskTimeItem>();
|
||||
|
||||
//Нам нужно раскидать 480 минут в день.
|
||||
foreach (var taskGroup in commits.GroupBy(f => f.Branch).OrderByDescending(f=>f.Count()))
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return new List<TaskTimeItem>();
|
||||
}
|
||||
|
||||
int currentTaskCommits = taskGroup.Count();
|
||||
int currentTaskTime = (int)RoundTo(settings.MinuterPerWorkDay / totalCommitsCount * currentTaskCommits, settings.RountToMinutes);
|
||||
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<TaskTimeItem>();
|
||||
}
|
||||
|
||||
if (remainMinutes != 0)
|
||||
{
|
||||
_log.Trace($"Погрешность распределения времени: {remainMinutes}. Добавляю к первой задаче.");
|
||||
}
|
||||
|
||||
//если переборщили или не достаточно добавили до 8 часов - скореектируем остаток в первой задаче (она самая трудозатратная).
|
||||
workTimeItems.First().Time += TimeSpan.FromMinutes(remainMinutes);
|
||||
|
||||
return workTimeItems;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private decimal RoundTo(decimal value, decimal to = 15, bool up = true)
|
||||
{
|
||||
if ((value % to) == 0)
|
||||
return value;
|
||||
if (up)
|
||||
return (value - (value % to) + to);
|
||||
|
||||
return (value - (value % to));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace JiraTimeBotForm.TasksProcessors
|
||||
{
|
||||
public interface IAllTasksProcessors
|
||||
{
|
||||
WorkLogTasksProcessor WorkLogTasksProcessor { get; }
|
||||
|
||||
MeetingProcessor MeetingProcessor { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.TasksProcessors
|
||||
{
|
||||
interface ITasksProcessor
|
||||
{
|
||||
void Process(DateTime date, List<TaskTimeItem> taskTimes, Settings settings, bool dummyMode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.JiraIntegration;
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.TasksProcessors
|
||||
{
|
||||
public class MeetingProcessor : ITasksProcessor
|
||||
{
|
||||
private readonly ILog _log;
|
||||
private readonly JiraApi _jiraApi;
|
||||
|
||||
public MeetingProcessor(ILog log, JiraApi jiraApi)
|
||||
{
|
||||
_log = log;
|
||||
_jiraApi = jiraApi;
|
||||
}
|
||||
|
||||
public void Process(DateTime date, List<TaskTimeItem> taskTimes, Settings settings, bool dummyMode)
|
||||
{
|
||||
_log.Trace($"На реальную дату {date:dd.MM.yyyy} распределение по задачам:");
|
||||
|
||||
foreach (var taskTime in taskTimes.OrderByDescending(f => f.Time))
|
||||
{
|
||||
var taskName = _jiraApi.GetTaskName(taskTime.Branch, settings);
|
||||
_log.Trace($" - [{taskTime.Branch}, коммитов {taskTime.Commits}]: {taskName} - {taskTime.Time}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.JiraIntegration;
|
||||
using JiraTimeBotForm.TaskTime;
|
||||
using JiraTimeBotForm.TaskTime.Objects;
|
||||
|
||||
namespace JiraTimeBotForm.TasksProcessors
|
||||
{
|
||||
public class WorkLogTasksProcessor : ITasksProcessor
|
||||
{
|
||||
private readonly ILog _log;
|
||||
private readonly JiraApi _jiraApi;
|
||||
|
||||
public WorkLogTasksProcessor(ILog log, JiraApi jiraApi)
|
||||
{
|
||||
_log = log;
|
||||
_jiraApi = jiraApi;
|
||||
}
|
||||
|
||||
public void Process(DateTime date, List<TaskTimeItem> taskTimes, Settings settings, bool dummyMode)
|
||||
{
|
||||
_log.Trace($"На реальную дату {date:dd.MM.yyyy} распределение по задачам:");
|
||||
foreach (var taskTime in taskTimes)
|
||||
{
|
||||
_log.Trace($"- {taskTime.Branch} (коммитов {taskTime.Commits}): {taskTime.Time}");
|
||||
}
|
||||
|
||||
_jiraApi.SetTodayWorklog(taskTimes, settings, dummy: dummyMode, addCommentsToWorklog: settings.AddCommentsToWorklog);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace JiraTimeBotForm.UI.Tray
|
||||
{
|
||||
public interface ITrayMenu
|
||||
{
|
||||
NotifyIcon Create(frmMain form);
|
||||
void Show();
|
||||
void Hide();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace JiraTimeBotForm.UI.Tray
|
||||
{
|
||||
public static class RestoreFromTray {
|
||||
[DllImport( "user32.dll" )]
|
||||
private static extern int ShowWindow( IntPtr hWnd, uint Msg );
|
||||
|
||||
private const uint SW_RESTORE = 0x09;
|
||||
|
||||
public static void Restore( this Form form )
|
||||
{
|
||||
if (form.WindowState == FormWindowState.Minimized)
|
||||
{
|
||||
ShowWindow(form.Handle, SW_RESTORE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace JiraTimeBotForm.UI.Tray
|
||||
{
|
||||
public class TrayMenu: ITrayMenu
|
||||
{
|
||||
private NotifyIcon _trayIcon;
|
||||
|
||||
public NotifyIcon Create(frmMain form)
|
||||
{
|
||||
var trayMenu = new ContextMenu();
|
||||
trayMenu.MenuItems.Add("Exit", OnExit);
|
||||
|
||||
_trayIcon = new NotifyIcon
|
||||
{
|
||||
Text = "JiraTimeBot",
|
||||
Icon = form.Icon,
|
||||
ContextMenu = trayMenu,
|
||||
Visible = false,
|
||||
Tag = form
|
||||
};
|
||||
_trayIcon.Click += btnTray_Click;
|
||||
_trayIcon.DoubleClick += btnTray_Click;
|
||||
|
||||
return _trayIcon;
|
||||
}
|
||||
|
||||
public void Show()
|
||||
{
|
||||
_trayIcon.Visible = true;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
_trayIcon.Visible = false;
|
||||
}
|
||||
|
||||
private void OnExit(object sender, EventArgs e)
|
||||
{
|
||||
Application.Exit();
|
||||
}
|
||||
|
||||
private void btnTray_Click(object sender, EventArgs e)
|
||||
{
|
||||
var icon = (NotifyIcon) sender;
|
||||
var form = (frmMain) icon.Tag;
|
||||
form.Visible = true;
|
||||
form.Activate();
|
||||
form.BringToFront();
|
||||
RestoreFromTray.Restore(form);
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+149
@@ -0,0 +1,149 @@
|
||||
namespace JiraTimeBotForm.UI
|
||||
{
|
||||
partial class frmMain
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.components = new System.ComponentModel.Container();
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmMain));
|
||||
this.txtLog = new System.Windows.Forms.TextBox();
|
||||
this.btnStart = new System.Windows.Forms.Button();
|
||||
this.ntfyIcon = new System.Windows.Forms.NotifyIcon(this.components);
|
||||
this.tmrStart = new System.Windows.Forms.Timer(this.components);
|
||||
this.txtDummyMode = new System.Windows.Forms.CheckBox();
|
||||
this.btnMeeting = new System.Windows.Forms.Button();
|
||||
this.btnCancel = new System.Windows.Forms.Button();
|
||||
this.btnSettings = new System.Windows.Forms.Button();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// txtLog
|
||||
//
|
||||
this.txtLog.Location = new System.Drawing.Point(132, 16);
|
||||
this.txtLog.Multiline = true;
|
||||
this.txtLog.Name = "txtLog";
|
||||
this.txtLog.ReadOnly = true;
|
||||
this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
|
||||
this.txtLog.Size = new System.Drawing.Size(596, 294);
|
||||
this.txtLog.TabIndex = 11;
|
||||
//
|
||||
// btnStart
|
||||
//
|
||||
this.btnStart.Location = new System.Drawing.Point(12, 229);
|
||||
this.btnStart.Name = "btnStart";
|
||||
this.btnStart.Size = new System.Drawing.Size(114, 23);
|
||||
this.btnStart.TabIndex = 12;
|
||||
this.btnStart.Text = "Проставить время";
|
||||
this.btnStart.UseVisualStyleBackColor = true;
|
||||
this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
|
||||
//
|
||||
// ntfyIcon
|
||||
//
|
||||
this.ntfyIcon.Text = "notifyIcon1";
|
||||
this.ntfyIcon.Visible = true;
|
||||
//
|
||||
// tmrStart
|
||||
//
|
||||
this.tmrStart.Interval = 1500;
|
||||
this.tmrStart.Tick += new System.EventHandler(this.tmrStart_Tick);
|
||||
//
|
||||
// txtDummyMode
|
||||
//
|
||||
this.txtDummyMode.AutoSize = true;
|
||||
this.txtDummyMode.Location = new System.Drawing.Point(12, 206);
|
||||
this.txtDummyMode.Name = "txtDummyMode";
|
||||
this.txtDummyMode.Size = new System.Drawing.Size(114, 17);
|
||||
this.txtDummyMode.TabIndex = 13;
|
||||
this.txtDummyMode.Text = "Тестовый прогон";
|
||||
this.txtDummyMode.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnMeeting
|
||||
//
|
||||
this.btnMeeting.Location = new System.Drawing.Point(12, 258);
|
||||
this.btnMeeting.Name = "btnMeeting";
|
||||
this.btnMeeting.Size = new System.Drawing.Size(114, 23);
|
||||
this.btnMeeting.TabIndex = 14;
|
||||
this.btnMeeting.Text = "Митинг";
|
||||
this.btnMeeting.UseVisualStyleBackColor = true;
|
||||
this.btnMeeting.Click += new System.EventHandler(this.btnMeeting_Click);
|
||||
//
|
||||
// btnCancel
|
||||
//
|
||||
this.btnCancel.Enabled = false;
|
||||
this.btnCancel.Location = new System.Drawing.Point(12, 287);
|
||||
this.btnCancel.Name = "btnCancel";
|
||||
this.btnCancel.Size = new System.Drawing.Size(114, 23);
|
||||
this.btnCancel.TabIndex = 15;
|
||||
this.btnCancel.Text = "Отменить операцию";
|
||||
this.btnCancel.UseVisualStyleBackColor = true;
|
||||
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
|
||||
//
|
||||
// btnSettings
|
||||
//
|
||||
this.btnSettings.Location = new System.Drawing.Point(12, 16);
|
||||
this.btnSettings.Name = "btnSettings";
|
||||
this.btnSettings.Size = new System.Drawing.Size(114, 23);
|
||||
this.btnSettings.TabIndex = 16;
|
||||
this.btnSettings.Text = "Настройки";
|
||||
this.btnSettings.UseVisualStyleBackColor = true;
|
||||
this.btnSettings.Click += new System.EventHandler(this.btnSettings_Click);
|
||||
//
|
||||
// frmMain
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(740, 320);
|
||||
this.Controls.Add(this.btnSettings);
|
||||
this.Controls.Add(this.btnCancel);
|
||||
this.Controls.Add(this.btnMeeting);
|
||||
this.Controls.Add(this.txtDummyMode);
|
||||
this.Controls.Add(this.btnStart);
|
||||
this.Controls.Add(this.txtLog);
|
||||
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
|
||||
this.Margin = new System.Windows.Forms.Padding(2);
|
||||
this.Name = "frmMain";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
|
||||
this.Text = "Бот логирования времени";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmMain_FormClosing);
|
||||
this.Load += new System.EventHandler(this.Form1_Load);
|
||||
this.Resize += new System.EventHandler(this.frmMain_Resize);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
private System.Windows.Forms.TextBox txtLog;
|
||||
private System.Windows.Forms.Button btnStart;
|
||||
private System.Windows.Forms.NotifyIcon ntfyIcon;
|
||||
private System.Windows.Forms.Timer tmrStart;
|
||||
private System.Windows.Forms.CheckBox txtDummyMode;
|
||||
private System.Windows.Forms.Button btnMeeting;
|
||||
private System.Windows.Forms.Button btnCancel;
|
||||
private System.Windows.Forms.Button btnSettings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Configuration;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using Autofac;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
using JiraTimeBotForm.DI;
|
||||
using JiraTimeBotForm.TasksProcessors;
|
||||
using JiraTimeBotForm.UI.Tray;
|
||||
|
||||
// This is the code for your desktop app.
|
||||
// Press Ctrl+F5 (or go to Debug > Start Without Debugging) to run your app.
|
||||
|
||||
namespace JiraTimeBotForm.UI
|
||||
{
|
||||
public partial class frmMain : Form
|
||||
{
|
||||
private readonly ILog _log;
|
||||
private readonly Job _job;
|
||||
private readonly IReadOnlyList<Control> _controls;
|
||||
private readonly IContainer _container;
|
||||
private readonly IAllTasksProcessors _tasksProcessors;
|
||||
private readonly ITrayMenu _trayIcon;
|
||||
private CancellationTokenSource _tokenSource;
|
||||
private Settings _settings;
|
||||
private readonly Action _settingsWindowShow;
|
||||
private readonly Action<string> _settingsErrorReporter;
|
||||
|
||||
private CancellationTokenSource GetTokenSource()
|
||||
{
|
||||
var tokenSource = new CancellationTokenSource();
|
||||
tokenSource.Token.Register(() =>
|
||||
{
|
||||
_log.Info("Операция отменена...");
|
||||
LockUnlock(true);
|
||||
});
|
||||
|
||||
return tokenSource;
|
||||
}
|
||||
|
||||
public frmMain()
|
||||
{
|
||||
InitializeComponent();
|
||||
_container = new Bootstrapper().Build(txtLog);
|
||||
|
||||
_trayIcon = _container.Resolve<ITrayMenu>();
|
||||
_trayIcon.Create(this);
|
||||
|
||||
_job = _container.Resolve<Job>();
|
||||
_log = _container.Resolve<ILog>();
|
||||
_tasksProcessors = _container.Resolve<IAllTasksProcessors>();
|
||||
|
||||
_controls = new Control[] { txtDummyMode, btnStart, btnMeeting, btnSettings };
|
||||
|
||||
_settingsWindowShow = () =>
|
||||
{
|
||||
var frmSettings = new frmSettings();
|
||||
frmSettings.ShowDialog(this);
|
||||
};
|
||||
|
||||
_settingsErrorReporter = msg => MessageBox.Show(msg, "Загрузка настроек", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);;
|
||||
}
|
||||
|
||||
private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
_trayIcon.Hide();
|
||||
}
|
||||
|
||||
public Settings ReadSettingsAndLock()
|
||||
{
|
||||
_settings = Settings.LoadAndCheck(_settingsWindowShow, _settingsErrorReporter);
|
||||
|
||||
LockUnlock(false);
|
||||
|
||||
return _settings;
|
||||
}
|
||||
|
||||
public void LockUnlock(bool enabled)
|
||||
{
|
||||
foreach (var control in _controls)
|
||||
{
|
||||
control.Enabled = enabled;
|
||||
}
|
||||
|
||||
btnCancel.Enabled = !enabled;
|
||||
tmrStart.Enabled = enabled;
|
||||
}
|
||||
|
||||
private void Form1_Load(object sender, EventArgs e)
|
||||
{
|
||||
PrintStartMessage();
|
||||
tmrStart.Enabled = true;
|
||||
}
|
||||
|
||||
private void PrintStartMessage()
|
||||
{
|
||||
_settings = Settings.LoadAndCheck(_settingsWindowShow, _settingsErrorReporter);
|
||||
_log.Info($"Загружен бот для {_settings.JiraUserName}, Режим: {_settings.WorkType.ToString()}, работаем в {_settings.RepositoryPath}");
|
||||
}
|
||||
|
||||
private async void btnStart_Click(object sender, EventArgs e)
|
||||
{
|
||||
var settings = ReadSettingsAndLock();
|
||||
|
||||
using (_tokenSource = GetTokenSource())
|
||||
{
|
||||
await _job.DoTheJob(settings, _tasksProcessors.WorkLogTasksProcessor, txtDummyMode.Checked, _tokenSource.Token);
|
||||
}
|
||||
|
||||
LockUnlock(true);
|
||||
}
|
||||
|
||||
private void frmMain_Resize(object sender, EventArgs e)
|
||||
{
|
||||
if (FormWindowState.Minimized == this.WindowState)
|
||||
{
|
||||
_trayIcon.Show();
|
||||
this.Hide();
|
||||
}
|
||||
|
||||
else if (FormWindowState.Normal == this.WindowState)
|
||||
{
|
||||
_trayIcon.Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private async void tmrStart_Tick(object sender, EventArgs e)
|
||||
{
|
||||
var runTime = _settings.ActivationTime;
|
||||
|
||||
if (Math.Abs((DateTime.Now.TimeOfDay - runTime).TotalSeconds) < 1)
|
||||
{
|
||||
if (DateTime.Now.DayOfWeek == DayOfWeek.Saturday || DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
|
||||
{
|
||||
_log.Error("Сегодня суббота или воскресенье. Работать нельзя =)");
|
||||
return;
|
||||
}
|
||||
|
||||
_log.Info("Автоматическое внесение времени.");
|
||||
|
||||
var settings = ReadSettingsAndLock();
|
||||
|
||||
await Task.Delay(2000);
|
||||
|
||||
if (txtDummyMode.Checked)
|
||||
{
|
||||
_log.Info("Выключаем режим Тестового прогона");
|
||||
}
|
||||
|
||||
using (var tokenSource = GetTokenSource())
|
||||
{
|
||||
await _job.DoTheJob(settings, _tasksProcessors.WorkLogTasksProcessor, txtDummyMode.Checked, tokenSource.Token);
|
||||
}
|
||||
|
||||
LockUnlock(true);
|
||||
}
|
||||
}
|
||||
|
||||
private async void btnMeeting_Click(object sender, EventArgs e)
|
||||
{
|
||||
var settings = ReadSettingsAndLock();
|
||||
|
||||
using (_tokenSource = GetTokenSource())
|
||||
{
|
||||
await _job.DoTheJob(settings, _tasksProcessors.MeetingProcessor, txtDummyMode.Checked, _tokenSource.Token);
|
||||
}
|
||||
|
||||
LockUnlock(true);
|
||||
}
|
||||
|
||||
private void btnCancel_Click(object sender, EventArgs e)
|
||||
{
|
||||
_tokenSource.Cancel();
|
||||
}
|
||||
|
||||
private void btnSettings_Click(object sender, EventArgs e)
|
||||
{
|
||||
var frm = new frmSettings();
|
||||
frm.ShowDialog();
|
||||
|
||||
PrintStartMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+263
@@ -0,0 +1,263 @@
|
||||
namespace JiraTimeBotForm.UI
|
||||
{
|
||||
partial class frmSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
this.label5 = new System.Windows.Forms.Label();
|
||||
this.txtRoundTo = new System.Windows.Forms.TextBox();
|
||||
this.label4 = new System.Windows.Forms.Label();
|
||||
this.txtRepoPath = new System.Windows.Forms.TextBox();
|
||||
this.label2 = new System.Windows.Forms.Label();
|
||||
this.txtMercurialEmail = new System.Windows.Forms.TextBox();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.txtJiraPassword = new System.Windows.Forms.TextBox();
|
||||
this.lbl1 = new System.Windows.Forms.Label();
|
||||
this.txtJiraLogin = new System.Windows.Forms.TextBox();
|
||||
this.label3 = new System.Windows.Forms.Label();
|
||||
this.actTime = new System.Windows.Forms.DateTimePicker();
|
||||
this.chkAddComments = new System.Windows.Forms.CheckBox();
|
||||
this.btnSave = new System.Windows.Forms.Button();
|
||||
this.cboWorkType = new System.Windows.Forms.ComboBox();
|
||||
this.label6 = new System.Windows.Forms.Label();
|
||||
this.label7 = new System.Windows.Forms.Label();
|
||||
this.txtJQL = new System.Windows.Forms.TextBox();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// label5
|
||||
//
|
||||
this.label5.AutoSize = true;
|
||||
this.label5.Location = new System.Drawing.Point(7, 153);
|
||||
this.label5.Name = "label5";
|
||||
this.label5.Size = new System.Drawing.Size(100, 13);
|
||||
this.label5.TabIndex = 28;
|
||||
this.label5.Text = "Округл. времени*:";
|
||||
//
|
||||
// txtRoundTo
|
||||
//
|
||||
this.txtRoundTo.Location = new System.Drawing.Point(109, 150);
|
||||
this.txtRoundTo.Name = "txtRoundTo";
|
||||
this.txtRoundTo.Size = new System.Drawing.Size(100, 20);
|
||||
this.txtRoundTo.TabIndex = 27;
|
||||
this.txtRoundTo.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.txtRoundTo_KeyPress);
|
||||
//
|
||||
// label4
|
||||
//
|
||||
this.label4.AutoSize = true;
|
||||
this.label4.Location = new System.Drawing.Point(27, 48);
|
||||
this.label4.Name = "label4";
|
||||
this.label4.Size = new System.Drawing.Size(76, 13);
|
||||
this.label4.TabIndex = 26;
|
||||
this.label4.Text = "Путь до репо:";
|
||||
//
|
||||
// txtRepoPath
|
||||
//
|
||||
this.txtRepoPath.Location = new System.Drawing.Point(109, 45);
|
||||
this.txtRepoPath.Name = "txtRepoPath";
|
||||
this.txtRepoPath.Size = new System.Drawing.Size(100, 20);
|
||||
this.txtRepoPath.TabIndex = 25;
|
||||
//
|
||||
// label2
|
||||
//
|
||||
this.label2.AutoSize = true;
|
||||
this.label2.Location = new System.Drawing.Point(21, 127);
|
||||
this.label2.Name = "label2";
|
||||
this.label2.Size = new System.Drawing.Size(82, 13);
|
||||
this.label2.TabIndex = 24;
|
||||
this.label2.Text = "Mercurial name:";
|
||||
//
|
||||
// txtMercurialEmail
|
||||
//
|
||||
this.txtMercurialEmail.Location = new System.Drawing.Point(109, 124);
|
||||
this.txtMercurialEmail.Name = "txtMercurialEmail";
|
||||
this.txtMercurialEmail.Size = new System.Drawing.Size(100, 20);
|
||||
this.txtMercurialEmail.TabIndex = 23;
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Location = new System.Drawing.Point(36, 100);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(71, 13);
|
||||
this.label1.TabIndex = 22;
|
||||
this.label1.Text = "Пароль Jira*:";
|
||||
//
|
||||
// txtJiraPassword
|
||||
//
|
||||
this.txtJiraPassword.Location = new System.Drawing.Point(109, 97);
|
||||
this.txtJiraPassword.Name = "txtJiraPassword";
|
||||
this.txtJiraPassword.PasswordChar = '*';
|
||||
this.txtJiraPassword.Size = new System.Drawing.Size(100, 20);
|
||||
this.txtJiraPassword.TabIndex = 21;
|
||||
//
|
||||
// lbl1
|
||||
//
|
||||
this.lbl1.AutoSize = true;
|
||||
this.lbl1.Location = new System.Drawing.Point(43, 74);
|
||||
this.lbl1.Name = "lbl1";
|
||||
this.lbl1.Size = new System.Drawing.Size(64, 13);
|
||||
this.lbl1.TabIndex = 20;
|
||||
this.lbl1.Text = "Логин Jira*:";
|
||||
//
|
||||
// txtJiraLogin
|
||||
//
|
||||
this.txtJiraLogin.Location = new System.Drawing.Point(109, 71);
|
||||
this.txtJiraLogin.Name = "txtJiraLogin";
|
||||
this.txtJiraLogin.Size = new System.Drawing.Size(100, 20);
|
||||
this.txtJiraLogin.TabIndex = 19;
|
||||
//
|
||||
// label3
|
||||
//
|
||||
this.label3.AutoSize = true;
|
||||
this.label3.Location = new System.Drawing.Point(16, 206);
|
||||
this.label3.Name = "label3";
|
||||
this.label3.Size = new System.Drawing.Size(91, 13);
|
||||
this.label3.TabIndex = 30;
|
||||
this.label3.Text = "Время запуска*:";
|
||||
//
|
||||
// actTime
|
||||
//
|
||||
this.actTime.Format = System.Windows.Forms.DateTimePickerFormat.Time;
|
||||
this.actTime.Location = new System.Drawing.Point(109, 202);
|
||||
this.actTime.Name = "actTime";
|
||||
this.actTime.Size = new System.Drawing.Size(100, 20);
|
||||
this.actTime.TabIndex = 29;
|
||||
//
|
||||
// chkAddComments
|
||||
//
|
||||
this.chkAddComments.AutoSize = true;
|
||||
this.chkAddComments.Location = new System.Drawing.Point(46, 242);
|
||||
this.chkAddComments.Name = "chkAddComments";
|
||||
this.chkAddComments.Size = new System.Drawing.Size(133, 17);
|
||||
this.chkAddComments.TabIndex = 31;
|
||||
this.chkAddComments.Text = "Добавлять описание";
|
||||
this.chkAddComments.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// btnSave
|
||||
//
|
||||
this.btnSave.Location = new System.Drawing.Point(61, 276);
|
||||
this.btnSave.Name = "btnSave";
|
||||
this.btnSave.Size = new System.Drawing.Size(75, 23);
|
||||
this.btnSave.TabIndex = 32;
|
||||
this.btnSave.Text = "Сохранить";
|
||||
this.btnSave.UseVisualStyleBackColor = true;
|
||||
this.btnSave.Click += new System.EventHandler(this.btnSave_Click);
|
||||
//
|
||||
// cboWorkType
|
||||
//
|
||||
this.cboWorkType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
|
||||
this.cboWorkType.FormattingEnabled = true;
|
||||
this.cboWorkType.Items.AddRange(new object[] {
|
||||
"Mercurial",
|
||||
"Jira"});
|
||||
this.cboWorkType.Location = new System.Drawing.Point(109, 18);
|
||||
this.cboWorkType.Name = "cboWorkType";
|
||||
this.cboWorkType.Size = new System.Drawing.Size(99, 21);
|
||||
this.cboWorkType.TabIndex = 34;
|
||||
this.cboWorkType.SelectedIndexChanged += new System.EventHandler(this.cboWorkType_SelectedIndexChanged);
|
||||
//
|
||||
// label6
|
||||
//
|
||||
this.label6.AutoSize = true;
|
||||
this.label6.Location = new System.Drawing.Point(18, 21);
|
||||
this.label6.Name = "label6";
|
||||
this.label6.Size = new System.Drawing.Size(85, 13);
|
||||
this.label6.TabIndex = 35;
|
||||
this.label6.Text = "Режим работы:";
|
||||
//
|
||||
// label7
|
||||
//
|
||||
this.label7.AutoSize = true;
|
||||
this.label7.Location = new System.Drawing.Point(74, 179);
|
||||
this.label7.Name = "label7";
|
||||
this.label7.Size = new System.Drawing.Size(29, 13);
|
||||
this.label7.TabIndex = 37;
|
||||
this.label7.Text = "JQL:";
|
||||
//
|
||||
// txtJQL
|
||||
//
|
||||
this.txtJQL.Location = new System.Drawing.Point(109, 176);
|
||||
this.txtJQL.Name = "txtJQL";
|
||||
this.txtJQL.Size = new System.Drawing.Size(99, 20);
|
||||
this.txtJQL.TabIndex = 36;
|
||||
//
|
||||
// frmSettings
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(220, 311);
|
||||
this.Controls.Add(this.label7);
|
||||
this.Controls.Add(this.txtJQL);
|
||||
this.Controls.Add(this.label6);
|
||||
this.Controls.Add(this.cboWorkType);
|
||||
this.Controls.Add(this.btnSave);
|
||||
this.Controls.Add(this.chkAddComments);
|
||||
this.Controls.Add(this.label3);
|
||||
this.Controls.Add(this.actTime);
|
||||
this.Controls.Add(this.label5);
|
||||
this.Controls.Add(this.txtRoundTo);
|
||||
this.Controls.Add(this.label4);
|
||||
this.Controls.Add(this.txtRepoPath);
|
||||
this.Controls.Add(this.label2);
|
||||
this.Controls.Add(this.txtMercurialEmail);
|
||||
this.Controls.Add(this.label1);
|
||||
this.Controls.Add(this.txtJiraPassword);
|
||||
this.Controls.Add(this.lbl1);
|
||||
this.Controls.Add(this.txtJiraLogin);
|
||||
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
|
||||
this.MaximizeBox = false;
|
||||
this.Name = "frmSettings";
|
||||
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
|
||||
this.Text = "Настройки";
|
||||
this.Load += new System.EventHandler(this.frmSettings_Load);
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.Label label5;
|
||||
private System.Windows.Forms.TextBox txtRoundTo;
|
||||
private System.Windows.Forms.Label label4;
|
||||
private System.Windows.Forms.TextBox txtRepoPath;
|
||||
private System.Windows.Forms.Label label2;
|
||||
private System.Windows.Forms.TextBox txtMercurialEmail;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.TextBox txtJiraPassword;
|
||||
private System.Windows.Forms.Label lbl1;
|
||||
private System.Windows.Forms.TextBox txtJiraLogin;
|
||||
private System.Windows.Forms.Label label3;
|
||||
private System.Windows.Forms.DateTimePicker actTime;
|
||||
private System.Windows.Forms.CheckBox chkAddComments;
|
||||
private System.Windows.Forms.Button btnSave;
|
||||
private System.Windows.Forms.ComboBox cboWorkType;
|
||||
private System.Windows.Forms.Label label6;
|
||||
private System.Windows.Forms.Label label7;
|
||||
private System.Windows.Forms.TextBox txtJQL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Windows.Forms;
|
||||
using JiraTimeBotForm.Configuration;
|
||||
|
||||
namespace JiraTimeBotForm.UI
|
||||
{
|
||||
public partial class frmSettings : Form
|
||||
{
|
||||
public frmSettings()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void frmSettings_Load(object sender, EventArgs e)
|
||||
{
|
||||
var settings = Settings.Load();
|
||||
if (settings != null)
|
||||
{
|
||||
SetSettings(settings);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetSettings(Settings settings)
|
||||
{
|
||||
txtJiraLogin.Text = settings.JiraUserName;
|
||||
txtJiraPassword.Text = settings.JiraPassword;
|
||||
txtMercurialEmail.Text = settings.MercurialAuthorEmail;
|
||||
txtRepoPath.Text = settings.RepositoryPath;
|
||||
actTime.Text = settings.ActivationTime.ToString("hh\\:mm\\:ss");
|
||||
//txtDummyMode.Checked = settings.DummyMode;
|
||||
chkAddComments.Checked = settings.AddCommentsToWorklog;
|
||||
txtRoundTo.Text = settings.RountToMinutes.ToString();
|
||||
cboWorkType.SelectedIndex = (int) settings.WorkType;
|
||||
txtJQL.Text = settings.JiraQuery;
|
||||
txtJQL.Enabled = settings.WorkType == WorkType.JiraLogs;
|
||||
}
|
||||
|
||||
public Settings ReadSettingsAndLock()
|
||||
{
|
||||
if (!int.TryParse(txtRoundTo.Text, out var roundTo))
|
||||
{
|
||||
roundTo = 15;
|
||||
}
|
||||
|
||||
var settings = new Settings
|
||||
{
|
||||
JiraUserName = txtJiraLogin.Text,
|
||||
JiraPassword = txtJiraPassword.Text,
|
||||
MercurialAuthorEmail = txtMercurialEmail.Text,
|
||||
ActivationTime = TimeSpan.Parse(actTime.Text),
|
||||
RepositoryPath = txtRepoPath.Text,
|
||||
//DummyMode = txtDummyMode.Checked,
|
||||
AddCommentsToWorklog = chkAddComments.Checked,
|
||||
RountToMinutes = roundTo,
|
||||
WorkType = (WorkType) cboWorkType.SelectedIndex,
|
||||
JiraQuery = txtJQL.Text,
|
||||
};
|
||||
|
||||
//LockUnlock(false);
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
private void btnSave_Click(object sender, EventArgs e)
|
||||
{
|
||||
var settings = ReadSettingsAndLock();
|
||||
settings.Save();
|
||||
|
||||
MessageBox.Show("Настройки сохранены.");
|
||||
|
||||
DialogResult = DialogResult.OK;
|
||||
this.Close();
|
||||
}
|
||||
|
||||
private void txtRoundTo_KeyPress(object sender, KeyPressEventArgs e)
|
||||
{
|
||||
e.Handled = !char.IsDigit(e.KeyChar) && !char.IsControl(e.KeyChar);
|
||||
}
|
||||
|
||||
private void cboWorkType_SelectedIndexChanged(object sender, EventArgs e)
|
||||
{
|
||||
txtJQL.Enabled = (cboWorkType.SelectedIndex == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="cboWorkType.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
</root>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Atlassian.SDK" version="10.4.0" targetFramework="net461" />
|
||||
<package id="Autofac" version="4.9.1" targetFramework="net461" />
|
||||
<package id="Autofac.Extras.AggregateService" version="4.1.0" targetFramework="net461" />
|
||||
<package id="Castle.Core" version="4.0.0" targetFramework="net461" />
|
||||
<package id="Mercurial.Net" version="1.1.1.607" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="12.0.1" targetFramework="net461" />
|
||||
<package id="NLog" version="4.5.11" targetFramework="net461" />
|
||||
<package id="RestSharp" version="106.6.9" targetFramework="net461" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user