From 26506f749ccf17a9062a96b09538b47f54761d2d Mon Sep 17 00:00:00 2001 From: "pierre@remote" Date: Thu, 11 Jul 2024 17:51:11 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=A0=20errors=20in=20calls=20to=20Jira?= =?UTF-8?q?=20server=20now=20rethrown=20enriched=20with=20call=20arguments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- jwl.infra/UriQueryBuilder.cs | 4 +- jwl.jira/Exceptions/AddWorkLogException.cs | 24 +++++++ jwl.jira/Exceptions/DeleteWorklogException.cs | 23 +++++++ .../Exceptions/GetIssueWorkLogsException.cs | 24 +++++++ jwl.jira/Exceptions/JiraClientException.cs | 21 ++++++ .../Exceptions/JiraIssueSpecificException.cs | 31 +++++++++ jwl.jira/Exceptions/UpdateWorklogException.cs | 27 ++++++++ jwl.jira/Flavours/JiraWithICTimePluginApi.cs | 13 +++- jwl.jira/Flavours/VanillaJiraClient.cs | 66 +++++++++++++++++-- 9 files changed, 225 insertions(+), 8 deletions(-) create mode 100644 jwl.jira/Exceptions/AddWorkLogException.cs create mode 100644 jwl.jira/Exceptions/DeleteWorklogException.cs create mode 100644 jwl.jira/Exceptions/GetIssueWorkLogsException.cs create mode 100644 jwl.jira/Exceptions/JiraClientException.cs create mode 100644 jwl.jira/Exceptions/JiraIssueSpecificException.cs create mode 100644 jwl.jira/Exceptions/UpdateWorklogException.cs diff --git a/jwl.infra/UriQueryBuilder.cs b/jwl.infra/UriQueryBuilder.cs index 8e5389e..bcd2995 100644 --- a/jwl.infra/UriQueryBuilder.cs +++ b/jwl.infra/UriQueryBuilder.cs @@ -38,10 +38,10 @@ public UriQueryBuilder Add(string? key, string? value) } public override string ToString() => this.Any() - ? string.Join('&', this + ? '?' + string.Join('&', this .Where(x => !string.IsNullOrEmpty(x.Key) || !string.IsNullOrEmpty(x.Value)) .Select(x => Uri.EscapeDataString(x.Key ?? string.Empty) + "=" + Uri.EscapeDataString(x.Value ?? string.Empty)) - .Prepend("?")) + ) : string.Empty; public static implicit operator string(UriQueryBuilder self) => self.ToString(); diff --git a/jwl.jira/Exceptions/AddWorkLogException.cs b/jwl.jira/Exceptions/AddWorkLogException.cs new file mode 100644 index 0000000..0aece19 --- /dev/null +++ b/jwl.jira/Exceptions/AddWorkLogException.cs @@ -0,0 +1,24 @@ +namespace jwl.jira.Exceptions; + +internal class AddWorkLogException + : JiraIssueSpecificException +{ + public DateTime Moment { get; } + public int TimeSpentSeconds { get; } + public string? Activity { get; init; } + public string? Comment { get; init; } + + public AddWorkLogException(string issueKey, DateTime moment, int timeSpentSeconds) + : base(issueKey, $"Error adding {timeSpentSeconds} seconds on issue {issueKey} at {moment}") + { + Moment = moment; + TimeSpentSeconds = timeSpentSeconds; + } + + public AddWorkLogException(string issueKey, DateTime moment, int timeSpentSeconds, Exception innerException) + : base(issueKey, $"Error adding {timeSpentSeconds} seconds on issue {issueKey} at {moment}", innerException) + { + Moment = moment; + TimeSpentSeconds = timeSpentSeconds; + } +} \ No newline at end of file diff --git a/jwl.jira/Exceptions/DeleteWorklogException.cs b/jwl.jira/Exceptions/DeleteWorklogException.cs new file mode 100644 index 0000000..1513d4d --- /dev/null +++ b/jwl.jira/Exceptions/DeleteWorklogException.cs @@ -0,0 +1,23 @@ +namespace jwl.jira.Exceptions; + +[Serializable] +internal class DeleteWorklogException + : JiraIssueSpecificException +{ + public long IssueId { get; } + public long WorklogId { get; } + + public DeleteWorklogException(long issueId, long worklogId) + : base($"ID {issueId}", $"Error deleting worklog ID {worklogId} on issue ID {issueId}") + { + IssueId = issueId; + WorklogId = worklogId; + } + + public DeleteWorklogException(long issueId, long worklogId, Exception innerException) + : base($"ID {issueId}", $"Error deleting worklog ID {worklogId} on issue ID {issueId}", innerException) + { + IssueId = issueId; + WorklogId = worklogId; + } +} \ No newline at end of file diff --git a/jwl.jira/Exceptions/GetIssueWorkLogsException.cs b/jwl.jira/Exceptions/GetIssueWorkLogsException.cs new file mode 100644 index 0000000..8f23a58 --- /dev/null +++ b/jwl.jira/Exceptions/GetIssueWorkLogsException.cs @@ -0,0 +1,24 @@ +namespace jwl.jira.Exceptions; + +using System; + +public class GetIssueWorkLogsException + : JiraIssueSpecificException +{ + public DateTime DateFrom { get; } + public DateTime DateTo { get; } + + public GetIssueWorkLogsException(string issueKey, DateTime dateFrom, DateTime dateTo) + : base(issueKey, $"Error retrieving worklogs for {issueKey} and timestamp range from {dateFrom} to {dateTo}") + { + DateFrom = dateFrom; + DateTo = dateTo; + } + + public GetIssueWorkLogsException(string issueKey, DateTime dateFrom, DateTime dateTo, Exception innerException) + : base(issueKey, $"Error retrieving worklogs for issue \"{issueKey}\" and period from {dateFrom} to {dateTo}", innerException) + { + DateFrom = dateFrom; + DateTo = dateTo; + } +} diff --git a/jwl.jira/Exceptions/JiraClientException.cs b/jwl.jira/Exceptions/JiraClientException.cs new file mode 100644 index 0000000..f0be9ca --- /dev/null +++ b/jwl.jira/Exceptions/JiraClientException.cs @@ -0,0 +1,21 @@ +namespace jwl.jira.Exceptions; + +using System; + +public class JiraClientException + : ApplicationException +{ + public JiraClientException() + { + } + + public JiraClientException(string? message) + : base(message) + { + } + + public JiraClientException(string? message, Exception? innerException) + : base(message, innerException) + { + } +} diff --git a/jwl.jira/Exceptions/JiraIssueSpecificException.cs b/jwl.jira/Exceptions/JiraIssueSpecificException.cs new file mode 100644 index 0000000..8dab6f2 --- /dev/null +++ b/jwl.jira/Exceptions/JiraIssueSpecificException.cs @@ -0,0 +1,31 @@ +namespace jwl.jira.Exceptions; + +public class JiraIssueSpecificException + : JiraClientException +{ + public string? IssueKey { get; } + + public JiraIssueSpecificException(string issueKey) + : base($"Error on Jira issue {issueKey}") + { + IssueKey = issueKey; + } + + public JiraIssueSpecificException(string issueKey, string message) + : base(message) + { + IssueKey = issueKey; + } + + public JiraIssueSpecificException(string issueKey, Exception innerException) + : base($"Error on Jira issue {issueKey}", innerException) + { + IssueKey = issueKey; + } + + public JiraIssueSpecificException(string issueKey, string message, Exception innerException) + : base(message, innerException) + { + IssueKey = issueKey; + } +} diff --git a/jwl.jira/Exceptions/UpdateWorklogException.cs b/jwl.jira/Exceptions/UpdateWorklogException.cs new file mode 100644 index 0000000..d80f1e5 --- /dev/null +++ b/jwl.jira/Exceptions/UpdateWorklogException.cs @@ -0,0 +1,27 @@ +namespace jwl.jira.Exceptions; + +public class UpdateWorklogException + : JiraIssueSpecificException +{ + public long WorklogId { get; } + public DateTime Moment { get; } + public int TimeSpentSeconds { get; } + public string? Activity { get; init; } + public string? Comment { get; init; } + + public UpdateWorklogException(string issueKey, long worklogId, DateTime moment, int timeSpentSeconds) + : base(issueKey, $"Error updating worklog ID {worklogId} with {timeSpentSeconds} seconds on issue {issueKey} at {moment}") + { + WorklogId = worklogId; + Moment = moment; + TimeSpentSeconds = timeSpentSeconds; + } + + public UpdateWorklogException(string issueKey, long worklogId, DateTime moment, int timeSpentSeconds, Exception innerException) + : base(issueKey, $"Error updating worklog ID {worklogId} with {timeSpentSeconds} seconds on issue {issueKey} at {moment}", innerException) + { + WorklogId = worklogId; + Moment = moment; + TimeSpentSeconds = timeSpentSeconds; + } +} \ No newline at end of file diff --git a/jwl.jira/Flavours/JiraWithICTimePluginApi.cs b/jwl.jira/Flavours/JiraWithICTimePluginApi.cs index 368e467..ddfe129 100644 --- a/jwl.jira/Flavours/JiraWithICTimePluginApi.cs +++ b/jwl.jira/Flavours/JiraWithICTimePluginApi.cs @@ -5,6 +5,7 @@ using System.Xml.Serialization; using jwl.infra; using jwl.jira.api.rest.request; +using jwl.jira.Exceptions; using jwl.jira.Flavours; using jwl.wadl; @@ -216,7 +217,16 @@ public async Task AddWorkLog(string issueKey, DateOnly day, int timeSpentSeconds Content = new FormUrlEncodedContent(args), }; httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(WadlRepresentation.MediaTypeJson)); - HttpResponseMessage response = await _httpClient.SendAsync(httpRequest); + + HttpResponseMessage response; + try + { + response = await _httpClient.SendAsync(httpRequest); + } + catch (Exception ex) + { + throw new AddWorkLogException(issueKey, day.ToDateTime(TimeOnly.MinValue), timeSpentSeconds, ex); + } if (response.Content.Headers.ContentType?.MediaType != WadlRepresentation.MediaTypeJson) throw new InvalidDataException($"Invalid media type returned ({response.Content.Headers.ContentType?.MediaType ?? string.Empty})"); @@ -257,6 +267,7 @@ private async Task GetWADL() { Uri uri = new Uri($"{_flavourOptions.PluginBaseUri}/application.wadl", UriKind.Relative); using Stream response = await _httpClient.GetStreamAsync(uri); + if (response == null) throw new HttpRequestException($"Empty content received from ${uri}"); diff --git a/jwl.jira/Flavours/VanillaJiraClient.cs b/jwl.jira/Flavours/VanillaJiraClient.cs index b37e1cf..f989edd 100644 --- a/jwl.jira/Flavours/VanillaJiraClient.cs +++ b/jwl.jira/Flavours/VanillaJiraClient.cs @@ -8,6 +8,7 @@ namespace jwl.jira; using System.Xml; using jwl.infra; using jwl.jira.api.rest.response; +using jwl.jira.Exceptions; using jwl.jira.Flavours; public class VanillaJiraClient @@ -89,7 +90,17 @@ public async Task GetIssueWorkLogs(DateOnly from, DateOnly to, string .Add(@"worklog") }; - var response = await _httpClient.GetAsJsonAsync(uriBuilder.Uri.PathAndQuery); + string uri = uriBuilder.Uri.PathAndQuery.TrimStart('/'); + + JiraIssueWorklogs? response; + try + { + response = await _httpClient.GetAsJsonAsync(uri); + } + catch (Exception ex) + { + throw new GetIssueWorkLogsException(issueKey, from.ToDateTime(TimeOnly.MinValue), to.ToDateTime(TimeOnly.MinValue).AddDays(1), ex); + } (DateTime minDt, DateTime supDt) = DateOnlyUtils.DateOnlyRangeToDateTimeRange(from, to); @@ -141,8 +152,10 @@ public async Task AddWorkLog(string issueKey, DateOnly day, int timeSpentSeconds }; StringBuilder commentBuilder = new StringBuilder(); + if (activity != null) commentBuilder.Append($"({activity}){Environment.NewLine}"); + commentBuilder.Append(comment); var request = new api.rest.request.JiraAddWorklogByIssueKey( @@ -155,7 +168,20 @@ public async Task AddWorkLog(string issueKey, DateOnly day, int timeSpentSeconds Comment: commentBuilder.ToString() ); - HttpResponseMessage response = await _httpClient.PostAsJsonAsync(uriBuilder.Uri.PathAndQuery, request); + HttpResponseMessage response; + try + { + response = await _httpClient.PostAsJsonAsync(uriBuilder.Uri.PathAndQuery.TrimStart('/'), request); + } + catch (Exception ex) + { + throw new AddWorkLogException(issueKey, day.ToDateTime(TimeOnly.MinValue), timeSpentSeconds, ex) + { + Activity = activity, + Comment = comment + }; + } + await CheckHttpResponseForErrorMessages(response); } @@ -190,7 +216,16 @@ public async Task DeleteWorkLog(long issueId, long worklogId, bool notifyUsers = .Add(@"notifyUsers", notifyUsers.ToString().ToLower()) }; - HttpResponseMessage response = await _httpClient.DeleteAsync(uriBuilder.Uri.PathAndQuery); + HttpResponseMessage response; + try + { + response = await _httpClient.DeleteAsync(uriBuilder.Uri.PathAndQuery.TrimStart('/')); + } + catch (Exception ex) + { + throw new DeleteWorklogException(issueId, worklogId, ex); + } + await CheckHttpResponseForErrorMessages(response); } @@ -213,7 +248,20 @@ public async Task UpdateWorkLog(string issueKey, long worklogId, DateOnly day, i Comment: comment ); - HttpResponseMessage response = await _httpClient.PutAsJsonAsync(uriBuilder.Uri.PathAndQuery, request); + HttpResponseMessage response; + try + { + response = await _httpClient.PutAsJsonAsync(uriBuilder.Uri.PathAndQuery.TrimStart('/'), request); + } + catch (Exception ex) + { + throw new UpdateWorklogException(issueKey, worklogId, day.ToDateTime(TimeOnly.MinValue), timeSpentSeconds, ex) + { + Activity = activity, + Comment = comment + }; + } + await CheckHttpResponseForErrorMessages(response); } @@ -225,6 +273,14 @@ public async Task UpdateWorkLog(string issueKey, long worklogId, DateOnly day, i Query = new UriQueryBuilder() .Add(@"username", UserName) }; - return await _httpClient.GetAsJsonAsync(uriBuilder.Uri.PathAndQuery); + + try + { + return await _httpClient.GetAsJsonAsync(uriBuilder.Uri.PathAndQuery.TrimStart('/')); + } + catch (Exception ex) + { + throw new JiraClientException($"Error retrieving user {UserName} info", ex); + } } }