이슈 변경 사항을 추적하기 위한 서비스 구현.
- DB 추적 및 Excel 리포트 출력
This commit is contained in:
@@ -68,6 +68,7 @@ public partial class App : System.Windows.Application
|
||||
services.AddSingleton<SlackService>();
|
||||
services.AddDbContext<AnytypeVtsContext>();
|
||||
services.AddSingleton<AnytypeVtsRepository>();
|
||||
services.AddSingleton<ExcelExportService>();
|
||||
services.AddTransient<MainViewModel>();
|
||||
services.AddTransient<MainWindow>();
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ public partial class AnytypeVtsContext : DbContext
|
||||
private readonly AppSettings _appSettings;
|
||||
public DbSet<VtsIssue> VtsIssues { get; set; }
|
||||
public DbSet<Config> Configs { get; set; }
|
||||
public DbSet<IssueStatus> IssueStatuses { get; set; }
|
||||
public string DbPath { get; }
|
||||
|
||||
public AnytypeVtsContext(IOptions<AppSettings> options)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using EFCore.BulkExtensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Storage;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Transactions;
|
||||
using System.Windows.Forms;
|
||||
using VTSFetcher.Repositories.Entities;
|
||||
|
||||
namespace VTSFetcher.Repositories
|
||||
@@ -8,11 +11,69 @@ namespace VTSFetcher.Repositories
|
||||
public class AnytypeVtsRepository
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private IServiceScope _transactionScope;
|
||||
private AnytypeVtsContext _transactionContext;
|
||||
private IDbContextTransaction _transaction;
|
||||
|
||||
public AnytypeVtsRepository(IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
|
||||
// 데이터베이스 및 테이블 생성
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
db.Database.EnsureCreated();
|
||||
}
|
||||
|
||||
#region Transaction
|
||||
public void BeginTransaction()
|
||||
{
|
||||
_transactionScope = _scopeFactory.CreateScope();
|
||||
_transactionContext = _transactionScope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
_transaction = _transactionContext.Database.BeginTransaction();
|
||||
}
|
||||
|
||||
public void CommitTransaction()
|
||||
{
|
||||
try
|
||||
{
|
||||
_transactionContext?.SaveChanges();
|
||||
_transaction?.Commit();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_transaction?.Dispose();
|
||||
_transactionContext?.Dispose();
|
||||
_transactionScope?.Dispose();
|
||||
_transaction = null;
|
||||
_transactionContext = null;
|
||||
_transactionScope = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void RollbackTransaction()
|
||||
{
|
||||
try
|
||||
{
|
||||
_transaction?.Rollback();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_transaction?.Dispose();
|
||||
_transactionContext?.Dispose();
|
||||
_transactionScope?.Dispose();
|
||||
_transaction = null;
|
||||
_transactionContext = null;
|
||||
_transactionScope = null;
|
||||
}
|
||||
}
|
||||
|
||||
private AnytypeVtsContext GetContext()
|
||||
{
|
||||
return _transactionContext ?? throw new InvalidOperationException("No active transaction. Call BeginTransaction first.");
|
||||
}
|
||||
#endregion Transaction
|
||||
|
||||
#region Configs
|
||||
public Dictionary<string, string> GetConfigs()
|
||||
{
|
||||
@@ -52,6 +113,27 @@ namespace VTSFetcher.Repositories
|
||||
.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
public async Task<List<VtsIssue>> GetTodayNewVtsIssuesAsync()
|
||||
{
|
||||
var today = DateTimeOffset.Now.Date;
|
||||
var tomorrow = today.AddDays(1);
|
||||
var todayOffset = new DateTimeOffset(today);
|
||||
var tomorrowOffset = new DateTimeOffset(tomorrow);
|
||||
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
|
||||
var existingKeys = await db.IssueStatuses.Select(s => s.Key).ToListAsync();
|
||||
var todayIssues = await db.VtsIssues
|
||||
.Where(v => v.Updated >= todayOffset && v.Updated < tomorrowOffset)
|
||||
.ToListAsync();
|
||||
|
||||
var result = todayIssues
|
||||
.Where(v => !existingKeys.Contains(v.Key))
|
||||
.ToList();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void UpsertVtsIssues(List<VtsIssue> vtsIssues, List<string> excludedColumns)
|
||||
{
|
||||
@@ -78,5 +160,186 @@ namespace VTSFetcher.Repositories
|
||||
return result;
|
||||
}
|
||||
#endregion VtsIsseus
|
||||
|
||||
#region IssueStatuses
|
||||
public async Task<List<VtsIssue>> GetIssues4StatusAsync()
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
var result = await db.VtsIssues
|
||||
.Where(i => i.Type.ToLower() != "sub-task"
|
||||
&& i.Type.ToLower() != "epic"
|
||||
&& i.Status.ToLower() != "closed"
|
||||
&& i.Status.ToLower() != "qa passed")
|
||||
.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
public async Task<List<IssueStatus>> GetIssueStatusesAsync()
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
var result = await db.IssueStatuses.ToListAsync();
|
||||
return result;
|
||||
}
|
||||
public async Task<int> DeleteOldIssueStatusesAsync(string status, int daysOld)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
|
||||
var cutoffDate = DateTimeOffset.Now.AddDays(-daysOld);
|
||||
|
||||
var deletedCount = await db.IssueStatuses
|
||||
.Where(i => i.Status.ToLower() == status.ToLower()
|
||||
&& i.Changed < cutoffDate)
|
||||
.ExecuteDeleteAsync();
|
||||
|
||||
return deletedCount;
|
||||
}
|
||||
public async Task InsertIssueStatusesAsync(List<IssueStatus> statuses)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
db.IssueStatuses.AddRange(statuses);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
public async Task UpdateIssueStatusesAsync(List<IssueStatus> statuses)
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
db.IssueStatuses.UpdateRange(statuses);
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
public async Task<List<IssueWithStatus>> GetIssuesWithStatusDetailAsync()
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<AnytypeVtsContext>();
|
||||
|
||||
var result = await (
|
||||
from status in db.IssueStatuses
|
||||
join issue in db.VtsIssues
|
||||
on status.Key equals issue.Key
|
||||
select new IssueWithStatus
|
||||
{
|
||||
Key = issue.Key,
|
||||
Summary = issue.Summary,
|
||||
Parent = issue.Parent,
|
||||
Type = issue.Type,
|
||||
CurrentStatus = issue.Status,
|
||||
Assignee = issue.Assignee,
|
||||
Manager = issue.Manager,
|
||||
Due = issue.Due,
|
||||
Updated = issue.Updated,
|
||||
PreviousStatus = status.Status,
|
||||
Changed = status.Changed,
|
||||
Duration = status.Duration
|
||||
}
|
||||
).ToListAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
#endregion IssueStatuses
|
||||
|
||||
#region IssueStatuses - Transaction Support
|
||||
public List<IssueStatus> GetIssueStatusesInTransaction()
|
||||
{
|
||||
var db = GetContext();
|
||||
var result = db.IssueStatuses.ToList();
|
||||
return result;
|
||||
}
|
||||
|
||||
public void UpdateIssueStatusesInTransaction(List<IssueStatus> statuses)
|
||||
{
|
||||
var db = GetContext();
|
||||
foreach (var status in statuses)
|
||||
{
|
||||
// 이미 추적중인 엔티티가 있는지 확인
|
||||
var existingEntity = db.IssueStatuses.Local
|
||||
.FirstOrDefault(e => e.Key == status.Key);
|
||||
|
||||
if (existingEntity != null)
|
||||
{
|
||||
// 이미 추적중이면 값만 업데이트
|
||||
existingEntity.Status = status.Status;
|
||||
existingEntity.Changed = status.Changed;
|
||||
existingEntity.Duration = status.Duration;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 추적중이지 않으면 Attach 후 Modified 상태로 변경
|
||||
db.IssueStatuses.Attach(status);
|
||||
db.Entry(status).State = EntityState.Modified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<IssueWithStatus> GetIssuesWithStatusDetailInTransaction()
|
||||
{
|
||||
var db = GetContext();
|
||||
var result = (
|
||||
from status in db.IssueStatuses
|
||||
join issue in db.VtsIssues
|
||||
on status.Key equals issue.Key
|
||||
select new IssueWithStatus
|
||||
{
|
||||
Key = issue.Key,
|
||||
Summary = issue.Summary,
|
||||
Parent = issue.Parent,
|
||||
Type = issue.Type,
|
||||
CurrentStatus = issue.Status,
|
||||
Assignee = issue.Assignee,
|
||||
Manager = issue.Manager,
|
||||
Due = issue.Due,
|
||||
Updated = issue.Updated,
|
||||
PreviousStatus = status.Status,
|
||||
Changed = status.Changed,
|
||||
Duration = status.Duration
|
||||
}
|
||||
).ToList();
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<VtsIssue> GetTodayNewVtsIssuesInTransaction()
|
||||
{
|
||||
var today = DateTimeOffset.Now.Date;
|
||||
var tomorrow = today.AddDays(1);
|
||||
var todayOffset = new DateTimeOffset(today);
|
||||
var tomorrowOffset = new DateTimeOffset(tomorrow);
|
||||
|
||||
var db = GetContext();
|
||||
|
||||
var existingKeys = db.IssueStatuses.Select(s => s.Key).ToList();
|
||||
var todayIssues = db.VtsIssues
|
||||
.AsEnumerable()
|
||||
.Where(v => v.Updated >= todayOffset && v.Updated < tomorrowOffset)
|
||||
.ToList();
|
||||
|
||||
var result = todayIssues
|
||||
.Where(v => !existingKeys.Contains(v.Key))
|
||||
.ToList();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void InsertIssueStatusesInTransaction(List<IssueStatus> statuses)
|
||||
{
|
||||
var db = GetContext();
|
||||
db.IssueStatuses.AddRange(statuses);
|
||||
// SaveChanges는 CommitTransaction에서 일괄 처리
|
||||
}
|
||||
|
||||
public int DeleteOldIssueStatusesInTransaction(string status, int daysOld)
|
||||
{
|
||||
var db = GetContext();
|
||||
var cutoffDate = DateTimeOffset.Now.AddDays(-daysOld);
|
||||
|
||||
var itemsToDelete = db.IssueStatuses
|
||||
.AsEnumerable() // 클라이언트 평가로 전환
|
||||
.Where(i => i.Status.Equals(status, StringComparison.OrdinalIgnoreCase)
|
||||
&& i.Changed < cutoffDate)
|
||||
.ToList();
|
||||
db.IssueStatuses.RemoveRange(itemsToDelete);
|
||||
return itemsToDelete.Count;
|
||||
}
|
||||
#endregion IssueStatuses - Transaction Support
|
||||
}
|
||||
}
|
||||
|
||||
18
VTSFetcher/Repositories/Entities/IssueStatus.cs
Normal file
18
VTSFetcher/Repositories/Entities/IssueStatus.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace VTSFetcher.Repositories.Entities;
|
||||
|
||||
public class IssueStatus
|
||||
{
|
||||
[Key]
|
||||
public string Key { get; set; }
|
||||
public string Status { get; set; }
|
||||
public DateTimeOffset Changed { get; set; }
|
||||
public int Duration { get; set; }
|
||||
}
|
||||
19
VTSFetcher/Repositories/Entities/IssueWithStatus.cs
Normal file
19
VTSFetcher/Repositories/Entities/IssueWithStatus.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace VTSFetcher.Repositories.Entities;
|
||||
|
||||
public class IssueWithStatus
|
||||
{
|
||||
public string Key { get; set; }
|
||||
public string Summary { get; set; }
|
||||
public string? Parent { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string CurrentStatus { get; set; }
|
||||
public string Assignee { get; set; }
|
||||
public string Manager { get; set; }
|
||||
public DateTimeOffset Due { get; set; }
|
||||
public DateTimeOffset Updated { get; set; }
|
||||
|
||||
// IssueStatus 정보
|
||||
public string? PreviousStatus { get; set; }
|
||||
public DateTimeOffset? Changed { get; set; }
|
||||
public int Duration { get; set; }
|
||||
}
|
||||
62
VTSFetcher/Services/ExcelExportService.cs
Normal file
62
VTSFetcher/Services/ExcelExportService.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using ClosedXML.Excel;
|
||||
using Serilog;
|
||||
using System.Globalization;
|
||||
using VTSFetcher.Repositories.Entities;
|
||||
|
||||
namespace VTSFetcher.Services;
|
||||
|
||||
public class ExcelExportService
|
||||
{
|
||||
public bool ExportIssueStatusesToExcel(List<IssueWithStatus> issues, string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var workbook = new XLWorkbook();
|
||||
var worksheet = workbook.Worksheets.Add("IssueStatus");
|
||||
|
||||
// 헤더 설정
|
||||
worksheet.Cell(1, 1).Value = "Assignee";
|
||||
worksheet.Cell(1, 2).Value = "Key";
|
||||
worksheet.Cell(1, 3).Value = "Summary";
|
||||
worksheet.Cell(1, 4).Value = "Status";
|
||||
worksheet.Cell(1, 5).Value = "Duration";
|
||||
worksheet.Cell(1, 6).Value = "Changed";
|
||||
|
||||
// 헤더 스타일
|
||||
var headerRange = worksheet.Range(1, 1, 1, 6);
|
||||
headerRange.Style.Font.Bold = true;
|
||||
headerRange.Style.Fill.BackgroundColor = XLColor.LightGray;
|
||||
|
||||
// Assignee로 정렬
|
||||
var sortedIssues = issues.OrderBy(i => i.Assignee).ThenBy(i => i.CurrentStatus).ToList();
|
||||
|
||||
// 데이터 입력
|
||||
for (int i = 0; i < sortedIssues.Count; i++)
|
||||
{
|
||||
var issue = sortedIssues[i];
|
||||
int row = i + 2;
|
||||
|
||||
worksheet.Cell(row, 1).Value = issue.Assignee;
|
||||
worksheet.Cell(row, 2).Value = issue.Key;
|
||||
worksheet.Cell(row, 3).Value = issue.Summary;
|
||||
worksheet.Cell(row, 4).Value = issue.CurrentStatus;
|
||||
worksheet.Cell(row, 5).Value = issue.Duration;
|
||||
worksheet.Cell(row, 6).Value = issue.Changed?.ToString("yyMMdd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
// 열 너비 자동 조정
|
||||
worksheet.Columns().AdjustToContents();
|
||||
|
||||
// 파일 저장
|
||||
workbook.SaveAs(filePath);
|
||||
|
||||
Log.Information("Excel file exported successfully: {file_path}", filePath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Failed to export Excel file: {error_message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ClosedXML" Version="0.105.0" />
|
||||
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.8" />
|
||||
|
||||
@@ -3,6 +3,7 @@ using Serilog;
|
||||
using System.Collections.Concurrent;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
@@ -27,10 +28,12 @@ public class MainViewModel : INotifyPropertyChanged
|
||||
private readonly VtsService _vts;
|
||||
private readonly AnytypeService _anytype;
|
||||
private readonly SlackService _slack;
|
||||
private readonly ExcelExportService _excelExport;
|
||||
private readonly Dictionary<string, string> _configs;
|
||||
private readonly DispatcherTimer _fetchTimer;
|
||||
private readonly DispatcherTimer _publishTimer;
|
||||
private readonly DispatcherTimer _slackTimer;
|
||||
private readonly DispatcherTimer _statusTimer;
|
||||
private readonly CancellationTokenSource _cts;
|
||||
private ConcurrentQueue<VtsIssue> _publishedIssuesQueue;
|
||||
|
||||
@@ -80,16 +83,20 @@ public class MainViewModel : INotifyPropertyChanged
|
||||
|
||||
private bool _isWorking = true;
|
||||
public ICommand FetchNowCommand { get; }
|
||||
public ICommand SaveStatusCommand { get; }
|
||||
public ICommand ReportCommand { get; }
|
||||
#endregion Fields
|
||||
|
||||
public MainViewModel(IOptions<AppSettings> options, AnytypeVtsRepository repo,
|
||||
VtsService vtsService, AnytypeService anytypeService, SlackService slack)
|
||||
VtsService vtsService, AnytypeService anytypeService, SlackService slack,
|
||||
ExcelExportService excelExport)
|
||||
{
|
||||
_appSettings = options.Value;
|
||||
_repo = repo;
|
||||
_vts = vtsService;
|
||||
_anytype = anytypeService;
|
||||
_slack = slack;
|
||||
_excelExport = excelExport;
|
||||
_configs = _repo.GetConfigs();
|
||||
|
||||
_lastFetchTimeOffset = DateTimeOffset.Parse(_configs[Config.LastFetchTimeKey]);
|
||||
@@ -112,7 +119,18 @@ public class MainViewModel : INotifyPropertyChanged
|
||||
_slackTimer.Tick += SlackTick;
|
||||
_slackTimer.Start();
|
||||
|
||||
//_statusTimer = new DispatcherTimer();
|
||||
//_statusTimer.Interval = TimeSpan.FromSeconds(30);
|
||||
//_statusTimer.Tick += StatusTick;
|
||||
//_statusTimer.Start();
|
||||
|
||||
FetchNowCommand = new RelayCommand(_ => FetchNow());
|
||||
SaveStatusCommand = new RelayCommand(_ => {
|
||||
UpdateStatusData();
|
||||
});
|
||||
ReportCommand = new RelayCommand(_ => {
|
||||
GenerateReport();
|
||||
} );
|
||||
|
||||
_cts = new CancellationTokenSource();
|
||||
_publishedIssuesQueue = new();
|
||||
@@ -273,6 +291,87 @@ public class MainViewModel : INotifyPropertyChanged
|
||||
}
|
||||
#endregion FetchEvents
|
||||
|
||||
#region SaveStatusEvents
|
||||
private void StatusTick(object? sender, EventArgs e)
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
if (now.Hour == 21 && now.Minute == 0)
|
||||
{
|
||||
UpdateStatusData();
|
||||
}
|
||||
}
|
||||
private bool UpdateStatusData()
|
||||
{
|
||||
Log.Debug("Start CreateReport");
|
||||
try
|
||||
{
|
||||
_repo.BeginTransaction();
|
||||
|
||||
var issueStatuses = _repo.GetIssueStatusesInTransaction();
|
||||
var issuesWithStatus = _repo.GetIssuesWithStatusDetailInTransaction();
|
||||
var changedIssueStatuses = new List<IssueStatus>();
|
||||
foreach (var issue in issuesWithStatus)
|
||||
{
|
||||
var issueStatus = issueStatuses.Find(s => s.Key == issue.Key);
|
||||
if (issueStatus == null) continue;
|
||||
if (issueStatus.Status.ToLower() == issue.CurrentStatus.ToLower())
|
||||
{
|
||||
issueStatus.Duration++;
|
||||
}
|
||||
else
|
||||
{
|
||||
issueStatus.Status = issue.CurrentStatus;
|
||||
issueStatus.Duration = 0;
|
||||
issueStatus.Changed = issue.Updated;
|
||||
}
|
||||
}
|
||||
_repo.UpdateIssueStatusesInTransaction(issueStatuses);
|
||||
|
||||
// Add new issues.
|
||||
var todayIssues = _repo.GetTodayNewVtsIssuesInTransaction();
|
||||
var newIssueStatuses = new List<IssueStatus>();
|
||||
foreach (var issue in todayIssues)
|
||||
{
|
||||
newIssueStatuses.Add(new IssueStatus
|
||||
{
|
||||
Key = issue.Key,
|
||||
Changed = issue.Updated,
|
||||
Duration = 0,
|
||||
Status = issue.Status
|
||||
});
|
||||
}
|
||||
_repo.InsertIssueStatusesInTransaction(newIssueStatuses);
|
||||
|
||||
// Remove old resolved issues
|
||||
var deletedResolvedStatusCount = _repo.DeleteOldIssueStatusesInTransaction("Resolved", 5);
|
||||
var deletedClosedStatusCount = _repo.DeleteOldIssueStatusesInTransaction("Closed", 1);
|
||||
var deletedQaPassedStatusCount = _repo.DeleteOldIssueStatusesInTransaction("QA PASSED", 1);
|
||||
_repo.CommitTransaction();
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
_repo.RollbackTransaction();
|
||||
Log.Error("Unhandled exception occured during creating report: {error_message}", ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private void GenerateReport()
|
||||
{
|
||||
// Generate report
|
||||
var issues = _repo.GetIssuesWithStatusDetailAsync().Result;
|
||||
var fileName = $"{DateTime.Now:yyyy-MM-dd}.xlsx";
|
||||
var fileFolder = Path.Combine(AppContext.BaseDirectory, "Reports");
|
||||
var filePath = Path.Combine(fileFolder, fileName);
|
||||
var result = _excelExport.ExportIssueStatusesToExcel(issues, filePath);
|
||||
if (result)
|
||||
Log.Information("Successfully exported to Excel: {file_path}", filePath);
|
||||
else
|
||||
Log.Error("Failed to export to Excel: {file_path}", filePath);
|
||||
|
||||
}
|
||||
#endregion SaveStatusEvents
|
||||
|
||||
#region PublishEvents
|
||||
private void PublishTimerTick(object? sender, EventArgs e)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,8 @@ 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("Save status", null, (s, e) => SaveStatusNow());
|
||||
contextMenu.Items.Add("Report", null, (s, e) => ReportNow());
|
||||
contextMenu.Items.Add("Exit", null, (s, e) => ExitApp());
|
||||
|
||||
_notifyIcon.ContextMenuStrip = contextMenu;
|
||||
@@ -47,6 +49,20 @@ public partial class MainWindow : Window
|
||||
vm.FetchNowCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
private void SaveStatusNow()
|
||||
{
|
||||
if (DataContext is MainViewModel vm)
|
||||
{
|
||||
vm.SaveStatusCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
private void ReportNow()
|
||||
{
|
||||
if (DataContext is MainViewModel vm)
|
||||
{
|
||||
vm.ReportCommand.Execute(null);
|
||||
}
|
||||
}
|
||||
private void ExitApp()
|
||||
{
|
||||
_exitFlag = true;
|
||||
|
||||
Reference in New Issue
Block a user