Skip to content

Commit a19e940

Browse files
authored
Merge pull request #13 from nop77svk/feature/10-ictime-plugin-support
➕ ICTime plugin support
2 parents 33f4635 + 7d291a0 commit a19e940

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2916
-619
lines changed

GlobalSuppressions.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,25 @@
55

66
[assembly: SuppressMessage("Style", "IDE0017")] // Object properties initialization can be simplified
77
[assembly: SuppressMessage("Style", "IDE0057")] // Substring can be simplified
8-
[assembly: SuppressMessage("Style", "IDE0060")] // Remove unused parameter // 2do!
8+
[assembly: SuppressMessage("Style", "IDE0060")] // Remove unused parameter
99
[assembly: SuppressMessage("Style", "IDE0063")] // Use simple 'using' statement
1010
[assembly: SuppressMessage("Style", "IDE0090")] // Use new (...)
1111

1212
[assembly: SuppressMessage("Usage", "CA1816")] // Dispose methods should call SuppressFinalize
13-
[assembly: SuppressMessage("Style", "CA2208")] // Wrong paramName to Argument...Exception
1413

1514
[assembly: SuppressMessage("Style", "SA1009")] // Closing parenthesis should not be preceded by a space
1615
[assembly: SuppressMessage("Style", "SA1011")] // Closing square bracket should be followed by a space
17-
[assembly: SuppressMessage("Style", "SA1021")] // Negative signe should be preceded by a space
16+
[assembly: SuppressMessage("Style", "SA1021")] // Negative sign should be preceded by a space
1817
[assembly: SuppressMessage("Style", "SA1101")] // Prefix local calls with this
1918
[assembly: SuppressMessage("Style", "SA1111")] // Closing parenthesis should be on line of last parameter
20-
[assembly: SuppressMessage("Style", "SA1118")] // parameter spans multiple lines // 2do!
21-
[assembly: SuppressMessage("Style", "SA1122")] // Use string.Empty // 2do!
19+
[assembly: SuppressMessage("Style", "SA1118")] // parameter spans multiple lines
2220
[assembly: SuppressMessage("Style", "SA1201")] // Enum should not follow a class
2321
[assembly: SuppressMessage("Style", "SA1300")] // Element ... should begin with an uppercase letter
2422
[assembly: SuppressMessage("Style", "SA1309")] // Field should not begin with an underscore
2523
[assembly: SuppressMessage("Style", "SA1408")] // Conditional expressions should declare precedence
2624
[assembly: SuppressMessage("Style", "SA1413")] // Use trailing comma in multi-line initializers
2725
[assembly: SuppressMessage("Style", "SA1503")] // Braces should not be ommitted
28-
[assembly: SuppressMessage("Style", "SA1515")] // 1-line comments should be preceded by a blank line // 2do!
26+
[assembly: SuppressMessage("Style", "SA1515")] // 1-line comments should be preceded by a blank line
2927
[assembly: SuppressMessage("Style", "SA1516")] // Elements should be separated by a blank line
3028

3129
[assembly: SuppressMessage("Style", "SA0001")] // XML comment analysis disabled

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Meet the scripted Jira worklogging! Give it your worklogs in a CSV file (and you
1616
- Jira server (with version 2 REST API)
1717
- "vanilla" Jira server support: ✔️
1818
- "Tempo Timesheets" plugin support: ✔️
19-
- "ICTime" plugin support: ❎ (planned)
19+
- "ICTime" plugin support: ✔️
2020

2121
## Configuration
2222

@@ -33,7 +33,7 @@ As for the CLI worklogger binary, there are command-line options available as we
3333
Available values are:
3434
- Vanilla
3535
- TempoTimeSheets
36-
- ICTime (not implemented yet)
36+
- ICTime
3737

3838
## The input CSV structure
3939

@@ -43,5 +43,3 @@ Five columns, data delimited (by default) by a colon:
4343
- <code>Activity</code> (string) - Tempo Timesheets worklog type or ICTime activity; values are remapped
4444
- <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>)
4545
- <code>Comment</code> (string) - optional worklog comment
46-
47-
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.sln

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jwl.infra", "jwl.infra\jwl.
1313
EndProject
1414
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jwl.core", "jwl.core\jwl.core.csproj", "{DA3C8E30-0358-4103-97E0-29740E648A27}"
1515
EndProject
16+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "jwl.wadl", "jwl.wadl\jwl.wadl.csproj", "{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}"
17+
EndProject
18+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "jwl.test", "jwl.test\jwl.test.csproj", "{35704A9C-16A2-4F64-9042-C59421FCDE9B}"
19+
EndProject
1620
Global
1721
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1822
Debug|Any CPU = Debug|Any CPU
@@ -83,6 +87,30 @@ Global
8387
{DA3C8E30-0358-4103-97E0-29740E648A27}.Release|x64.Build.0 = Release|Any CPU
8488
{DA3C8E30-0358-4103-97E0-29740E648A27}.Release|x86.ActiveCfg = Release|Any CPU
8589
{DA3C8E30-0358-4103-97E0-29740E648A27}.Release|x86.Build.0 = Release|Any CPU
90+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
91+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
92+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Debug|x64.ActiveCfg = Debug|Any CPU
93+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Debug|x64.Build.0 = Debug|Any CPU
94+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Debug|x86.ActiveCfg = Debug|Any CPU
95+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Debug|x86.Build.0 = Debug|Any CPU
96+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
97+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Release|Any CPU.Build.0 = Release|Any CPU
98+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Release|x64.ActiveCfg = Release|Any CPU
99+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Release|x64.Build.0 = Release|Any CPU
100+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Release|x86.ActiveCfg = Release|Any CPU
101+
{3A874F02-B059-4DEE-A1EF-2F13BC981BBA}.Release|x86.Build.0 = Release|Any CPU
102+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
103+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Debug|Any CPU.Build.0 = Debug|Any CPU
104+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Debug|x64.ActiveCfg = Debug|Any CPU
105+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Debug|x64.Build.0 = Debug|Any CPU
106+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Debug|x86.ActiveCfg = Debug|Any CPU
107+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Debug|x86.Build.0 = Debug|Any CPU
108+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Release|Any CPU.ActiveCfg = Release|Any CPU
109+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Release|Any CPU.Build.0 = Release|Any CPU
110+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Release|x64.ActiveCfg = Release|Any CPU
111+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Release|x64.Build.0 = Release|Any CPU
112+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Release|x86.ActiveCfg = Release|Any CPU
113+
{35704A9C-16A2-4F64-9042-C59421FCDE9B}.Release|x86.Build.0 = Release|Any CPU
86114
EndGlobalSection
87115
GlobalSection(SolutionProperties) = preSolution
88116
HideSolutionNode = FALSE

jwl.console/CLI.cs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ namespace jwl.console;
44
[Verb("fill", isDefault: true, HelpText = "Fill Jira with worklogs")]
55
public class FillCLI
66
{
7-
[Option('v', "verbose", HelpText = "\nGive more verbose feedback\nNote: Not implemented yet! 2do! :-)", Default = false, Hidden = true)]
8-
public bool UseVerboseFeedback { get; set; }
9-
107
[Option('i', "input", HelpText = "\nInput CSVs with the worklogs", Separator = ',', Required = true)]
118
public IEnumerable<string> InputFiles { get; set; } = new string[0];
129

@@ -21,7 +18,7 @@ public class FillCLI
2118
public string? UserCredentials { get; set; }
2219

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

@@ -52,11 +49,10 @@ public core.AppConfig ToAppConfig()
5249

5350
return new core.AppConfig()
5451
{
55-
UseVerboseFeedback = UseVerboseFeedback,
5652
JiraServer = new jira.ServerConfig()
5753
{
58-
ServerFlavour = ServerFlavour,
59-
ActivityMap = null,
54+
Flavour = ServerFlavour,
55+
FlavourOptions = null,
6056
BaseUrl = jiraServerSpecification,
6157
UseProxy = !NoProxy,
6258
MaxConnectionsPerServer = MaxConnectionsPerServer,

jwl.console/ScrollingConsoleProcessFeedback.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public void NoWorklogsToFill()
6464

6565
public void OverallProcessStart()
6666
{
67-
Console.Error.WriteLine(@"Jira Worklogger CLI 1.1.0"); // 2do! read from assembly
67+
Console.Error.WriteLine(@"Jira Worklogger CLI 2024.2.0"); // 2do! read from assembly
6868
Console.Error.WriteLine(@"by Peter H., practically copyleft"); // 2do! read from assembly
6969
Console.Error.WriteLine(new string('-', Console.WindowWidth - 1));
7070
}

jwl.console/jwl.console.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<ItemGroup>
77
<PackageReference Include="CommandLineParser" Version="2.9.1" />
8-
<PackageReference Include="NoP77svk.Console" Version="2023.3.1" />
8+
<PackageReference Include="NoP77svk.Console" Version="2024.1.1" />
99
<!-- <PackageReference Include="nulastudio.NetBeauty" Version="2.1.2.1" /> -->
1010
</ItemGroup>
1111

jwl.core/AppConfig.cs

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
1-
namespace jwl.core;
2-
using AutoMapper;
3-
4-
public class AppConfig
5-
{
6-
public bool? UseVerboseFeedback { get; init; }
7-
public jwl.jira.ServerConfig? JiraServer { get; init; }
8-
public jwl.core.UserConfig? User { get; init; }
9-
public jwl.inputs.CsvFormatConfig? CsvOptions { get; init; }
10-
11-
private static Lazy<MapperConfiguration> overridingMapperConfiguration = new (() => new MapperConfiguration(cfg =>
12-
{
13-
cfg.CreateMap<AppConfig, AppConfig>()
14-
.ForAllMembers(m => m.Condition((src, dest, member) => member != null));
15-
cfg.CreateMap<jira.ServerConfig, jira.ServerConfig>()
16-
.ForAllMembers(m => m.Condition((src, dest, member) => member != null));
17-
cfg.CreateMap<inputs.CsvFormatConfig, inputs.CsvFormatConfig>()
18-
.ForAllMembers(m => m.Condition((src, dest, member) => member != null));
19-
cfg.CreateMap<core.UserConfig, core.UserConfig>()
1+
namespace jwl.core;
2+
using AutoMapper;
3+
4+
public class AppConfig
5+
{
6+
public bool? UseVerboseFeedback { get; init; }
7+
public jwl.jira.ServerConfig? JiraServer { get; init; }
8+
public jwl.core.UserConfig? User { get; init; }
9+
public jwl.inputs.CsvFormatConfig? CsvOptions { get; init; }
10+
11+
private static Lazy<MapperConfiguration> overridingMapperConfiguration = new (() => new MapperConfiguration(cfg =>
12+
{
13+
cfg.CreateMap<AppConfig, AppConfig>()
14+
.ForAllMembers(m => m.Condition((src, dest, member) => member != null));
15+
cfg.CreateMap<jira.ServerConfig, jira.ServerConfig>()
16+
.ForAllMembers(m => m.Condition((src, dest, member) => member != null));
17+
cfg.CreateMap<inputs.CsvFormatConfig, inputs.CsvFormatConfig>()
2018
.ForAllMembers(m => m.Condition((src, dest, member) => member != null));
21-
22-
cfg.AddGlobalIgnore(nameof(AppConfig.JiraServer.ActivityMap));
23-
}));
24-
25-
private static Lazy<IMapper> overridingMapper = new (() => overridingMapperConfiguration.Value.CreateMapper());
26-
27-
public AppConfig OverrideWith(AppConfig? other)
28-
{
29-
if (other == null)
19+
cfg.CreateMap<core.UserConfig, core.UserConfig>()
20+
.ForAllMembers(m => m.Condition((src, dest, member) => member != null));
21+
22+
cfg.AddGlobalIgnore(nameof(AppConfig.JiraServer.FlavourOptions));
23+
cfg.AddGlobalIgnore(nameof(AppConfig.JiraServer.VanillaJiraFlavourOptions));
24+
}));
25+
26+
private static Lazy<IMapper> overridingMapper = new (() => overridingMapperConfiguration.Value.CreateMapper());
27+
28+
public AppConfig OverrideWith(AppConfig? other)
29+
{
30+
if (other == null)
3031
return this;
3132

32-
AppConfig result = overridingMapper.Value.Map<AppConfig, AppConfig>(other, this);
33-
return result;
34-
}
35-
}
33+
AppConfig result = overridingMapper.Value.Map<AppConfig, AppConfig>(other, this);
34+
return result;
35+
}
36+
}

jwl.core/AppConfigFactory.cs

Lines changed: 85 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,85 @@
1-
namespace jwl.core;
2-
using jwl.inputs;
3-
using jwl.jira;
4-
using Microsoft.Extensions.Configuration;
5-
6-
public static class AppConfigFactory
7-
{
8-
public const int DefaultMaxConnectionsPerServer = 4;
9-
10-
private const string ConfigFileName = @"jwl.config";
11-
private const string DefaultSubFolder = @"jira-worklogger";
12-
13-
public static AppConfig CreateWithDefaults()
14-
{
15-
return new AppConfig()
16-
{
17-
JiraServer = new ServerConfig()
18-
{
19-
ServerFlavour = nameof(JiraServerFlavour.Vanilla),
20-
ActivityMap = null,
21-
BaseUrl = @"http://jira.my-domain.xyz",
22-
MaxConnectionsPerServer = DefaultMaxConnectionsPerServer,
23-
SkipSslCertificateCheck = false,
24-
UseProxy = false
25-
},
26-
CsvOptions = new CsvFormatConfig()
27-
{
28-
FieldDelimiter = ","
29-
},
30-
User = new UserConfig()
31-
{
32-
Name = string.Empty,
33-
Password = null
34-
}
35-
};
36-
}
37-
38-
public static AppConfig ReadConfig()
39-
{
40-
AppConfig? result;
41-
42-
IConfiguration config = new ConfigurationBuilder()
43-
.AddJsonFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ConfigFileName), optional: true)
44-
.AddJsonFile(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ConfigFileName), optional: true)
45-
.AddJsonFile(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), ConfigFileName), optional: true)
46-
.AddJsonFile(Path.Combine(Path.GetFullPath("."), ConfigFileName), optional: true)
47-
.Build();
48-
49-
result = config.Get<AppConfig>(opt =>
50-
{
51-
opt.BindNonPublicProperties = false;
52-
opt.ErrorOnUnknownConfiguration = true;
53-
});
54-
55-
return result ?? CreateWithDefaults();
56-
}
57-
}
1+
namespace jwl.core;
2+
using jwl.inputs;
3+
using jwl.jira;
4+
using jwl.jira.Flavours;
5+
using Microsoft.Extensions.Configuration;
6+
using System.Xml.XPath;
7+
8+
public static class AppConfigFactory
9+
{
10+
public const int DefaultMaxConnectionsPerServer = 4;
11+
12+
private const string ConfigFileName = @"jwl.config";
13+
private const string FlavourConfigFileNameTemplate = @"jwl.{0}.config";
14+
15+
public static AppConfig CreateWithDefaults()
16+
{
17+
return new AppConfig()
18+
{
19+
JiraServer = new ServerConfig()
20+
{
21+
Flavour = nameof(JiraServerFlavour.Vanilla),
22+
FlavourOptions = null,
23+
BaseUrl = @"http://jira.my-domain.xyz",
24+
MaxConnectionsPerServer = DefaultMaxConnectionsPerServer,
25+
SkipSslCertificateCheck = false,
26+
UseProxy = false
27+
},
28+
CsvOptions = new CsvFormatConfig()
29+
{
30+
FieldDelimiter = ","
31+
},
32+
User = new UserConfig()
33+
{
34+
Name = string.Empty,
35+
Password = null
36+
}
37+
};
38+
}
39+
40+
public static AppConfig ReadConfig()
41+
{
42+
const int RootAppConfigId = -1;
43+
44+
Action<BinderOptions> binderOptions = opt =>
45+
{
46+
opt.BindNonPublicProperties = false;
47+
opt.ErrorOnUnknownConfiguration = true;
48+
};
49+
50+
var config = Enum.GetValues<JiraServerFlavour>()
51+
.Select(e => new KeyValuePair<int, string>(
52+
(int)e,
53+
string.Format(FlavourConfigFileNameTemplate, e.ToString().ToLowerInvariant()))
54+
)
55+
.Prepend(new KeyValuePair<int, string>(RootAppConfigId, ConfigFileName))
56+
.Select(cfg => new KeyValuePair<int, IConfiguration>(
57+
cfg.Key,
58+
new ConfigurationBuilder()
59+
.AddJsonFile(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, cfg.Value), optional: true)
60+
.AddJsonFile(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), cfg.Value), optional: true)
61+
.AddJsonFile(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), cfg.Value), optional: true)
62+
.AddJsonFile(Path.Combine(Path.GetFullPath("."), cfg.Value), optional: true)
63+
.Build()
64+
))
65+
.Select(cfg => new KeyValuePair<int, object?>(
66+
cfg.Key,
67+
cfg.Key switch
68+
{
69+
-1 => cfg.Value.Get<AppConfig>(binderOptions),
70+
(int)JiraServerFlavour.Vanilla => cfg.Value.Get<FlavourVanillaJiraOptions>(binderOptions) ?? new FlavourVanillaJiraOptions(),
71+
(int)JiraServerFlavour.TempoTimeSheets => cfg.Value.Get<FlavourTempoTimesheetsOptions>(binderOptions) ?? new FlavourTempoTimesheetsOptions(),
72+
(int)JiraServerFlavour.ICTime => cfg.Value.Get<FlavourICTimeOptions>(binderOptions) ?? new FlavourICTimeOptions(),
73+
_ => throw new IndexOutOfRangeException($"Unrecognized server flavour ID {cfg.Key}")
74+
}
75+
))
76+
.ToDictionary(cfg => cfg.Key, cfg => cfg.Value);
77+
78+
// 2do! automap the defaults
79+
AppConfig result = (AppConfig?)config[RootAppConfigId] ?? CreateWithDefaults();
80+
result.JiraServer!.VanillaJiraFlavourOptions = (FlavourVanillaJiraOptions?)config[(int)JiraServerFlavour.Vanilla];
81+
result.JiraServer!.FlavourOptions = (IFlavourOptions?)config[(int)result.JiraServer.FlavourId];
82+
83+
return result;
84+
}
85+
}

0 commit comments

Comments
 (0)