이슈 변경 사항을 추적하기 위한 서비스 구현.
- DB 추적 및 Excel 리포트 출력
This commit is contained in:
@@ -68,6 +68,7 @@ public partial class App : System.Windows.Application
|
|||||||
services.AddSingleton<SlackService>();
|
services.AddSingleton<SlackService>();
|
||||||
services.AddDbContext<AnytypeVtsContext>();
|
services.AddDbContext<AnytypeVtsContext>();
|
||||||
services.AddSingleton<AnytypeVtsRepository>();
|
services.AddSingleton<AnytypeVtsRepository>();
|
||||||
|
services.AddSingleton<ExcelExportService>();
|
||||||
services.AddTransient<MainViewModel>();
|
services.AddTransient<MainViewModel>();
|
||||||
services.AddTransient<MainWindow>();
|
services.AddTransient<MainWindow>();
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ namespace VTSFetcher.Repositories;
|
|||||||
public partial class AnytypeVtsContext : DbContext
|
public partial class AnytypeVtsContext : DbContext
|
||||||
{
|
{
|
||||||
private readonly AppSettings _appSettings;
|
private readonly AppSettings _appSettings;
|
||||||
public DbSet<VtsIssue> VtsIssues { get;set; }
|
public DbSet<VtsIssue> VtsIssues { get; set; }
|
||||||
public DbSet<Config> Configs { get; set; }
|
public DbSet<Config> Configs { get; set; }
|
||||||
|
public DbSet<IssueStatus> IssueStatuses { get; set; }
|
||||||
public string DbPath { get; }
|
public string DbPath { get; }
|
||||||
|
|
||||||
public AnytypeVtsContext(IOptions<AppSettings> options)
|
public AnytypeVtsContext(IOptions<AppSettings> options)
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
using EFCore.BulkExtensions;
|
using EFCore.BulkExtensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using System.Transactions;
|
||||||
|
using System.Windows.Forms;
|
||||||
using VTSFetcher.Repositories.Entities;
|
using VTSFetcher.Repositories.Entities;
|
||||||
|
|
||||||
namespace VTSFetcher.Repositories
|
namespace VTSFetcher.Repositories
|
||||||
@@ -8,11 +11,69 @@ namespace VTSFetcher.Repositories
|
|||||||
public class AnytypeVtsRepository
|
public class AnytypeVtsRepository
|
||||||
{
|
{
|
||||||
private readonly IServiceScopeFactory _scopeFactory;
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
private IServiceScope _transactionScope;
|
||||||
|
private AnytypeVtsContext _transactionContext;
|
||||||
|
private IDbContextTransaction _transaction;
|
||||||
|
|
||||||
public AnytypeVtsRepository(IServiceScopeFactory scopeFactory)
|
public AnytypeVtsRepository(IServiceScopeFactory scopeFactory)
|
||||||
{
|
{
|
||||||
_scopeFactory = 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
|
#region Configs
|
||||||
public Dictionary<string, string> GetConfigs()
|
public Dictionary<string, string> GetConfigs()
|
||||||
{
|
{
|
||||||
@@ -52,6 +113,27 @@ namespace VTSFetcher.Repositories
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
return result;
|
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)
|
public void UpsertVtsIssues(List<VtsIssue> vtsIssues, List<string> excludedColumns)
|
||||||
{
|
{
|
||||||
@@ -78,5 +160,186 @@ namespace VTSFetcher.Repositories
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
#endregion VtsIsseus
|
#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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="ClosedXML" Version="0.105.0" />
|
||||||
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
|
<PackageReference Include="EFCore.BulkExtensions" Version="9.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.8" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" 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.Collections.Concurrent;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.Intrinsics.X86;
|
using System.Runtime.Intrinsics.X86;
|
||||||
@@ -27,10 +28,12 @@ public class MainViewModel : INotifyPropertyChanged
|
|||||||
private readonly VtsService _vts;
|
private readonly VtsService _vts;
|
||||||
private readonly AnytypeService _anytype;
|
private readonly AnytypeService _anytype;
|
||||||
private readonly SlackService _slack;
|
private readonly SlackService _slack;
|
||||||
|
private readonly ExcelExportService _excelExport;
|
||||||
private readonly Dictionary<string, string> _configs;
|
private readonly Dictionary<string, string> _configs;
|
||||||
private readonly DispatcherTimer _fetchTimer;
|
private readonly DispatcherTimer _fetchTimer;
|
||||||
private readonly DispatcherTimer _publishTimer;
|
private readonly DispatcherTimer _publishTimer;
|
||||||
private readonly DispatcherTimer _slackTimer;
|
private readonly DispatcherTimer _slackTimer;
|
||||||
|
private readonly DispatcherTimer _statusTimer;
|
||||||
private readonly CancellationTokenSource _cts;
|
private readonly CancellationTokenSource _cts;
|
||||||
private ConcurrentQueue<VtsIssue> _publishedIssuesQueue;
|
private ConcurrentQueue<VtsIssue> _publishedIssuesQueue;
|
||||||
|
|
||||||
@@ -80,16 +83,20 @@ public class MainViewModel : INotifyPropertyChanged
|
|||||||
|
|
||||||
private bool _isWorking = true;
|
private bool _isWorking = true;
|
||||||
public ICommand FetchNowCommand { get; }
|
public ICommand FetchNowCommand { get; }
|
||||||
|
public ICommand SaveStatusCommand { get; }
|
||||||
|
public ICommand ReportCommand { get; }
|
||||||
#endregion Fields
|
#endregion Fields
|
||||||
|
|
||||||
public MainViewModel(IOptions<AppSettings> options, AnytypeVtsRepository repo,
|
public MainViewModel(IOptions<AppSettings> options, AnytypeVtsRepository repo,
|
||||||
VtsService vtsService, AnytypeService anytypeService, SlackService slack)
|
VtsService vtsService, AnytypeService anytypeService, SlackService slack,
|
||||||
|
ExcelExportService excelExport)
|
||||||
{
|
{
|
||||||
_appSettings = options.Value;
|
_appSettings = options.Value;
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
_vts = vtsService;
|
_vts = vtsService;
|
||||||
_anytype = anytypeService;
|
_anytype = anytypeService;
|
||||||
_slack = slack;
|
_slack = slack;
|
||||||
|
_excelExport = excelExport;
|
||||||
_configs = _repo.GetConfigs();
|
_configs = _repo.GetConfigs();
|
||||||
|
|
||||||
_lastFetchTimeOffset = DateTimeOffset.Parse(_configs[Config.LastFetchTimeKey]);
|
_lastFetchTimeOffset = DateTimeOffset.Parse(_configs[Config.LastFetchTimeKey]);
|
||||||
@@ -112,7 +119,18 @@ public class MainViewModel : INotifyPropertyChanged
|
|||||||
_slackTimer.Tick += SlackTick;
|
_slackTimer.Tick += SlackTick;
|
||||||
_slackTimer.Start();
|
_slackTimer.Start();
|
||||||
|
|
||||||
|
//_statusTimer = new DispatcherTimer();
|
||||||
|
//_statusTimer.Interval = TimeSpan.FromSeconds(30);
|
||||||
|
//_statusTimer.Tick += StatusTick;
|
||||||
|
//_statusTimer.Start();
|
||||||
|
|
||||||
FetchNowCommand = new RelayCommand(_ => FetchNow());
|
FetchNowCommand = new RelayCommand(_ => FetchNow());
|
||||||
|
SaveStatusCommand = new RelayCommand(_ => {
|
||||||
|
UpdateStatusData();
|
||||||
|
});
|
||||||
|
ReportCommand = new RelayCommand(_ => {
|
||||||
|
GenerateReport();
|
||||||
|
} );
|
||||||
|
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
_publishedIssuesQueue = new();
|
_publishedIssuesQueue = new();
|
||||||
@@ -273,6 +291,87 @@ public class MainViewModel : INotifyPropertyChanged
|
|||||||
}
|
}
|
||||||
#endregion FetchEvents
|
#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
|
#region PublishEvents
|
||||||
private void PublishTimerTick(object? sender, EventArgs e)
|
private void PublishTimerTick(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public partial class MainWindow : Window
|
|||||||
var contextMenu = new ContextMenuStrip();
|
var contextMenu = new ContextMenuStrip();
|
||||||
contextMenu.Items.Add("Open", null, (s, e) => ShowWindow());
|
contextMenu.Items.Add("Open", null, (s, e) => ShowWindow());
|
||||||
contextMenu.Items.Add("Fetch", null, (s, e) => FetchNow());
|
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());
|
contextMenu.Items.Add("Exit", null, (s, e) => ExitApp());
|
||||||
|
|
||||||
_notifyIcon.ContextMenuStrip = contextMenu;
|
_notifyIcon.ContextMenuStrip = contextMenu;
|
||||||
@@ -47,6 +49,20 @@ public partial class MainWindow : Window
|
|||||||
vm.FetchNowCommand.Execute(null);
|
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()
|
private void ExitApp()
|
||||||
{
|
{
|
||||||
_exitFlag = true;
|
_exitFlag = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user