diff --git a/Blueberry.Redmine/Dto/TimeOnIssue.cs b/Blueberry.Redmine/Dto/TimeOnIssue.cs new file mode 100644 index 0000000..fa4ed84 --- /dev/null +++ b/Blueberry.Redmine/Dto/TimeOnIssue.cs @@ -0,0 +1,105 @@ +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. +using System.Text.Json.Serialization; + +namespace Blueberry.Redmine.Dto +{ + public class TimeOnIssue + { + public class Activity + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + } + + public class CustomField + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("value")] + public string Value { get; set; } + } + + public class Issue + { + [JsonPropertyName("id")] + public int Id { get; set; } + } + + public class Project + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + } + + public class Root : IResponseList + { + [JsonPropertyName("time_entries")] + public List TimeEntries { get; set; } + + [JsonPropertyName("total_count")] + public int TotalCount { get; set; } + + [JsonPropertyName("offset")] + public int Offset { get; set; } + + [JsonPropertyName("limit")] + public int Limit { get; set; } + } + + public class TimeEntry + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("project")] + public Project Project { get; set; } + + [JsonPropertyName("issue")] + public Issue Issue { get; set; } + + [JsonPropertyName("user")] + public User User { get; set; } + + [JsonPropertyName("activity")] + public Activity Activity { get; set; } + + [JsonPropertyName("hours")] + public double Hours { get; set; } + + [JsonPropertyName("comments")] + public string Comments { get; set; } + + [JsonPropertyName("spent_on")] + public string SpentOn { get; set; } + + [JsonPropertyName("created_on")] + public DateTime CreatedOn { get; set; } + + [JsonPropertyName("updated_on")] + public DateTime UpdatedOn { get; set; } + + [JsonPropertyName("custom_fields")] + public List CustomFields { get; set; } + } + + public class User + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + } + } +} +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. diff --git a/Blueberry.Redmine/RedmineApiClient.cs b/Blueberry.Redmine/RedmineApiClient.cs index f46e562..2545f9e 100644 --- a/Blueberry.Redmine/RedmineApiClient.cs +++ b/Blueberry.Redmine/RedmineApiClient.cs @@ -232,6 +232,13 @@ namespace Blueberry.Redmine await SendRequestAsync(HttpMethod.Put, path, payload, token: token); } + public async Task> GetTimeOnIssue(int issueId, int limit = 25, IProgress<(int, int)>? progress = null, CancellationToken? token = null) + { + var path = $"time_entries.json?issue_id={issueId}"; + var times = await SendRequestWithPagingAsync(HttpMethod.Get, path, limit, (x)=>x.TimeEntries, progress, token: token); + return times; + } + public async Task CreateNewIssue(int projectId, int trackerId, string subject, string description, double estimatedHours, int priorityId, int? assigneeId = null, int? parentIssueId = null, CancellationToken? token = null) { diff --git a/Blueberry.Redmine/RedmineManager.cs b/Blueberry.Redmine/RedmineManager.cs index cd5fdc8..d1ca885 100644 --- a/Blueberry.Redmine/RedmineManager.cs +++ b/Blueberry.Redmine/RedmineManager.cs @@ -131,6 +131,12 @@ namespace Blueberry.Redmine return await _apiClient.GetTrackersForProject(projectId.ToString(), token); } + public async Task> GetTimeOnIssue(int issueId, int limit = 25, IProgress<(int, int)>? progress = null, CancellationToken? token = null) + { + var result = await _apiClient.GetTimeOnIssue(issueId, limit, progress, token); + return result; + } + public async Task SetIssueStatusAsync(int issueId, int statusId, CancellationToken? token = null) { await _apiClient.SetIssueStatus(issueId, statusId, token); diff --git a/Blueberry/IssueWindow.xaml.cs b/Blueberry/IssueWindow.xaml.cs index 95df93e..4e53425 100644 --- a/Blueberry/IssueWindow.xaml.cs +++ b/Blueberry/IssueWindow.xaml.cs @@ -37,7 +37,8 @@ namespace Blueberry iUpdatedTextBox.Text = _issue.UpdatedOn.ToString("yyyy-MM-dd"); iSpentTimeTextBox.Text = _issue.SpentHours.ToString(); journalProgressRing.Visibility = Visibility.Visible; - _journalDisplays.AddRange(await ProcessJournal(_issue.Journals)); + var hours = await _manager.GetTimeOnIssue(_issue.Id); + _journalDisplays.AddRange(await ProcessJournal(_issue.Journals, hours)); if(!_journalDisplays.Any(x=>!x.IsData)) detailsToggleSwitch.IsChecked = true; await LoadJournal(); @@ -75,7 +76,7 @@ namespace Blueberry public partial class IssueWindow { - public async Task> ProcessJournal(IEnumerable journals) + public async Task> ProcessJournal(IEnumerable journals, List hours) { var js = new List(); @@ -187,6 +188,24 @@ namespace Blueberry } catch (Exception) { } } } + + var totalHours = 0d; + hours = [.. hours.OrderBy(x => x.CreatedOn)]; + foreach (var hour in hours) + { + totalHours += hour.Hours; + var user = hour.User.Name; + var date = hour.CreatedOn.ToString("yyyy-MM-dd HH:mm"); + var content = $"Idő: {hour.Hours}\nEddig összesen: {totalHours}\n{hour.Comments}"; + js.Add(new JournalDisplay + { + User = user, + Date = date, + Content = content, + IsData = true + }); + } + js = [.. js.OrderBy(x => x.Date)]; return js; } diff --git a/Blueberry/UpdateManager.cs b/Blueberry/UpdateManager.cs index faa5123..bfa268e 100644 --- a/Blueberry/UpdateManager.cs +++ b/Blueberry/UpdateManager.cs @@ -8,7 +8,7 @@ namespace Blueberry public static class UpdateManager { private const string releaseUrl = "https://git.technopunk.space/api/v1/repos/tomi/Blueberry/releases/latest"; - public const string CurrentVersion = "0.1.2"; + public const string CurrentVersion = "0.1.3"; private static readonly string AppDir = AppDomain.CurrentDomain.BaseDirectory; private static readonly HttpClient client = new(); public delegate void DownloadCompletedEventArgs();