Skip to content

Commit

Permalink
Merge branch 'feature/refactoring-server-apis' of https://github.com/…
Browse files Browse the repository at this point in the history
…nop77svk/jira-worklogger into feature/refactoring-server-apis
  • Loading branch information
nop77svk committed Feb 5, 2024
2 parents 0f88237 + 93fbdbd commit e259cfb
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 38 deletions.
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,27 @@ Meet the scripted Jira worklogging! Give it your worklogs in a CSV file (and you
## 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
- "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)
- 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>TempoWorklogType</code> (string) - Tempo Timesheets worklog type; values are checked against the available values from Jira server on each execution.
- <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.
4 changes: 2 additions & 2 deletions jwl.core/JwlCoreProcess.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ private async Task FillJiraWithWorklogs(InputWorkLog[] inputWorklogs, WorkLog[]
MultiTaskStats progress = new MultiTaskStats(fillJiraWithWorklogsTasks.Length);
MultiTask multiTask = new MultiTask()
{
TaskFeedback = t => Feedback?.FillJiraWithWorklogsProcess(progress.ApplyTaskStatus(t.Status))
OnTaskAwaited = t => Feedback?.FillJiraWithWorklogsProcess(progress.ApplyTaskStatus(t.Status))
};

await multiTask.WhenAll(fillJiraWithWorklogsTasks);
Expand All @@ -181,7 +181,7 @@ private async Task<InputWorkLog[]> ReadInputFiles(IEnumerable<string> fileNames)
MultiTaskStats progressStats = new MultiTaskStats(readerTasks.Length);
MultiTask multiTask = new MultiTask()
{
TaskFeedback = t => Feedback?.ReadCsvInputProcess(progressStats.ApplyTaskStatus(t.Status))
OnTaskAwaited = t => Feedback?.ReadCsvInputProcess(progressStats.ApplyTaskStatus(t.Status))
};

if (readerTasks.Any())
Expand Down
58 changes: 30 additions & 28 deletions jwl.infra/MultiTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ public enum ProgressState
{
Unknown,
Starting,
BeforeTaskWait,
AfterTaskWait,
BeforeTaskAwait,
AfterTaskAwait,
Finished,
Error,
Cancelled
}

public ProgressState State { get; private set; } = ProgressState.Unknown;

public Action<MultiTask>? ProcessFeedback { get; init; }
public Action<Task>? TaskFeedback { get; init; }
public Action<MultiTask>? OnStateChange { get; init; }
public Action<Task>? OnTaskAwaited { get; init; }

public MultiTask()
{
Expand All @@ -25,35 +25,34 @@ public MultiTask()
public async Task WhenAll(IEnumerable<Task> tasks, CancellationToken? cancellationToken = null)
{
State = ProgressState.Starting;
ProcessFeedback?.Invoke(this);
OnStateChange?.Invoke(this);

HashSet<Task> tasksToExecute = tasks.ToHashSet();
List<Exception> errors = new List<Exception>();

while (tasksToExecute.Any())
{
State = ProgressState.BeforeTaskWait;
ProcessFeedback?.Invoke(this);
State = ProgressState.BeforeTaskAwait;
OnStateChange?.Invoke(this);

Task? taskFinished = null;
try
{
cancellationToken?.ThrowIfCancellationRequested();

taskFinished = await Task.WhenAny(tasksToExecute);
Task finishedTask = await Task.WhenAny(tasksToExecute);

State = ProgressState.AfterTaskWait;
ProcessFeedback?.Invoke(this);
TaskFeedback?.Invoke(taskFinished);
State = ProgressState.AfterTaskAwait;
OnStateChange?.Invoke(this);
OnTaskAwaited?.Invoke(finishedTask);

if (taskFinished.Status is TaskStatus.Faulted or TaskStatus.Canceled)
if (finishedTask.Status is TaskStatus.Faulted or TaskStatus.Canceled)
{
tasksToExecute.Remove(taskFinished);
throw taskFinished.Exception ?? new Exception($"Task ended in {taskFinished.Status} status without exception details");
tasksToExecute.Remove(finishedTask);
throw finishedTask.Exception ?? new Exception($"Task ended in {finishedTask.Status} status without exception details");
}
else if (taskFinished.Status == TaskStatus.RanToCompletion)
else if (finishedTask.Status == TaskStatus.RanToCompletion)
{
if (!tasksToExecute.Remove(taskFinished))
if (!tasksToExecute.Remove(finishedTask))
throw new InvalidOperationException("Task reported as finished... again!");
}
}
Expand All @@ -67,24 +66,27 @@ public async Task WhenAll(IEnumerable<Task> tasks, CancellationToken? cancellati
}
}

if (errors.All(ex => ex is TaskCanceledException))
if (errors.Any())
{
State = ProgressState.Cancelled;
ProcessFeedback?.Invoke(this);
if (errors.All(ex => ex is TaskCanceledException))
{
State = ProgressState.Cancelled;
OnStateChange?.Invoke(this);

throw new TaskCanceledException($"All {errors.Count} tasks have been cancelled", new AggregateException(errors));
}
else if (errors.Any())
{
State = ProgressState.Error;
ProcessFeedback?.Invoke(this);
throw new TaskCanceledException($"All {errors.Count} tasks have been cancelled", new AggregateException(errors));
}
else
{
State = ProgressState.Error;
OnStateChange?.Invoke(this);

throw new AggregateException(errors);
throw new AggregateException(errors);
}
}
else
{
State = ProgressState.Finished;
ProcessFeedback?.Invoke(this);
OnStateChange?.Invoke(this);
}
}
}

0 comments on commit e259cfb

Please sign in to comment.