Skip to content

Commit

Permalink
🧹 EOLNs
Browse files Browse the repository at this point in the history
  • Loading branch information
nop77svk committed Oct 28, 2024
1 parent 56d164e commit c4b7889
Show file tree
Hide file tree
Showing 32 changed files with 1,542 additions and 1,542 deletions.
94 changes: 47 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,47 @@
# Jira Worklogger

<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />Licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.

## The problem

Do you have to track your work by means of Jira issue worklogs? Is the Jira GUI _clickfest_ approach making your neurotic inner beast emerge on the surface?

## The solution

Meet the scripted Jira worklogging! Give it your worklogs in a CSV file (and your server URI and your user credentials in a config file) and let the automaton do the rest!

## Prerequisites

- .NET 6 run-time installed (for simple, cross-platform build) or no .NET runtime necessary (for self-contained, single-exe, Windows-only build); You choose!
- Jira server (with version 2 REST API)
- "vanilla" Jira server support: ✔️
- "Tempo Timesheets" plugin support: ✔️
- "ICTime" plugin support: ❎ (planned)

## Configuration

The jwl.config file is a simple JSON structure. It can be placed in (and will be read by jwl in the priority order of)
- "current folder" (as in "where your shell's <code>%CD%</code> or <code>${PWD}</code> is at the moment")
- local application data (<code>%USERPROFILE%\AppData\Local</code>)
- roaming application data (<code>%USERPROFILE%\AppData\Roaming</code>)
- jwl's "installation" folder

As for the CLI worklogger binary, there are command-line options available as well. Any partial options supplied via CLI will override their respective jwl.config counterparts with the highest priority.

### "ServerClass" setting

Available values are:
- Vanilla
- TempoTimeSheets
- ICTime (not implemented yet)

## The input CSV structure

Five columns, data delimited (by default) by a colon:
- <code>Date</code> (string) - worklog day date (valid formats: <code>YYYY-MM-DD</code>, <code>YYYY/MM/DD</code>, <code>DD.MM.YYYY</code>, all with optional <code> HH:MI:SS</code> part)
- <code>IssueKey</code> (string) - Jira issue key (<code>SOMEPROJECT-1234</code> and the likes)
- <code>Activity</code> (string) - Tempo Timesheets worklog type or ICTime activity; values are remapped
- <code>TimeSpent</code> (string) - time to be logged for the Jira issue and the date (valid formats: <code>HH:MI</code>, <code>MI</code>, <code>HH h MI</code>, <code>HH h MI m</code>)
- <code>Comment</code> (string) - optional worklog comment

The <code>Activity</code> values are remapped via config <code>$.JiraServer.ActivityMap</code>. The mapping configuration depends on your Jira server+plugins configuration and is a subject of manual setup by yourself.
# Jira Worklogger

<a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/"><img alt="Creative Commons License" style="border-width:0" src="https://i.creativecommons.org/l/by-sa/4.0/88x31.png" /></a><br />Licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-sa/4.0/">Creative Commons Attribution-ShareAlike 4.0 International License</a>.

## The problem

Do you have to track your work by means of Jira issue worklogs? Is the Jira GUI _clickfest_ approach making your neurotic inner beast emerge on the surface?

## The solution

Meet the scripted Jira worklogging! Give it your worklogs in a CSV file (and your server URI and your user credentials in a config file) and let the automaton do the rest!

## Prerequisites

- .NET 6 run-time installed (for simple, cross-platform build) or no .NET runtime necessary (for self-contained, single-exe, Windows-only build); You choose!
- Jira server (with version 2 REST API)
- "vanilla" Jira server support: ✔️
- "Tempo Timesheets" plugin support: ✔️
- "ICTime" plugin support: ❎ (planned)

## Configuration

The jwl.config file is a simple JSON structure. It can be placed in (and will be read by jwl in the priority order of)
- "current folder" (as in "where your shell's <code>%CD%</code> or <code>${PWD}</code> is at the moment")
- local application data (<code>%USERPROFILE%\AppData\Local</code>)
- roaming application data (<code>%USERPROFILE%\AppData\Roaming</code>)
- jwl's "installation" folder

As for the CLI worklogger binary, there are command-line options available as well. Any partial options supplied via CLI will override their respective jwl.config counterparts with the highest priority.

### "ServerClass" setting

Available values are:
- Vanilla
- TempoTimeSheets
- ICTime (not implemented yet)

## The input CSV structure

Five columns, data delimited (by default) by a colon:
- <code>Date</code> (string) - worklog day date (valid formats: <code>YYYY-MM-DD</code>, <code>YYYY/MM/DD</code>, <code>DD.MM.YYYY</code>, all with optional <code> HH:MI:SS</code> part)
- <code>IssueKey</code> (string) - Jira issue key (<code>SOMEPROJECT-1234</code> and the likes)
- <code>Activity</code> (string) - Tempo Timesheets worklog type or ICTime activity; values are remapped
- <code>TimeSpent</code> (string) - time to be logged for the Jira issue and the date (valid formats: <code>HH:MI</code>, <code>MI</code>, <code>HH h MI</code>, <code>HH h MI m</code>)
- <code>Comment</code> (string) - optional worklog comment

The <code>Activity</code> values are remapped via config <code>$.JiraServer.ActivityMap</code>. The mapping configuration depends on your Jira server+plugins configuration and is a subject of manual setup by yourself.
6 changes: 3 additions & 3 deletions _local_build.cmd
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pushd jwl.console
call global_build.cmd jira-worklogger
@exit /b %ERRORLEVEL%
pushd jwl.console
call global_build.cmd jira-worklogger
@exit /b %ERRORLEVEL%
150 changes: 75 additions & 75 deletions jwl.console/CLI.cs
Original file line number Diff line number Diff line change
@@ -1,76 +1,76 @@
namespace jwl.console;
using CommandLine;

[Verb("fill", isDefault: true, HelpText = "Fill Jira with worklogs")]
public class FillCLI
{
[Option('v', "verbose", HelpText = "\nGive more verbose feedback\nNote: Not implemented yet! 2do! :-)", Default = false, Hidden = true)]
public bool UseVerboseFeedback { get; set; }

[Option('i', "input", HelpText = "\nInput CSVs with the worklogs", Separator = ',', Required = true)]
public IEnumerable<string> InputFiles { get; set; } = new string[0];

[Option("ifs", HelpText = "Input CSV fields delimiter"
+ $"\nJSON config: $.{nameof(core.AppConfig.CsvOptions)}.{nameof(inputs.CsvFormatConfig.FieldDelimiter)}")]
public string? FieldDelimiter { get; set; }

[Option('t', "target", HelpText = "Connection string to Jira server in the form of <user name>@<server host>[:<server port>]"
+ "\nNote: The \"https://\" scheme is automatically asserted with this option!"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.BaseUrl)} for <server host>[:<server port>]"
+ $"\nJSON config: $.{nameof(core.AppConfig.User)}.{nameof(core.UserConfig.Name)} for <user name>")]
public string? UserCredentials { get; set; }

[Option("server-flavour", HelpText = "Jira server flavour (whether vanilla or with some timesheet plugins)"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.Flavour)}"
+ $"\nAvailable values: {nameof(jira.JiraServerFlavour.Vanilla)}, {nameof(jira.JiraServerFlavour.TempoTimeSheets)}, {nameof(jira.JiraServerFlavour.ICTime)}")]
public string? ServerFlavour { get; set; }

[Option("no-proxy", HelpText = "Turn off proxying the HTTP(S) connections to Jira server"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.UseProxy)} (negated!)")]
public bool? NoProxy { get; set; }

[Option("max-connections-per-server", HelpText = "Limit the number of concurrent connections to Jira server"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.MaxConnectionsPerServer)}")]
public int? MaxConnectionsPerServer { get; set; }

[Option("no-ssl-cert-check", HelpText = "Skip SSL/TLS certificate checks (for self-signed certificates)"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.SkipSslCertificateCheck)}")]
public bool? SkipSslCertificateCheck { get; set; }

public core.AppConfig ToAppConfig()
{
string[] connectionSpecifierSplit = UserCredentials?.Split('@', 2, StringSplitOptions.TrimEntries) ?? Array.Empty<string>();

string? jiraServerSpecification = connectionSpecifierSplit.Length > 1 ? connectionSpecifierSplit[1] : null;
if (!jiraServerSpecification?.Contains(@"://") ?? false)
jiraServerSpecification = @"https://" + jiraServerSpecification;

string? jiraUserCredentials = connectionSpecifierSplit.Any() ? connectionSpecifierSplit[0] : null;
string[] jiraUserCredentialsSplit = jiraUserCredentials?.Split('/', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
string? jiraUserName = jiraUserCredentialsSplit.Any() ? jiraUserCredentialsSplit[0] : null;
string? jiraUserPassword = jiraUserCredentialsSplit.Length > 1 ? jiraUserCredentialsSplit[1] : null;

return new core.AppConfig()
{
UseVerboseFeedback = UseVerboseFeedback,
JiraServer = new jira.ServerConfig()
{
Flavour = ServerFlavour,
FlavourOptions = null,
BaseUrl = jiraServerSpecification,
UseProxy = !NoProxy,
MaxConnectionsPerServer = MaxConnectionsPerServer,
SkipSslCertificateCheck = SkipSslCertificateCheck
},
User = new core.UserConfig()
{
Name = jiraUserName,
Password = jiraUserPassword
},
CsvOptions = new inputs.CsvFormatConfig()
{
FieldDelimiter = FieldDelimiter
}
};
}
namespace jwl.console;
using CommandLine;

[Verb("fill", isDefault: true, HelpText = "Fill Jira with worklogs")]
public class FillCLI
{
[Option('v', "verbose", HelpText = "\nGive more verbose feedback\nNote: Not implemented yet! 2do! :-)", Default = false, Hidden = true)]
public bool UseVerboseFeedback { get; set; }

[Option('i', "input", HelpText = "\nInput CSVs with the worklogs", Separator = ',', Required = true)]
public IEnumerable<string> InputFiles { get; set; } = new string[0];

[Option("ifs", HelpText = "Input CSV fields delimiter"
+ $"\nJSON config: $.{nameof(core.AppConfig.CsvOptions)}.{nameof(inputs.CsvFormatConfig.FieldDelimiter)}")]
public string? FieldDelimiter { get; set; }

[Option('t', "target", HelpText = "Connection string to Jira server in the form of <user name>@<server host>[:<server port>]"
+ "\nNote: The \"https://\" scheme is automatically asserted with this option!"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.BaseUrl)} for <server host>[:<server port>]"
+ $"\nJSON config: $.{nameof(core.AppConfig.User)}.{nameof(core.UserConfig.Name)} for <user name>")]
public string? UserCredentials { get; set; }

[Option("server-flavour", HelpText = "Jira server flavour (whether vanilla or with some timesheet plugins)"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.Flavour)}"
+ $"\nAvailable values: {nameof(jira.JiraServerFlavour.Vanilla)}, {nameof(jira.JiraServerFlavour.TempoTimeSheets)}, {nameof(jira.JiraServerFlavour.ICTime)}")]
public string? ServerFlavour { get; set; }

[Option("no-proxy", HelpText = "Turn off proxying the HTTP(S) connections to Jira server"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.UseProxy)} (negated!)")]
public bool? NoProxy { get; set; }

[Option("max-connections-per-server", HelpText = "Limit the number of concurrent connections to Jira server"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.MaxConnectionsPerServer)}")]
public int? MaxConnectionsPerServer { get; set; }

[Option("no-ssl-cert-check", HelpText = "Skip SSL/TLS certificate checks (for self-signed certificates)"
+ $"\nJSON config: $.{nameof(core.AppConfig.JiraServer)}.{nameof(jira.ServerConfig.SkipSslCertificateCheck)}")]
public bool? SkipSslCertificateCheck { get; set; }

public core.AppConfig ToAppConfig()
{
string[] connectionSpecifierSplit = UserCredentials?.Split('@', 2, StringSplitOptions.TrimEntries) ?? Array.Empty<string>();

string? jiraServerSpecification = connectionSpecifierSplit.Length > 1 ? connectionSpecifierSplit[1] : null;
if (!jiraServerSpecification?.Contains(@"://") ?? false)
jiraServerSpecification = @"https://" + jiraServerSpecification;

string? jiraUserCredentials = connectionSpecifierSplit.Any() ? connectionSpecifierSplit[0] : null;
string[] jiraUserCredentialsSplit = jiraUserCredentials?.Split('/', 2, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
string? jiraUserName = jiraUserCredentialsSplit.Any() ? jiraUserCredentialsSplit[0] : null;
string? jiraUserPassword = jiraUserCredentialsSplit.Length > 1 ? jiraUserCredentialsSplit[1] : null;

return new core.AppConfig()
{
UseVerboseFeedback = UseVerboseFeedback,
JiraServer = new jira.ServerConfig()
{
Flavour = ServerFlavour,
FlavourOptions = null,
BaseUrl = jiraServerSpecification,
UseProxy = !NoProxy,
MaxConnectionsPerServer = MaxConnectionsPerServer,
SkipSslCertificateCheck = SkipSslCertificateCheck
},
User = new core.UserConfig()
{
Name = jiraUserName,
Password = jiraUserPassword
},
CsvOptions = new inputs.CsvFormatConfig()
{
FieldDelimiter = FieldDelimiter
}
};
}
}
Loading

0 comments on commit c4b7889

Please sign in to comment.