From 4cc68b0f20e714e39ecef5e64ef5077a532eb713 Mon Sep 17 00:00:00 2001 From: smoh Date: Tue, 6 Jan 2026 09:36:57 +0900 Subject: [PATCH] =?UTF-8?q?jql=EC=97=90=20=EC=9D=98=ED=95=B4=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=EB=A5=BC=20=EB=B6=88=EB=9F=AC=EC=98=A4=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VTSFetcher/Services/VtsService.cs | 21 ++++++++ VTSFetcher/ViewModels/MainViewModel.cs | 75 +++++++++++++++++++++++++- VTSFetcher/Views/MainWindow.xaml.cs | 8 +++ 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/VTSFetcher/Services/VtsService.cs b/VTSFetcher/Services/VtsService.cs index b4ac19b..eb200bd 100644 --- a/VTSFetcher/Services/VtsService.cs +++ b/VTSFetcher/Services/VtsService.cs @@ -56,6 +56,27 @@ public class VtsService return issue; } + public async Task FetchVtsIssuesByJql(string jql, string targetProject, int startAt, CancellationToken ct) + { + var uri = $"{_appSettings.VTS.ApiBaseUrl}/" + + $"search?jql={jql} " + + $"AND project={targetProject} " + + $"&fields={_appSettings.VTS.SearchFields}" + + $"&startAt={startAt}&maxResults=50"; + var request = new HttpRequestMessage(HttpMethod.Get, uri); + request.Headers.Add("Accept", "application/json"); + request.Headers.Add("Authorization", $"Bearer {_appSettings.VTS.AccessToken}"); + Log.Debug("Fetch VTS issues ready. {fetch_url}", uri); + + using var client = new HttpClient(); + var response = await client.SendAsync(request, ct).ConfigureAwait(false); + response.EnsureSuccessStatusCode(); + + var jsonResponse = await response.Content.ReadAsStringAsync(ct).ConfigureAwait(false); + var vtsResponse = JsonSerializer.Deserialize(jsonResponse); + return vtsResponse; + } + private List JsonDeserializeToVtsIssues(string json) { var result = new List(); diff --git a/VTSFetcher/ViewModels/MainViewModel.cs b/VTSFetcher/ViewModels/MainViewModel.cs index ccce9fb..1285b98 100644 --- a/VTSFetcher/ViewModels/MainViewModel.cs +++ b/VTSFetcher/ViewModels/MainViewModel.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.Options; +using DocumentFormat.OpenXml.Spreadsheet; +using Microsoft.Extensions.Options; using Serilog; using System.Collections.Concurrent; using System.ComponentModel; @@ -84,6 +85,7 @@ public class MainViewModel : INotifyPropertyChanged private bool _isWorking = true; public ICommand FetchNowCommand { get; } + public ICommand JqlCommand { get; } public ICommand SaveStatusCommand { get; } public ICommand ReportCommand { get; } #endregion Fields @@ -126,6 +128,7 @@ public class MainViewModel : INotifyPropertyChanged _statusTimer.Start(); FetchNowCommand = new RelayCommand(_ => FetchNow()); + JqlCommand = new RelayCommand(_ => FetchByJqlNow()); SaveStatusCommand = new RelayCommand(_ => { UpdateStatusData(); }); @@ -292,6 +295,74 @@ public class MainViewModel : INotifyPropertyChanged UpdateLabels(); } } + private void FetchByJqlNow() + { + Log.Debug("Start FetchByJqlNow"); + + // Read jql.txt file + var jqlFilePath = Path.Combine(AppContext.BaseDirectory, "jql.txt"); + // If the file does not exist, create an empty file and return. + if (!File.Exists(jqlFilePath)) + File.WriteAllText(jqlFilePath, ""); + //Read the JQL from the file + var jql = File.ReadAllText(jqlFilePath).Trim(); + if (string.IsNullOrEmpty(jql)) + { + Log.Debug("End FetchByJqlNow: JQL is empty."); + return; + } + + // Start fetching by JQL + Log.Information("Start fetching by JQL: {jql}", jql); + foreach (var project in _appSettings.VTS.TargetProjects) + { + try + { + List issues = []; + _sw.Restart(); + var vtsResponses = new List(); + VTSResponse vtsResponse; + vtsResponse = _vts.FetchVtsIssuesByJql(jql, project, 0, _cts.Token).GetAwaiter().GetResult(); + vtsResponses.Add(vtsResponse); + var startAt = vtsResponse?.maxResults ?? 0; + var total = vtsResponse?.total ?? 0; + while (startAt < total) + { + vtsResponse = _vts.FetchVtsIssuesByJql(jql, project, startAt, _cts.Token).GetAwaiter().GetResult(); + vtsResponses.Add(vtsResponse); + startAt += vtsResponse?.maxResults ?? 50; + } + issues = vtsResponses.SelectMany(r => r.issues.Select(i => new VtsIssue + { + Key = i.key, + Summary = i.fields.summary, + Type = i.fields.issuetype.name, + Status = i.fields.status.name, + Assignee = i.fields.assignee?.displayName ?? "", + Manager = i.fields.reporter?.displayName ?? "", + Due = i.fields.duedate ?? new DateTime(1999, 12, 23, 23, 59, 59), + Updated = i.fields.UpdatedAt, + Parent = i.fields.parent?.key, + Sprint = SprintHelper.ExtractActiveSprintName(i.fields.customfield_10806) + })).ToList(); + _sw.Stop(); + Log.Information("Fetched {issue_count} issues from VTS in {operation_time}ms" + , issues.Count, _sw.ElapsedMilliseconds); + foreach (var i in issues) + { + i.NeedNotify = (IsNeedNotify(i)) ? 1 : 0; + Log.Debug("{issue_key}: {issue_summary} Updated at {issue_updated}", i.Key, i.Summary, i.Updated); + } + + _repo.UpsertVtsIssues(issues, ["Published", "ObjectId"]); + } + catch (Exception ex) + { + Log.Error("Unhandled exception occured during fetching VTS issues: {error_message}", ex.Message); + } + } + Log.Debug("End FetchByJqlNow"); + } #endregion FetchEvents #region SaveStatusEvents @@ -411,9 +482,9 @@ public class MainViewModel : INotifyPropertyChanged var orgIssue = _repo.GetVtsIssueAsync(issue.Key).GetAwaiter().GetResult(); if (orgIssue == null) // A new issue assigned to me. { - Log.Debug("{method_name} - A new issue({target_keys}) assigned to me.", nameof(IsNeedNotify), issue.Key, issue.Assignee); if (issue.Assignee == MyName) { + Log.Debug("{method_name} - A new issue({target_keys}) assigned to me.", nameof(IsNeedNotify), issue.Key, issue.Assignee); _publishedIssuesQueue.Enqueue(issue); return true; } diff --git a/VTSFetcher/Views/MainWindow.xaml.cs b/VTSFetcher/Views/MainWindow.xaml.cs index 3259816..276d039 100644 --- a/VTSFetcher/Views/MainWindow.xaml.cs +++ b/VTSFetcher/Views/MainWindow.xaml.cs @@ -26,6 +26,7 @@ public partial class MainWindow : Window var contextMenu = new ContextMenuStrip(); contextMenu.Items.Add("Open", null, (s, e) => ShowWindow()); contextMenu.Items.Add("Fetch", null, (s, e) => FetchNow()); + contextMenu.Items.Add("JQL", null, (s, e) => FetchByJqlNow()); contextMenu.Items.Add("Save status", null, (s, e) => SaveStatusNow()); contextMenu.Items.Add("Report", null, (s, e) => ReportNow()); contextMenu.Items.Add("Exit", null, (s, e) => ExitApp()); @@ -49,6 +50,13 @@ public partial class MainWindow : Window vm.FetchNowCommand.Execute(null); } } + private void FetchByJqlNow() + { + if (DataContext is MainViewModel vm) + { + vm.JqlCommand.Execute(null); + } + } private void SaveStatusNow() { if (DataContext is MainViewModel vm)