260 lines
10 KiB
C#
260 lines
10 KiB
C#
using Blueberry.Redmine;
|
|
using Blueberry.Redmine.Dto;
|
|
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Windows;
|
|
using System.Windows.Media;
|
|
using Windows.Networking.NetworkOperators;
|
|
using Wpf.Ui.Controls;
|
|
|
|
namespace Blueberry
|
|
{
|
|
/// <summary>
|
|
/// Interaction logic for IssueWindow.xaml
|
|
/// </summary>
|
|
public partial class IssueWindow : FluentWindow
|
|
{
|
|
private readonly DetailedIssue.Issue _issue;
|
|
private readonly RedmineManager _manager;
|
|
private readonly RedmineConfig _config;
|
|
private readonly List<JournalDisplay> _journalDisplays = [];
|
|
private CancellationTokenSource _tokenSource = new();
|
|
public ObservableCollection<JournalDisplay> Journals { get; set; } = [];
|
|
|
|
public IssueWindow(DetailedIssue.Issue issue, RedmineManager manager, RedmineConfig config)
|
|
{
|
|
_issue = issue;
|
|
_manager = manager;
|
|
_config = config;
|
|
InitializeComponent();
|
|
DataContext = this;
|
|
}
|
|
|
|
private async void FluentWindow_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
iSubjectTextBox.Text = _issue.Subject;
|
|
iProjectTextBox.Text = _issue.Project.Name;
|
|
iDescriptionTextBox.Text = _issue.Description;
|
|
iCreatedTextBox.Text = _issue.CreatedOn.ToString("yyyy-MM-dd");
|
|
iUpdatedTextBox.Text = _issue.UpdatedOn.ToString("yyyy-MM-dd");
|
|
iSpentTimeTextBox.Text = _issue.SpentHours.ToString();
|
|
journalProgressRing.Visibility = Visibility.Visible;
|
|
|
|
List<TimeOnIssue.TimeEntry> hours = [];
|
|
try
|
|
{
|
|
hours = await _manager.GetTimeOnIssue(_issue.Id, progress: UpdateProgress(), token: _tokenSource.Token);
|
|
} catch { }
|
|
_journalDisplays.AddRange(await ProcessJournal(_issue.Journals, hours));
|
|
if(!_journalDisplays.Any(x=>!x.IsData))
|
|
detailsToggleSwitch.IsChecked = true;
|
|
await LoadJournal();
|
|
journalProgressRing.Visibility = Visibility.Hidden;
|
|
}
|
|
|
|
private IProgress<(int, int)> UpdateProgress()
|
|
{
|
|
var p = new Progress<(int current, int total)>((x) =>
|
|
{
|
|
Dispatcher.Invoke(() =>
|
|
{
|
|
journalProgressRing.IsIndeterminate = false;
|
|
var percent = (int)Math.Round((double)x.current / x.total * 100);
|
|
journalProgressRing.Progress = percent;
|
|
});
|
|
});
|
|
return p;
|
|
}
|
|
|
|
private async Task LoadJournal()
|
|
{
|
|
var showDetails = detailsToggleSwitch.IsChecked ?? true;
|
|
|
|
Journals.Clear();
|
|
foreach (var j in _journalDisplays)
|
|
if ((!showDetails && !j.IsData)
|
|
|| showDetails)
|
|
Journals.Add(j);
|
|
}
|
|
|
|
private async void detailsToggleSwitch_Checked(object sender, RoutedEventArgs e)
|
|
{
|
|
await LoadJournal();
|
|
}
|
|
|
|
private void openBrowserButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
string url = $"{_config.RedmineUrl}/issues/{_issue.Id}";
|
|
|
|
var psi = new ProcessStartInfo
|
|
{
|
|
FileName = url,
|
|
UseShellExecute = true
|
|
};
|
|
Process.Start(psi);
|
|
}
|
|
|
|
private void FluentWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
|
|
{
|
|
_tokenSource.Cancel();
|
|
}
|
|
}
|
|
|
|
public partial class IssueWindow
|
|
{
|
|
public async Task<List<JournalDisplay>> ProcessJournal(IEnumerable<DetailedIssue.Journal> journals, List<TimeOnIssue.TimeEntry> hours)
|
|
{
|
|
var js = new List<JournalDisplay>();
|
|
|
|
foreach (var item in journals)
|
|
{
|
|
var user = item.User.Name;
|
|
var date = item.CreatedOn.ToString("yyyy-MM-dd HH:mm");
|
|
var content = item.Notes;
|
|
if (!string.IsNullOrWhiteSpace(content))
|
|
{
|
|
if (item.PrivateNotes)
|
|
js.Add(new JournalDisplay
|
|
{
|
|
User = user,
|
|
Date = date,
|
|
Content = content,
|
|
IsPrivate = true
|
|
});
|
|
else
|
|
js.Add(new JournalDisplay
|
|
{
|
|
User = user,
|
|
Date = date,
|
|
Content = content
|
|
});
|
|
}
|
|
|
|
foreach (var data in item.Details)
|
|
{
|
|
try
|
|
{
|
|
if (data.Property == "attr")
|
|
{
|
|
switch (data.Name)
|
|
{
|
|
case "due_date":
|
|
var old = data.OldValue ?? "null";
|
|
content = $"Due date:\n{old} > {data.NewValue}";
|
|
js.Add(new JournalDisplay
|
|
{
|
|
User = user,
|
|
Date = date,
|
|
Content = content,
|
|
IsData = true
|
|
});
|
|
break;
|
|
case "assigned_to_id":
|
|
old = "null";
|
|
if (data.OldValue != null)
|
|
{
|
|
var u = await _manager.GetUserAsync(int.Parse(data.OldValue));
|
|
old = u.Firstname + " " + u.Lastname;
|
|
}
|
|
|
|
var newU = await _manager.GetUserAsync(int.Parse(data.NewValue));
|
|
|
|
content = $"Assigned to:\n{old} > {newU.Firstname} {newU.Lastname}";
|
|
js.Add(new JournalDisplay
|
|
{
|
|
User = user,
|
|
Date = date,
|
|
Content = content,
|
|
IsData = true
|
|
});
|
|
break;
|
|
case "status_id":
|
|
old = "null";
|
|
if (data.OldValue != null)
|
|
{
|
|
old = (await _manager.GetStatusesAsync()).Where(x => x.Id == int.Parse(data.OldValue)).First().Name;
|
|
}
|
|
|
|
var newS = (await _manager.GetStatusesAsync()).Where(x => x.Id == int.Parse(data.NewValue)).First().Name;
|
|
|
|
content = $"Status changed to:\n{old} > {newS}";
|
|
js.Add(new JournalDisplay
|
|
{
|
|
User = user,
|
|
Date = date,
|
|
Content = content,
|
|
IsData = true
|
|
});
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (data.Property == "cf")
|
|
{
|
|
try
|
|
{
|
|
if (int.TryParse(data.Name, out var cfId))
|
|
{
|
|
var cfs = await _manager.GetCustomFieldsAsync();
|
|
var cfName = cfs.Where(x => x.Id == cfId).First().Name;
|
|
var old = data.OldValue ?? "null";
|
|
content = $"{cfName}\n{old} > {data.NewValue}";
|
|
js.Add(new JournalDisplay
|
|
{
|
|
User = user,
|
|
Date = date,
|
|
Content = content,
|
|
IsData = true
|
|
});
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
public class JournalDisplay
|
|
{
|
|
public string User { get; set; }
|
|
public string Date { get; set; }
|
|
public string Content { get; set; }
|
|
public bool IsPrivate { get; set; }
|
|
public bool IsData { get; set; }
|
|
public Brush StatusColor
|
|
{
|
|
get
|
|
{
|
|
var resourceKey = IsData ? "TextFillColorTertiaryBrush" : "TextFillColorSecondaryBrush" ;
|
|
|
|
// Look up the brush from the App resources
|
|
return Application.Current.TryFindResource(resourceKey) as Brush;
|
|
}
|
|
}
|
|
|
|
public SolidColorBrush NameColor => StringToColorConverter.GetColorFromName(User);
|
|
}
|
|
}
|
|
}
|