Compare commits

..

2 Commits

Author SHA1 Message Date
76cfb440b9 add hours to issue details (1.1.3) 2025-12-15 12:07:13 +01:00
ad6ca741e8 Remove unused usings 2025-12-15 11:22:21 +01:00
7 changed files with 141 additions and 7 deletions

View File

@@ -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<TimeEntry> 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<CustomField> 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.

View File

@@ -232,6 +232,13 @@ namespace Blueberry.Redmine
await SendRequestAsync<object>(HttpMethod.Put, path, payload, token: token); await SendRequestAsync<object>(HttpMethod.Put, path, payload, token: token);
} }
public async Task<List<TimeOnIssue.TimeEntry>> 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<TimeOnIssue.Root, TimeOnIssue.TimeEntry>(HttpMethod.Get, path, limit, (x)=>x.TimeEntries, progress, token: token);
return times;
}
public async Task<int> CreateNewIssue(int projectId, int trackerId, string subject, string description, public async Task<int> CreateNewIssue(int projectId, int trackerId, string subject, string description,
double estimatedHours, int priorityId, int? assigneeId = null, int? parentIssueId = null, CancellationToken? token = null) double estimatedHours, int priorityId, int? assigneeId = null, int? parentIssueId = null, CancellationToken? token = null)
{ {

View File

@@ -131,6 +131,12 @@ namespace Blueberry.Redmine
return await _apiClient.GetTrackersForProject(projectId.ToString(), token); return await _apiClient.GetTrackersForProject(projectId.ToString(), token);
} }
public async Task<List<TimeOnIssue.TimeEntry>> 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) public async Task SetIssueStatusAsync(int issueId, int statusId, CancellationToken? token = null)
{ {
await _apiClient.SetIssueStatus(issueId, statusId, token); await _apiClient.SetIssueStatus(issueId, statusId, token);

View File

@@ -1,5 +1,4 @@
using Blueberry; using Blueberry.Redmine;
using Blueberry.Redmine;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;

View File

@@ -37,7 +37,8 @@ namespace Blueberry
iUpdatedTextBox.Text = _issue.UpdatedOn.ToString("yyyy-MM-dd"); iUpdatedTextBox.Text = _issue.UpdatedOn.ToString("yyyy-MM-dd");
iSpentTimeTextBox.Text = _issue.SpentHours.ToString(); iSpentTimeTextBox.Text = _issue.SpentHours.ToString();
journalProgressRing.Visibility = Visibility.Visible; 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)) if(!_journalDisplays.Any(x=>!x.IsData))
detailsToggleSwitch.IsChecked = true; detailsToggleSwitch.IsChecked = true;
await LoadJournal(); await LoadJournal();
@@ -75,7 +76,7 @@ namespace Blueberry
public partial class IssueWindow public partial class IssueWindow
{ {
public async Task<List<JournalDisplay>> ProcessJournal(IEnumerable<DetailedIssue.Journal> journals) public async Task<List<JournalDisplay>> ProcessJournal(IEnumerable<DetailedIssue.Journal> journals, List<TimeOnIssue.TimeEntry> hours)
{ {
var js = new List<JournalDisplay>(); var js = new List<JournalDisplay>();
@@ -187,6 +188,24 @@ namespace Blueberry
} catch (Exception) { } } 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; return js;
} }

View File

@@ -4,7 +4,6 @@ using Blueberry.Redmine.Dto;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics; using System.Diagnostics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using Wpf.Ui.Controls; using Wpf.Ui.Controls;

View File

@@ -2,14 +2,13 @@
using System.IO; using System.IO;
using System.Net.Http; using System.Net.Http;
using System.Text.Json; using System.Text.Json;
using Windows.Media.Protection.PlayReady;
namespace Blueberry namespace Blueberry
{ {
public static class UpdateManager public static class UpdateManager
{ {
private const string releaseUrl = "https://git.technopunk.space/api/v1/repos/tomi/Blueberry/releases/latest"; 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 string AppDir = AppDomain.CurrentDomain.BaseDirectory;
private static readonly HttpClient client = new(); private static readonly HttpClient client = new();
public delegate void DownloadCompletedEventArgs(); public delegate void DownloadCompletedEventArgs();