-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
32 changed files
with
1,542 additions
and
1,542 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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% |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
}; | ||
} | ||
} |
Oops, something went wrong.