diff --git a/IssueTracker/IssueRow.cs b/IssueTracker/IssueRow.cs new file mode 100644 index 0000000..7d4be8d --- /dev/null +++ b/IssueTracker/IssueRow.cs @@ -0,0 +1,24 @@ +using CsvHelper.Configuration.Attributes; + +namespace IssueTracker; + +public class IssueRow +{ + [Name("Issue Type")] + public string IssueType { get; set; } = ""; + + [Name("Issue key")] + public string IssueKey { get; set; } = ""; + + [Name("Issue id")] + public string IssueId { get; set; } = ""; + + public string Summary { get; set; } = ""; + public string Assignee { get; set; } = ""; + public string Manager { get; set; } = ""; + public string Status { get; set; } = ""; + public string Created { get; set; } = ""; + public string Updated { get; set; } = ""; + + public int WorkingDate { get; set; } +} diff --git a/IssueTracker/IssueTracker.csproj b/IssueTracker/IssueTracker.csproj index ed9781c..cdc69f1 100644 --- a/IssueTracker/IssueTracker.csproj +++ b/IssueTracker/IssueTracker.csproj @@ -7,4 +7,8 @@ enable + + + + diff --git a/IssueTracker/Program.cs b/IssueTracker/Program.cs index 0f07ab8..8f0b885 100644 --- a/IssueTracker/Program.cs +++ b/IssueTracker/Program.cs @@ -1,10 +1,165 @@ -namespace IssueTracker +using CsvHelper; +using CsvHelper.Configuration; +using System.Globalization; + +namespace IssueTracker; + +internal class Program { - internal class Program + static int Main(string[] args) { - static void Main(string[] args) + if (args.Length < 2) { - Console.WriteLine("Hello, World!"); + Console.WriteLine("Usage: IssueTracker "); + return -1; } + + var inputCsv = args[0]; + var outputCsv = args[1]; + + if (!File.Exists(inputCsv)) + { + Console.WriteLine($"Input CSV not found: {inputCsv}"); + return -2; + } + + Console.WriteLine("========================================"); + Console.WriteLine(" Issue Tracker started"); + Console.WriteLine($" Input : {inputCsv}"); + Console.WriteLine($" Output: {outputCsv}"); + Console.WriteLine("========================================"); + + var csvConfig = new CsvConfiguration(CultureInfo.InvariantCulture) + { + HasHeaderRecord = true, + IgnoreBlankLines = true, + BadDataFound = null, // 깨진 데이터 무시 + MissingFieldFound = null, // 컬럼 누락 무시 + HeaderValidated = null, // 헤더 검증 비활성화 + PrepareHeaderForMatch = args => args.Header.Trim() // 헤더 공백 제거 + }; + + // 1️. 최초 실행: output CSV가 없는 경우 + if (!File.Exists(outputCsv)) + { + Console.WriteLine("Tracking CSV not found. Creating new file."); + + var firstIssues = LoadCsv(inputCsv, csvConfig); + Console.WriteLine($"[Initial] Loaded {firstIssues.Count} issues from input CSV"); + + SaveCsv(outputCsv, firstIssues, csvConfig); + + Console.WriteLine("Tracking CSV created successfully."); + return 0; + } + + // 2️. 기존 Tracking CSV 로드 + var previousIssues = LoadCsv(outputCsv, csvConfig) + .ToDictionary(x => x.IssueKey); + Console.WriteLine($"[Previous] Loaded {previousIssues.Count} issues from tracking CSV"); + + // 3️. 오늘 CSV 로드 + var todayIssues = LoadCsv(inputCsv, csvConfig) + .ToDictionary(x => x.IssueKey); + Console.WriteLine($"[Today] Loaded {todayIssues.Count} issues from input CSV"); + + var newIssues = new List(); + var updatedIssues = new List(); + var retainedIssues = new List(); + + int updatedCount = 0; + int retainedCount = 0; + int newCount = 0; + + // 4️. 기존 모든 이슈 처리 (입력에 없어도 유지) + foreach (var (key, old) in previousIssues) + { + if (todayIssues.TryGetValue(key, out var issue)) + { + // 입력에 존재하는 이슈 -> 업데이트 + if (string.Equals(old.Status, issue.Status, StringComparison.OrdinalIgnoreCase)) + { + // 상태 유지 + issue.WorkingDate = old.WorkingDate + 1; + } + else + { + // 상태 변경 + if (string.Equals(issue.Status, "Resolved", StringComparison.OrdinalIgnoreCase)) + { + // Resolved -> 값 유지 + 추적 중지 + issue.WorkingDate = old.WorkingDate; + } + else + { + // 다른 상태 변경 -> 초기화 + issue.WorkingDate = 0; + } + } + + updatedIssues.Add(issue); + updatedCount++; + } + else + { + // 입력에 없는 이슈 -> 기존 데이터 그대로 유지 (아래쪽 배치) + retainedIssues.Add(old); + retainedCount++; + //Console.WriteLine($"[Retained] Issue {key} not in input CSV, keeping in output"); + } + } + + // 5️. 입력에는 있지만 기존 추적에 없던 신규 이슈 추가 (위쪽 배치) + foreach (var (key, issue) in todayIssues) + { + if (!previousIssues.ContainsKey(key)) + { + // 신규 Issue + issue.WorkingDate = 0; + newIssues.Add(issue); + newCount++; + Console.WriteLine($"[New] Issue {key} added to tracking"); + } + } + + Console.WriteLine("----------------------------------------"); + Console.WriteLine($"Updated: {updatedCount}, Retained: {retainedCount}, New: {newCount}"); + Console.WriteLine("----------------------------------------"); + + // 6️. 정렬: 신규 -> 업데이트 -> 추적 중단 + var result = new List(); + result.AddRange(newIssues); + result.AddRange(updatedIssues); + result.AddRange(retainedIssues); + + Console.WriteLine($"Total output rows: {result.Count}"); + Console.WriteLine("----------------------------------------"); + + // 7️. 결과 저장 + SaveCsv(outputCsv, result, csvConfig); + + Console.WriteLine("Tracking CSV updated successfully."); + return 0; + } + + private static List LoadCsv(string path, CsvConfiguration config) + { + using var reader = new StreamReader(path); + using var csv = new CsvReader(reader, config); + + return csv.GetRecords().ToList(); + } + + private static void SaveCsv( + string path, + List data, + CsvConfiguration config) + { + using var writer = new StreamWriter(path, false, System.Text.Encoding.UTF8); + using var csv = new CsvWriter(writer, config); + + csv.WriteHeader(); + csv.NextRecord(); + csv.WriteRecords(data); } }