From f9b2b8ac08a244ffa7e5af1e1a0db891bbca64f3 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 18:26:04 +0100 Subject: [PATCH 01/16] Remove obsolete Epsilon.Http.Abstractions project --- .../Http}/HttpService.cs | 2 +- .../Http}/Json/HttpClientJsonExtensions.cs | 2 +- .../Http}/Json/HttpResponseMessageJsonExtensions.cs | 2 +- Epsilon.Canvas.Abstractions/Model/Module.cs | 1 - .../Model/OutcomeResultCollection.cs | 3 +-- .../Service/IOutcomeHttpService.cs | 1 - Epsilon.Canvas/CanvasModuleCollectionFetcher.cs | 1 - Epsilon.Canvas/Epsilon.Canvas.csproj | 2 +- Epsilon.Canvas/Service/AssignmentHttpService.cs | 6 +++--- Epsilon.Canvas/Service/ModuleHttpService.cs | 4 ++-- Epsilon.Canvas/Service/OutcomeHttpService.cs | 5 ++--- Epsilon.Canvas/Service/PaginatorHttpService.cs | 4 ++-- Epsilon.Canvas/Service/SubmissionHttpService.cs | 2 +- .../Epsilon.Http.Abstractions.csproj | 9 --------- Epsilon.sln | 6 ------ 15 files changed, 15 insertions(+), 35 deletions(-) rename {Epsilon.Http.Abstractions => Epsilon.Abstractions/Http}/HttpService.cs (78%) rename {Epsilon.Http.Abstractions => Epsilon.Abstractions/Http}/Json/HttpClientJsonExtensions.cs (97%) rename {Epsilon.Http.Abstractions => Epsilon.Abstractions/Http}/Json/HttpResponseMessageJsonExtensions.cs (96%) delete mode 100644 Epsilon.Http.Abstractions/Epsilon.Http.Abstractions.csproj diff --git a/Epsilon.Http.Abstractions/HttpService.cs b/Epsilon.Abstractions/Http/HttpService.cs similarity index 78% rename from Epsilon.Http.Abstractions/HttpService.cs rename to Epsilon.Abstractions/Http/HttpService.cs index 8a6ac3f2..c5643701 100644 --- a/Epsilon.Http.Abstractions/HttpService.cs +++ b/Epsilon.Abstractions/Http/HttpService.cs @@ -1,4 +1,4 @@ -namespace Epsilon.Http.Abstractions; +namespace Epsilon.Abstractions.Http; public abstract class HttpService { diff --git a/Epsilon.Http.Abstractions/Json/HttpClientJsonExtensions.cs b/Epsilon.Abstractions/Http/Json/HttpClientJsonExtensions.cs similarity index 97% rename from Epsilon.Http.Abstractions/Json/HttpClientJsonExtensions.cs rename to Epsilon.Abstractions/Http/Json/HttpClientJsonExtensions.cs index d079116c..b1810d7f 100644 --- a/Epsilon.Http.Abstractions/Json/HttpClientJsonExtensions.cs +++ b/Epsilon.Abstractions/Http/Json/HttpClientJsonExtensions.cs @@ -1,4 +1,4 @@ -namespace Epsilon.Http.Abstractions.Json; +namespace Epsilon.Abstractions.Http.Json; public static class HttpClientJsonExtensions { diff --git a/Epsilon.Http.Abstractions/Json/HttpResponseMessageJsonExtensions.cs b/Epsilon.Abstractions/Http/Json/HttpResponseMessageJsonExtensions.cs similarity index 96% rename from Epsilon.Http.Abstractions/Json/HttpResponseMessageJsonExtensions.cs rename to Epsilon.Abstractions/Http/Json/HttpResponseMessageJsonExtensions.cs index e890303a..be5064cc 100644 --- a/Epsilon.Http.Abstractions/Json/HttpResponseMessageJsonExtensions.cs +++ b/Epsilon.Abstractions/Http/Json/HttpResponseMessageJsonExtensions.cs @@ -1,6 +1,6 @@ using System.Text.Json; -namespace Epsilon.Http.Abstractions.Json; +namespace Epsilon.Abstractions.Http.Json; public static class HttpResponseMessageJsonExtensions { diff --git a/Epsilon.Canvas.Abstractions/Model/Module.cs b/Epsilon.Canvas.Abstractions/Model/Module.cs index 3b53730b..954a8771 100644 --- a/Epsilon.Canvas.Abstractions/Model/Module.cs +++ b/Epsilon.Canvas.Abstractions/Model/Module.cs @@ -1,5 +1,4 @@ using System.Text.Json.Serialization; -using Epsilon.Canvas.Abstractions.Response; namespace Epsilon.Canvas.Abstractions.Model; diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs index d2012232..6443f307 100644 --- a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs @@ -1,7 +1,6 @@ using System.Text.Json.Serialization; -using Epsilon.Canvas.Abstractions.Model; -namespace Epsilon.Canvas.Abstractions.Response; +namespace Epsilon.Canvas.Abstractions.Model; public record OutcomeResultCollection( [property: JsonPropertyName("outcome_results")] IEnumerable OutcomeResults, diff --git a/Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs b/Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs index 01602bd1..dec04af8 100644 --- a/Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs +++ b/Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs @@ -1,5 +1,4 @@ using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Response; namespace Epsilon.Canvas.Abstractions.Service; diff --git a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs index 3ff5a2fa..d7c0c1d1 100644 --- a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs +++ b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs @@ -1,6 +1,5 @@ using Epsilon.Canvas.Abstractions; using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Response; using Epsilon.Canvas.Abstractions.Service; using Microsoft.Extensions.Logging; diff --git a/Epsilon.Canvas/Epsilon.Canvas.csproj b/Epsilon.Canvas/Epsilon.Canvas.csproj index 46691de6..f1687d2d 100644 --- a/Epsilon.Canvas/Epsilon.Canvas.csproj +++ b/Epsilon.Canvas/Epsilon.Canvas.csproj @@ -7,8 +7,8 @@ + - diff --git a/Epsilon.Canvas/Service/AssignmentHttpService.cs b/Epsilon.Canvas/Service/AssignmentHttpService.cs index 3d531f0f..b1ad80b8 100644 --- a/Epsilon.Canvas/Service/AssignmentHttpService.cs +++ b/Epsilon.Canvas/Service/AssignmentHttpService.cs @@ -1,7 +1,7 @@ -using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Abstractions.Http; +using Epsilon.Abstractions.Http.Json; +using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; -using Epsilon.Http.Abstractions; -using Epsilon.Http.Abstractions.Json; namespace Epsilon.Canvas.Service; diff --git a/Epsilon.Canvas/Service/ModuleHttpService.cs b/Epsilon.Canvas/Service/ModuleHttpService.cs index c7375c4e..4142c27f 100644 --- a/Epsilon.Canvas/Service/ModuleHttpService.cs +++ b/Epsilon.Canvas/Service/ModuleHttpService.cs @@ -1,8 +1,8 @@ using System.Text; +using Epsilon.Abstractions.Http; +using Epsilon.Abstractions.Http.Json; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; -using Epsilon.Http.Abstractions; -using Epsilon.Http.Abstractions.Json; namespace Epsilon.Canvas.Service; diff --git a/Epsilon.Canvas/Service/OutcomeHttpService.cs b/Epsilon.Canvas/Service/OutcomeHttpService.cs index fd3ec7da..9ba80c4a 100644 --- a/Epsilon.Canvas/Service/OutcomeHttpService.cs +++ b/Epsilon.Canvas/Service/OutcomeHttpService.cs @@ -1,9 +1,8 @@ using System.Text; +using Epsilon.Abstractions.Http; +using Epsilon.Abstractions.Http.Json; using Epsilon.Canvas.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Response; using Epsilon.Canvas.Abstractions.Service; -using Epsilon.Http.Abstractions; -using Epsilon.Http.Abstractions.Json; namespace Epsilon.Canvas.Service; diff --git a/Epsilon.Canvas/Service/PaginatorHttpService.cs b/Epsilon.Canvas/Service/PaginatorHttpService.cs index eb5675a7..8176c951 100644 --- a/Epsilon.Canvas/Service/PaginatorHttpService.cs +++ b/Epsilon.Canvas/Service/PaginatorHttpService.cs @@ -1,8 +1,8 @@ using System.Web; +using Epsilon.Abstractions.Http; +using Epsilon.Abstractions.Http.Json; using Epsilon.Canvas.Abstractions.Converter; using Epsilon.Canvas.Abstractions.Service; -using Epsilon.Http.Abstractions; -using Epsilon.Http.Abstractions.Json; namespace Epsilon.Canvas.Service; diff --git a/Epsilon.Canvas/Service/SubmissionHttpService.cs b/Epsilon.Canvas/Service/SubmissionHttpService.cs index 434d5a64..7b301009 100644 --- a/Epsilon.Canvas/Service/SubmissionHttpService.cs +++ b/Epsilon.Canvas/Service/SubmissionHttpService.cs @@ -1,7 +1,7 @@ using System.Text; +using Epsilon.Abstractions.Http; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; -using Epsilon.Http.Abstractions; namespace Epsilon.Canvas.Service; diff --git a/Epsilon.Http.Abstractions/Epsilon.Http.Abstractions.csproj b/Epsilon.Http.Abstractions/Epsilon.Http.Abstractions.csproj deleted file mode 100644 index eb2460e9..00000000 --- a/Epsilon.Http.Abstractions/Epsilon.Http.Abstractions.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - - net6.0 - enable - enable - - - diff --git a/Epsilon.sln b/Epsilon.sln index c1f11b5a..7bb4b9c2 100644 --- a/Epsilon.sln +++ b/Epsilon.sln @@ -12,8 +12,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Epsilon", "Epsilon\Epsilon. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Epsilon.Abstractions", "Epsilon.Abstractions\Epsilon.Abstractions.csproj", "{4AEA96B5-B30A-49C7-8871-29D18587936F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Epsilon.Http.Abstractions", "Epsilon.Http.Abstractions\Epsilon.Http.Abstractions.csproj", "{4BC41253-DFBF-4C5F-94AE-F639F306207C}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -44,9 +42,5 @@ Global {4AEA96B5-B30A-49C7-8871-29D18587936F}.Debug|Any CPU.Build.0 = Debug|Any CPU {4AEA96B5-B30A-49C7-8871-29D18587936F}.Release|Any CPU.ActiveCfg = Release|Any CPU {4AEA96B5-B30A-49C7-8871-29D18587936F}.Release|Any CPU.Build.0 = Release|Any CPU - {4BC41253-DFBF-4C5F-94AE-F639F306207C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BC41253-DFBF-4C5F-94AE-F639F306207C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BC41253-DFBF-4C5F-94AE-F639F306207C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BC41253-DFBF-4C5F-94AE-F639F306207C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 8704e9e4dc569978883aa3d81701f5ce78a5d5da Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 19:26:20 +0100 Subject: [PATCH 02/16] Fix invalid serializable implementation --- .../Export/Exceptions/NoExportersFoundException.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Epsilon/Export/Exceptions/NoExportersFoundException.cs b/Epsilon/Export/Exceptions/NoExportersFoundException.cs index f1e498e0..2cf7b1db 100644 --- a/Epsilon/Export/Exceptions/NoExportersFoundException.cs +++ b/Epsilon/Export/Exceptions/NoExportersFoundException.cs @@ -1,4 +1,6 @@ -namespace Epsilon.Export.Exceptions; +using System.Runtime.Serialization; + +namespace Epsilon.Export.Exceptions; [Serializable] public class NoExportersFoundException : Exception @@ -7,7 +9,13 @@ public NoExportersFoundException() { } - public NoExportersFoundException(IEnumerable formats) : base($"No exporters could be found with the given formats {string.Join(",", formats)}") + protected NoExportersFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + public NoExportersFoundException(IEnumerable formats) + : base($"No exporters could be found with the given formats {string.Join(",", formats)}") { } } \ No newline at end of file From c7e007fca91b6bfecbd4eb0e666952be1ea17738 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 19:29:25 +0100 Subject: [PATCH 03/16] Disable unused method return value hint --- Epsilon.Canvas/CanvasServiceCollectionExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs b/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs index f77b6274..9888c720 100644 --- a/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs +++ b/Epsilon.Canvas/CanvasServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ public static class CanvasServiceCollectionExtensions { private const string CanvasHttpClient = "CanvasHttpClient"; + // ReSharper disable once UnusedMethodReturnValue.Global public static IServiceCollection AddCanvas(this IServiceCollection services, IConfiguration config) { services.Configure(config); From d723f6b2bdf9b9e4cf47919c8d2c7ded757f14c4 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 19:31:29 +0100 Subject: [PATCH 04/16] Change exception to more appropriate one --- Epsilon.Canvas/Converter/LinkHeaderConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Epsilon.Canvas/Converter/LinkHeaderConverter.cs b/Epsilon.Canvas/Converter/LinkHeaderConverter.cs index d42566b2..ab318a5c 100644 --- a/Epsilon.Canvas/Converter/LinkHeaderConverter.cs +++ b/Epsilon.Canvas/Converter/LinkHeaderConverter.cs @@ -13,7 +13,7 @@ public LinkHeader ConvertFrom(HttpResponseMessage response) { if (!response.Headers.Contains("Link")) { - throw new ArgumentNullException(nameof(response.Headers), "Header does not contain link key"); + throw new KeyNotFoundException("Header does not contain link key"); } return ConvertFrom(response.Headers.GetValues("Link").First()); From daa954c68ea87098d18d31ed3746f560dbf55168 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 19:37:09 +0100 Subject: [PATCH 05/16] Update Grade score description --- .../Model/OutcomeResult.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs index 6527e71f..9c472784 100644 --- a/Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs @@ -11,16 +11,13 @@ public record OutcomeResult( { public string Grade() { - switch (Score) + return Score switch { - default: - return "Unsatisfactory"; - case 3: - return "Satisfactory"; - case 4: - return "Good"; - case 5: - return "Outstanding"; - } + <= 2 => "Unsatisfactory", + 3 => "Satisfactory", + 4 => "Good", + 5 => "Outstanding", + _ => "Not graded", + }; } } \ No newline at end of file From 870b445d6b5a345f984ebd91100c0cb3ebe04bff Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 19:44:25 +0100 Subject: [PATCH 06/16] Prevent null grades from exporting --- .../Model/OutcomeResult.cs | 4 ++-- Epsilon/Export/Exporters/CsvModuleExporter.cs | 20 +++++++++++-------- .../Export/Exporters/ExcelModuleExporter.cs | 2 +- .../Export/Exporters/WordModuleExporter.cs | 2 +- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs index 9c472784..f275f162 100644 --- a/Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs @@ -9,7 +9,7 @@ public record OutcomeResult( [property: JsonPropertyName("links")] OutcomeResultLink Link ) { - public string Grade() + public string? Grade() { return Score switch { @@ -17,7 +17,7 @@ public string Grade() 3 => "Satisfactory", 4 => "Good", 5 => "Outstanding", - _ => "Not graded", + _ => null, }; } } \ No newline at end of file diff --git a/Epsilon/Export/Exporters/CsvModuleExporter.cs b/Epsilon/Export/Exporters/CsvModuleExporter.cs index d387a23e..7f955d47 100644 --- a/Epsilon/Export/Exporters/CsvModuleExporter.cs +++ b/Epsilon/Export/Exporters/CsvModuleExporter.cs @@ -46,15 +46,19 @@ private static DataTable CreateDataTable(IEnumerable modules) { var outcome = links.OutcomesDictionary[result.Link.Outcome]; var alignment = links.AlignmentsDictionary[result.Link.Alignment]; + var grade = result.Grade(); - dt.Rows.Add( - outcome.Id, - alignment.Id, - alignment.Name, - outcome.Title, - result.Grade(), - module.Name - ); + if (grade != null) + { + dt.Rows.Add( + outcome.Id, + alignment.Id, + alignment.Name, + outcome.Title, + result.Grade(), + module.Name + ); + } } } diff --git a/Epsilon/Export/Exporters/ExcelModuleExporter.cs b/Epsilon/Export/Exporters/ExcelModuleExporter.cs index c3fe7b91..eeabf6b6 100644 --- a/Epsilon/Export/Exporters/ExcelModuleExporter.cs +++ b/Epsilon/Export/Exporters/ExcelModuleExporter.cs @@ -46,7 +46,7 @@ public void Export(IEnumerable modules, string format) foreach (var (outcomeId, outcome) in outcomes) { var assignmentIds = module.Collection.OutcomeResults - .Where(o => o.Link.Outcome == outcomeId) + .Where(o => o.Link.Outcome == outcomeId && o.Grade() != null) .Select(static o => o.Link.Assignment) .ToArray(); diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index 62feafaf..810fe03a 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -45,7 +45,7 @@ public void Export(IEnumerable modules, string format) foreach (var (outcomeId, outcome) in outcomes) { var assignmentIds = module.Collection.OutcomeResults - .Where(o => o.Link.Outcome == outcomeId) + .Where(o => o.Link.Outcome == outcomeId && o.Grade() != null) .Select(static o => o.Link.Assignment) .ToArray(); From 75e44165f500a88e7b5a0771d44166056cd7484f Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 19:45:38 +0100 Subject: [PATCH 07/16] Reduce cognitive complexity to acceptable level --- Epsilon/Export/Exporters/CsvModuleExporter.cs | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/Epsilon/Export/Exporters/CsvModuleExporter.cs b/Epsilon/Export/Exporters/CsvModuleExporter.cs index 7f955d47..a36a9ddd 100644 --- a/Epsilon/Export/Exporters/CsvModuleExporter.cs +++ b/Epsilon/Export/Exporters/CsvModuleExporter.cs @@ -79,31 +79,27 @@ private static void WriteHeader(TextWriter writer, DataTable dt) writer.Write(writer.NewLine); } - // TODO: Fix code smell, cognitive complexity in if statement nesting private static void WriteRows(TextWriter writer, DataTable dt) { foreach (DataRow dr in dt.Rows) { - for (var i = 0; i < dt.Columns.Count; i++) + foreach (DataColumn dtColumn in dt.Columns) { - if (!Convert.IsDBNull(dr[i])) + var value = dr[dtColumn.Ordinal].ToString(); + if (value != null) { - var value = dr[i].ToString(); - if (value != null) + if (value.Contains(';')) { - if (value.Contains(';')) - { - value = $"\"{value}\""; - writer.Write(value); - } - else - { - writer.Write(dr[i].ToString()); - } + value = $"\"{value}\""; + writer.Write(value); + } + else + { + writer.Write(value); } } - if (i < dt.Columns.Count - 1) + if (dtColumn.Ordinal < dt.Columns.Count - 1) { writer.Write(";"); } From d28e1a1bd825424ae62b008f0355a107d822ae71 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 19:49:17 +0100 Subject: [PATCH 08/16] Move logging call --- Epsilon.Canvas/CanvasModuleCollectionFetcher.cs | 2 -- Epsilon.Cli/Startup.cs | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs index d7c0c1d1..989c7372 100644 --- a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs +++ b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs @@ -24,8 +24,6 @@ IOutcomeHttpService outcomeService public async Task> GetAll(int courseId) { - _logger.LogInformation("Downloading results..."); - var response = await _outcomeService.GetResults(courseId, new[] { "outcomes", "alignments" }); var alignments = response.Links.Alignments diff --git a/Epsilon.Cli/Startup.cs b/Epsilon.Cli/Startup.cs index 6e2f0658..d86bed84 100644 --- a/Epsilon.Cli/Startup.cs +++ b/Epsilon.Cli/Startup.cs @@ -63,6 +63,7 @@ private async Task ExecuteAsync() } _logger.LogInformation("Targeting Canvas course: {CourseId}, at {Url}", _canvasSettings.CourseId, _canvasSettings.ApiUrl); + _logger.LogInformation("Downloading results, this may take a few seconds..."); var modules = await _collectionFetcher.GetAll(_canvasSettings.CourseId); var formats = _exportOptions.Formats.Split(","); From 772d47157db64405297ce4b1532eee9c3911e3fa Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 19:59:29 +0100 Subject: [PATCH 09/16] Reduce nesting --- .../Export/Exporters/WordModuleExporter.cs | 89 +++++++++---------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index 810fe03a..10ba2cd3 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -21,65 +21,64 @@ public WordModuleExporter(IOptions options) public void Export(IEnumerable modules, string format) { - using (DocX document = DocX.Create($"{_options.FormattedOutputName}.docx")) - { - document.AddFooters(); - var link = document.AddHyperlink("Epsilon", new Uri("https://github.com/Typiqally/epsilon")); + using var document = DocX.Create($"{_options.FormattedOutputName}.docx"); + + document.AddFooters(); + var link = document.AddHyperlink("Epsilon", new Uri("https://github.com/Typiqally/epsilon")); - document.Footers.Odd - .InsertParagraph("Created with ") - .AppendHyperlink(link).Color(Color.Blue).UnderlineStyle(UnderlineStyle.singleLine); + document.Footers.Odd + .InsertParagraph("Created with ") + .AppendHyperlink(link).Color(Color.Blue).UnderlineStyle(UnderlineStyle.singleLine); - foreach (var module in modules.Where(static m => m.Collection.OutcomeResults.Any())) - { - var links = module.Collection.Links; - var alignments = links.AlignmentsDictionary; - var outcomes = links.OutcomesDictionary; + foreach (var module in modules.Where(static m => m.Collection.OutcomeResults.Any())) + { + var links = module.Collection.Links; + var alignments = links.AlignmentsDictionary; + var outcomes = links.OutcomesDictionary; - var table = document.AddTable(1, 3); + var table = document.AddTable(1, 3); - table.Rows[0].Cells[0].Paragraphs[0].Append("KPI"); - table.Rows[0].Cells[1].Paragraphs[0].Append("Assignment(s)"); - table.Rows[0].Cells[2].Paragraphs[0].Append("Score"); + table.Rows[0].Cells[0].Paragraphs[0].Append("KPI"); + table.Rows[0].Cells[1].Paragraphs[0].Append("Assignment(s)"); + table.Rows[0].Cells[2].Paragraphs[0].Append("Score"); - foreach (var (outcomeId, outcome) in outcomes) - { - var assignmentIds = module.Collection.OutcomeResults - .Where(o => o.Link.Outcome == outcomeId && o.Grade() != null) - .Select(static o => o.Link.Assignment) - .ToArray(); - - if (assignmentIds.Any()) - { - var row = table.InsertRow(); - row.Cells[0].Paragraphs[0].Append(outcome.Title + " " + outcome.ShortDescription()); + foreach (var (outcomeId, outcome) in outcomes) + { + var assignmentIds = module.Collection.OutcomeResults + .Where(o => o.Link.Outcome == outcomeId && o.Grade() != null) + .Select(static o => o.Link.Assignment) + .ToArray(); - var cellValueBuilder = new StringBuilder(); + if (assignmentIds.Any()) + { + var row = table.InsertRow(); + row.Cells[0].Paragraphs[0].Append(outcome.Title + " " + outcome.ShortDescription()); - foreach (var (alignmentId, alignment) in alignments.Where(a => assignmentIds.Contains(a.Key))) - { - cellValueBuilder.AppendLine($"{alignment.Name} {alignment.Url}"); - } + var cellValueBuilder = new StringBuilder(); - row.Cells[1].Paragraphs[0].Append(cellValueBuilder.ToString()); + foreach (var (_, alignment) in alignments.Where(a => assignmentIds.Contains(a.Key))) + { + cellValueBuilder.AppendLine($"{alignment.Name} {alignment.Url}"); + } - var cellValueOutComeResultsBuilder = new StringBuilder(); - foreach (var outcomeResult in module.Collection.OutcomeResults.Where(result => - result.Link.Outcome == outcomeId)) - { - cellValueOutComeResultsBuilder.AppendLine(outcomeResult.Grade()); - } + row.Cells[1].Paragraphs[0].Append(cellValueBuilder.ToString()); - row.Cells[2].Paragraphs[0].Append(cellValueOutComeResultsBuilder.ToString()); + var cellValueOutComeResultsBuilder = new StringBuilder(); + foreach (var outcomeResult in module.Collection.OutcomeResults.Where(result => + result.Link.Outcome == outcomeId)) + { + cellValueOutComeResultsBuilder.AppendLine(outcomeResult.Grade()); } - } - var par = document.InsertParagraph(module.Name); - par.FontSize(24); - par.InsertTableAfterSelf(table).InsertPageBreakAfterSelf(); + row.Cells[2].Paragraphs[0].Append(cellValueOutComeResultsBuilder.ToString()); + } } - document.Save(); + var par = document.InsertParagraph(module.Name); + par.FontSize(24); + par.InsertTableAfterSelf(table).InsertPageBreakAfterSelf(); } + + document.Save(); } } \ No newline at end of file From e853338a38598d981d0456be5a4ef7fea95a24d9 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 20:02:18 +0100 Subject: [PATCH 10/16] Move project name and repository uri to constants --- Epsilon/Constants.cs | 7 +++++++ Epsilon/Export/Exporters/WordModuleExporter.cs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 Epsilon/Constants.cs diff --git a/Epsilon/Constants.cs b/Epsilon/Constants.cs new file mode 100644 index 00000000..9fdd106f --- /dev/null +++ b/Epsilon/Constants.cs @@ -0,0 +1,7 @@ +namespace Epsilon; + +public static class Constants +{ + public const string ProjectName = "Epsilon"; + public static readonly Uri RepositoryUri = new("https://github.com/Typiqally/epsilon"); +} \ No newline at end of file diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index 10ba2cd3..61311d12 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -24,7 +24,7 @@ public void Export(IEnumerable modules, string format) using var document = DocX.Create($"{_options.FormattedOutputName}.docx"); document.AddFooters(); - var link = document.AddHyperlink("Epsilon", new Uri("https://github.com/Typiqally/epsilon")); + var link = document.AddHyperlink(Constants.ProjectName, Constants.RepositoryUri); document.Footers.Odd .InsertParagraph("Created with ") From 0a3787c5e91fd12e615166da6227195c662d2f8c Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 20:23:32 +0100 Subject: [PATCH 11/16] Fix nullability warnings --- .../Export/ICanvasModuleExporter.cs | 2 +- Epsilon.Abstractions/Export/IExporter.cs | 2 +- .../ICanvasModuleCollectionFetcher.cs | 2 +- Epsilon.Canvas.Abstractions/Model/Module.cs | 6 +--- .../Model/ModuleOutcomeResultCollection.cs | 3 ++ .../Model/OutcomeResultCollectionLink.cs | 10 +++--- .../CanvasModuleCollectionFetcher.cs | 32 ++++++++----------- Epsilon.Canvas/Service/OutcomeHttpService.cs | 8 +++-- .../Service/PaginatorHttpService.cs | 5 ++- Epsilon.Cli/Startup.cs | 7 ++-- Epsilon/Epsilon.csproj | 1 + .../Export/Exporters/ConsoleModuleExporter.cs | 27 ++++++++-------- Epsilon/Export/Exporters/CsvModuleExporter.cs | 23 +++++++------ .../Export/Exporters/ExcelModuleExporter.cs | 20 +++++++----- .../Export/Exporters/WordModuleExporter.cs | 18 +++++++---- 15 files changed, 89 insertions(+), 77 deletions(-) create mode 100644 Epsilon.Canvas.Abstractions/Model/ModuleOutcomeResultCollection.cs diff --git a/Epsilon.Abstractions/Export/ICanvasModuleExporter.cs b/Epsilon.Abstractions/Export/ICanvasModuleExporter.cs index c05b3d01..e717b09b 100644 --- a/Epsilon.Abstractions/Export/ICanvasModuleExporter.cs +++ b/Epsilon.Abstractions/Export/ICanvasModuleExporter.cs @@ -2,7 +2,7 @@ namespace Epsilon.Abstractions.Export; -public interface ICanvasModuleExporter : IExporter> +public interface ICanvasModuleExporter : IExporter> { } \ No newline at end of file diff --git a/Epsilon.Abstractions/Export/IExporter.cs b/Epsilon.Abstractions/Export/IExporter.cs index fd463eff..68d494f8 100644 --- a/Epsilon.Abstractions/Export/IExporter.cs +++ b/Epsilon.Abstractions/Export/IExporter.cs @@ -4,5 +4,5 @@ public interface IExporter { public IEnumerable Formats { get; } - void Export(T data, string format); + Task Export(T data, string format); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/ICanvasModuleCollectionFetcher.cs b/Epsilon.Canvas.Abstractions/ICanvasModuleCollectionFetcher.cs index c4d4469c..6b8b97f5 100644 --- a/Epsilon.Canvas.Abstractions/ICanvasModuleCollectionFetcher.cs +++ b/Epsilon.Canvas.Abstractions/ICanvasModuleCollectionFetcher.cs @@ -4,5 +4,5 @@ namespace Epsilon.Canvas.Abstractions; public interface ICanvasModuleCollectionFetcher { - public Task> GetAll(int courseId); + public IAsyncEnumerable GetAll(int courseId); } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/Module.cs b/Epsilon.Canvas.Abstractions/Model/Module.cs index 954a8771..901962b5 100644 --- a/Epsilon.Canvas.Abstractions/Model/Module.cs +++ b/Epsilon.Canvas.Abstractions/Model/Module.cs @@ -7,8 +7,4 @@ public record Module( [property: JsonPropertyName("name")] string Name, [property: JsonPropertyName("items_count")] int Count, [property: JsonPropertyName("items")] IEnumerable? Items -) -{ - [JsonIgnore] - public OutcomeResultCollection Collection { get; set; } -} \ No newline at end of file +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/ModuleOutcomeResultCollection.cs b/Epsilon.Canvas.Abstractions/Model/ModuleOutcomeResultCollection.cs new file mode 100644 index 00000000..0ed006f7 --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Model/ModuleOutcomeResultCollection.cs @@ -0,0 +1,3 @@ +namespace Epsilon.Canvas.Abstractions.Model; + +public record ModuleOutcomeResultCollection(Module Module, OutcomeResultCollection Collection); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs index 10bed7a7..9c5e1a6e 100644 --- a/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs @@ -3,15 +3,13 @@ namespace Epsilon.Canvas.Abstractions.Model; public record OutcomeResultCollectionLink( - [property: JsonPropertyName("outcomes")] IEnumerable? Outcomes, - [property: JsonPropertyName("alignments")] IEnumerable? Alignments + [property: JsonPropertyName("outcomes")] IEnumerable Outcomes, + [property: JsonPropertyName("alignments")] IEnumerable Alignments ) { - public IDictionary OutcomesDictionary => Outcomes - .DistinctBy(static o => o.Id) + public IDictionary OutcomesDictionary => Outcomes.DistinctBy(static o => o.Id) .ToDictionary(static o => o.Id.ToString(), static o => o); - public IDictionary AlignmentsDictionary => Alignments - .DistinctBy(static a => a.Id) + public IDictionary AlignmentsDictionary => Alignments.DistinctBy(static a => a.Id) .ToDictionary(static a => a.Id, static a => a); } \ No newline at end of file diff --git a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs index 989c7372..82aa0491 100644 --- a/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs +++ b/Epsilon.Canvas/CanvasModuleCollectionFetcher.cs @@ -1,4 +1,5 @@ -using Epsilon.Canvas.Abstractions; +using System.Diagnostics; +using Epsilon.Canvas.Abstractions; using Epsilon.Canvas.Abstractions.Model; using Epsilon.Canvas.Abstractions.Service; using Microsoft.Extensions.Logging; @@ -7,7 +8,6 @@ namespace Epsilon.Canvas; public class CanvasModuleCollectionFetcher : ICanvasModuleCollectionFetcher { - private readonly ILogger _logger; private readonly IModuleHttpService _moduleService; private readonly IOutcomeHttpService _outcomeService; @@ -17,34 +17,30 @@ public CanvasModuleCollectionFetcher( IOutcomeHttpService outcomeService ) { - _logger = logger; _moduleService = moduleService; _outcomeService = outcomeService; } - public async Task> GetAll(int courseId) + public async IAsyncEnumerable GetAll(int courseId) { var response = await _outcomeService.GetResults(courseId, new[] { "outcomes", "alignments" }); - - var alignments = response.Links.Alignments - .DistinctBy(static a => a.Id) - .ToDictionary(static a => a.Id, static a => a); - - var outcomes = response.Links.Outcomes - .DistinctBy(static o => o.Id) - .ToDictionary(static o => o.Id.ToString(), static o => o); - var modules = await _moduleService.GetAll(courseId, new[] { "items" }); - foreach (var module in modules) + + Debug.Assert(response != null, nameof(response) + " != null"); + Debug.Assert(modules != null, nameof(modules) + " != null"); + + foreach (var module in modules.ToArray()) { + Debug.Assert(module.Items != null, "module.Items != null"); + var ids = module.Items.Select(static i => $"assignment_{i.ContentId}"); - module.Collection = new OutcomeResultCollection( + Debug.Assert(response.Links?.Alignments != null, "response.Links?.Alignments != null"); + + yield return new ModuleOutcomeResultCollection(module, new OutcomeResultCollection( response.OutcomeResults.Where(r => ids.Contains(r.Link.Alignment)), response.Links with { Alignments = response.Links.Alignments.Where(a => ids.Contains(a.Id)) } - ); + )); } - - return modules; } } \ No newline at end of file diff --git a/Epsilon.Canvas/Service/OutcomeHttpService.cs b/Epsilon.Canvas/Service/OutcomeHttpService.cs index 9ba80c4a..c3b6ad3f 100644 --- a/Epsilon.Canvas/Service/OutcomeHttpService.cs +++ b/Epsilon.Canvas/Service/OutcomeHttpService.cs @@ -29,11 +29,13 @@ public OutcomeHttpService(HttpClient client, IPaginatorHttpService paginator) : var query = $"?include[]={string.Join("&include[]=", include)}"; var responses = await _paginator.GetAllPages(HttpMethod.Get, url + query); + var responsesArray = responses.ToArray(); + return new OutcomeResultCollection( - responses.SelectMany(static r => r.OutcomeResults), + responsesArray.SelectMany(static r => r.OutcomeResults), new OutcomeResultCollectionLink( - responses.SelectMany(static r => r.Links.Outcomes), - responses.SelectMany(static r => r.Links.Alignments) + responsesArray.SelectMany(static r => r.Links?.Outcomes ?? Array.Empty()), + responsesArray.SelectMany(static r => r.Links?.Alignments ?? Array.Empty()) ) ); } diff --git a/Epsilon.Canvas/Service/PaginatorHttpService.cs b/Epsilon.Canvas/Service/PaginatorHttpService.cs index 8176c951..a522bbae 100644 --- a/Epsilon.Canvas/Service/PaginatorHttpService.cs +++ b/Epsilon.Canvas/Service/PaginatorHttpService.cs @@ -37,7 +37,10 @@ public async Task> GetAllPages(HttpMethod method, var (response, value) = await Client.SendAsync(request); var links = _headerConverter.ConvertFrom(response); - pages.Add(value); + if (value != null) + { + pages.Add(value); + } if (links.NextLink == null) { diff --git a/Epsilon.Cli/Startup.cs b/Epsilon.Cli/Startup.cs index d86bed84..6d9c45b8 100644 --- a/Epsilon.Cli/Startup.cs +++ b/Epsilon.Cli/Startup.cs @@ -64,7 +64,7 @@ private async Task ExecuteAsync() _logger.LogInformation("Targeting Canvas course: {CourseId}, at {Url}", _canvasSettings.CourseId, _canvasSettings.ApiUrl); _logger.LogInformation("Downloading results, this may take a few seconds..."); - var modules = await _collectionFetcher.GetAll(_canvasSettings.CourseId); + var items = _collectionFetcher.GetAll(_canvasSettings.CourseId); var formats = _exportOptions.Formats.Split(","); var exporters = _exporterCollection.DetermineExporters(formats).ToArray(); @@ -74,7 +74,8 @@ private async Task ExecuteAsync() foreach (var (format, exporter) in exporters) { _logger.LogInformation("Exporting to {Format} using {Exporter}...", format, exporter.GetType().Name); - exporter.Export(modules, format); + // ReSharper disable once PossibleMultipleEnumeration + await exporter.Export(items, format); } } catch (Exception ex) @@ -92,7 +93,7 @@ private static IEnumerable Validate(object model) var results = new List(); var context = new ValidationContext(model); - var isValid = Validator.TryValidateObject(model, context, results, true); + Validator.TryValidateObject(model, context, results, true); return results; } diff --git a/Epsilon/Epsilon.csproj b/Epsilon/Epsilon.csproj index e75a9c1e..dab09f5d 100644 --- a/Epsilon/Epsilon.csproj +++ b/Epsilon/Epsilon.csproj @@ -17,6 +17,7 @@ + diff --git a/Epsilon/Export/Exporters/ConsoleModuleExporter.cs b/Epsilon/Export/Exporters/ConsoleModuleExporter.cs index 77d2018b..d5424749 100644 --- a/Epsilon/Export/Exporters/ConsoleModuleExporter.cs +++ b/Epsilon/Export/Exporters/ConsoleModuleExporter.cs @@ -1,4 +1,5 @@ -using Epsilon.Abstractions.Export; +using System.Diagnostics; +using Epsilon.Abstractions.Export; using Epsilon.Canvas.Abstractions.Model; using Microsoft.Extensions.Logging; @@ -15,28 +16,26 @@ public ConsoleModuleExporter(ILogger logger) public IEnumerable Formats { get; } = new[] { "console", "logs" }; - public void Export(IEnumerable data, string format) + public async Task Export(IAsyncEnumerable data, string format) { - LogModule(data); - } - - private void LogModule(IEnumerable modules) - { - foreach (var module in modules) + await foreach (var item in data) { - _logger.LogInformation("Module: {Name}", module.Name); + _logger.LogInformation("Module: {Name}", item.Module.Name); - var links = module.Collection.Links; - var alignments = links.AlignmentsDictionary; - var outcomes = links.OutcomesDictionary; + var links = item.Collection.Links; + var alignments = links?.AlignmentsDictionary; + var outcomes = links?.OutcomesDictionary; + Debug.Assert(alignments != null, nameof(alignments) + " != null"); + Debug.Assert(outcomes != null, nameof(outcomes) + " != null"); + foreach (var alignment in alignments.Values) { _logger.LogInformation("Alignment: {Alignment}", alignment.Name); - foreach (var result in module.Collection.OutcomeResults.Where(o => o.Link.Alignment == alignment.Id)) + foreach (var result in item.Collection.OutcomeResults.Where(o => o.Link.Alignment == alignment.Id && o.Link.Outcome != null)) { - _logger.LogInformation("- {OutcomeName} {Score}", outcomes[result.Link.Outcome].Title, result.Grade()); + _logger.LogInformation("- {OutcomeName} {Score}", outcomes[result.Link.Outcome!].Title, result.Grade()); } } } diff --git a/Epsilon/Export/Exporters/CsvModuleExporter.cs b/Epsilon/Export/Exporters/CsvModuleExporter.cs index a36a9ddd..847d74ef 100644 --- a/Epsilon/Export/Exporters/CsvModuleExporter.cs +++ b/Epsilon/Export/Exporters/CsvModuleExporter.cs @@ -1,4 +1,5 @@ using System.Data; +using System.Diagnostics; using Epsilon.Abstractions.Export; using Epsilon.Canvas.Abstractions.Model; using Microsoft.Extensions.Options; @@ -16,9 +17,9 @@ public CsvModuleExporter(IOptions options) public IEnumerable Formats { get; } = new[] { "csv" }; - public void Export(IEnumerable data, string format) + public async Task Export(IAsyncEnumerable data, string format) { - var dt = CreateDataTable(data); + var dt = await CreateDataTable(data); var stream = new StreamWriter($"{_options.FormattedOutputName}.{format}", false); WriteHeader(stream, dt); @@ -27,7 +28,7 @@ public void Export(IEnumerable data, string format) stream.Close(); } - private static DataTable CreateDataTable(IEnumerable modules) + private static async Task CreateDataTable(IAsyncEnumerable items) { var dt = new DataTable(); @@ -38,14 +39,18 @@ private static DataTable CreateDataTable(IEnumerable modules) dt.Columns.Add("Score", typeof(string)); dt.Columns.Add("Module", typeof(string)); - foreach (var module in modules) + await foreach (var item in items) { - var links = module.Collection.Links; + var links = item.Collection.Links; - foreach (var result in module.Collection.OutcomeResults) + Debug.Assert(links?.OutcomesDictionary != null, "links?.OutcomesDictionary != null"); + Debug.Assert(links.AlignmentsDictionary != null, "links.AlignmentsDictionary != null"); + + foreach (var result in item.Collection.OutcomeResults) { - var outcome = links.OutcomesDictionary[result.Link.Outcome]; - var alignment = links.AlignmentsDictionary[result.Link.Alignment]; + Debug.Assert(result.Link != null, "result.Link != null"); + var outcome = links.OutcomesDictionary[result.Link.Outcome!]; + var alignment = links.AlignmentsDictionary[result.Link.Alignment!]; var grade = result.Grade(); if (grade != null) @@ -56,7 +61,7 @@ private static DataTable CreateDataTable(IEnumerable modules) alignment.Name, outcome.Title, result.Grade(), - module.Name + item.Module.Name ); } } diff --git a/Epsilon/Export/Exporters/ExcelModuleExporter.cs b/Epsilon/Export/Exporters/ExcelModuleExporter.cs index eeabf6b6..322fc2aa 100644 --- a/Epsilon/Export/Exporters/ExcelModuleExporter.cs +++ b/Epsilon/Export/Exporters/ExcelModuleExporter.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.Diagnostics; +using System.Text; using Epsilon.Abstractions.Export; using Epsilon.Canvas.Abstractions.Model; using ExcelLibrary.SpreadSheet; @@ -17,13 +18,13 @@ public ExcelModuleExporter(IOptions options) public IEnumerable Formats { get; } = new[] { "xls", "xlsx", "excel" }; - public void Export(IEnumerable modules, string format) + public async Task Export(IAsyncEnumerable data, string format) { var workbook = new Workbook(); - foreach (var module in modules.Where(static m => m.Collection.OutcomeResults.Any())) + await foreach (var item in data.Where(static m => m.Collection.OutcomeResults.Any())) { - var worksheet = new Worksheet(module.Name); + var worksheet = new Worksheet(item.Module.Name); //Because reasons @source https://stackoverflow.com/a/8127642 for (var i = 0; i < 100; i++) @@ -31,7 +32,10 @@ public void Export(IEnumerable modules, string format) worksheet.Cells[i, 0] = new Cell(""); } - var links = module.Collection.Links; + var links = item.Collection.Links; + + Debug.Assert(links != null, nameof(links) + " != null"); + var alignments = links.AlignmentsDictionary; var outcomes = links.OutcomesDictionary; @@ -45,7 +49,7 @@ public void Export(IEnumerable modules, string format) var index = 1; foreach (var (outcomeId, outcome) in outcomes) { - var assignmentIds = module.Collection.OutcomeResults + var assignmentIds = item.Collection.OutcomeResults .Where(o => o.Link.Outcome == outcomeId && o.Grade() != null) .Select(static o => o.Link.Assignment) .ToArray(); @@ -56,7 +60,7 @@ public void Export(IEnumerable modules, string format) var cellValueBuilder = new StringBuilder(); - foreach (var (alignmentId, alignment) in alignments.Where(a => assignmentIds.Contains(a.Key))) + foreach (var (_, alignment) in alignments.Where(a => assignmentIds.Contains(a.Key))) { cellValueBuilder.AppendLine($"{alignment.Name} {alignment.Url}"); } @@ -64,7 +68,7 @@ public void Export(IEnumerable modules, string format) worksheet.Cells[index, 1] = new Cell(cellValueBuilder.ToString()); var cellValueOutComeResultsBuilder = new StringBuilder(); - foreach (var outcomeResult in module.Collection.OutcomeResults.Where(result => + foreach (var outcomeResult in item.Collection.OutcomeResults.Where(result => result.Link.Outcome == outcomeId)) { cellValueOutComeResultsBuilder.AppendLine(outcomeResult.Grade()); diff --git a/Epsilon/Export/Exporters/WordModuleExporter.cs b/Epsilon/Export/Exporters/WordModuleExporter.cs index 61311d12..6a301a5b 100644 --- a/Epsilon/Export/Exporters/WordModuleExporter.cs +++ b/Epsilon/Export/Exporters/WordModuleExporter.cs @@ -1,4 +1,5 @@ -using System.Drawing; +using System.Diagnostics; +using System.Drawing; using System.Text; using Epsilon.Abstractions.Export; using Epsilon.Canvas.Abstractions.Model; @@ -19,7 +20,7 @@ public WordModuleExporter(IOptions options) public IEnumerable Formats { get; } = new[] { "word" }; - public void Export(IEnumerable modules, string format) + public async Task Export(IAsyncEnumerable data, string format) { using var document = DocX.Create($"{_options.FormattedOutputName}.docx"); @@ -30,9 +31,12 @@ public void Export(IEnumerable modules, string format) .InsertParagraph("Created with ") .AppendHyperlink(link).Color(Color.Blue).UnderlineStyle(UnderlineStyle.singleLine); - foreach (var module in modules.Where(static m => m.Collection.OutcomeResults.Any())) + await foreach (var item in data.Where(static m => m.Collection.OutcomeResults.Any())) { - var links = module.Collection.Links; + var links = item.Collection.Links; + + Debug.Assert(links != null, nameof(links) + " != null"); + var alignments = links.AlignmentsDictionary; var outcomes = links.OutcomesDictionary; @@ -44,7 +48,7 @@ public void Export(IEnumerable modules, string format) foreach (var (outcomeId, outcome) in outcomes) { - var assignmentIds = module.Collection.OutcomeResults + var assignmentIds = item.Collection.OutcomeResults .Where(o => o.Link.Outcome == outcomeId && o.Grade() != null) .Select(static o => o.Link.Assignment) .ToArray(); @@ -64,7 +68,7 @@ public void Export(IEnumerable modules, string format) row.Cells[1].Paragraphs[0].Append(cellValueBuilder.ToString()); var cellValueOutComeResultsBuilder = new StringBuilder(); - foreach (var outcomeResult in module.Collection.OutcomeResults.Where(result => + foreach (var outcomeResult in item.Collection.OutcomeResults.Where(result => result.Link.Outcome == outcomeId)) { cellValueOutComeResultsBuilder.AppendLine(outcomeResult.Grade()); @@ -74,7 +78,7 @@ public void Export(IEnumerable modules, string format) } } - var par = document.InsertParagraph(module.Name); + var par = document.InsertParagraph(item.Module.Name); par.FontSize(24); par.InsertTableAfterSelf(table).InsertPageBreakAfterSelf(); } From f5cfd0a22a1a7bbd09dc3754503e90852f101648 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 20:23:39 +0100 Subject: [PATCH 12/16] Remove unused class --- Epsilon.Canvas.Abstractions/Model/User.cs | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 Epsilon.Canvas.Abstractions/Model/User.cs diff --git a/Epsilon.Canvas.Abstractions/Model/User.cs b/Epsilon.Canvas.Abstractions/Model/User.cs deleted file mode 100644 index b283ffd2..00000000 --- a/Epsilon.Canvas.Abstractions/Model/User.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Text.Json.Serialization; - -namespace Epsilon.Canvas.Abstractions.Model; - -public class User -{ - [JsonPropertyName("id")] - public int Id { get; set; } - - [JsonPropertyName("name")] - public string Name { get; set; } -} \ No newline at end of file From 0cb2aaa738fac27bc7f82e1ae4487a79c9bbdd11 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 20:26:17 +0100 Subject: [PATCH 13/16] Use ?: operator and move constants to top level of class --- Epsilon.Canvas/Service/PaginatorHttpService.cs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/Epsilon.Canvas/Service/PaginatorHttpService.cs b/Epsilon.Canvas/Service/PaginatorHttpService.cs index a522bbae..5009c20f 100644 --- a/Epsilon.Canvas/Service/PaginatorHttpService.cs +++ b/Epsilon.Canvas/Service/PaginatorHttpService.cs @@ -8,6 +8,7 @@ namespace Epsilon.Canvas.Service; public class PaginatorHttpService : HttpService, IPaginatorHttpService { + private const int Limit = 100; private readonly ILinkHeaderConverter _headerConverter; public PaginatorHttpService(HttpClient client, ILinkHeaderConverter headerConverter) : base(client) @@ -19,21 +20,13 @@ public async Task> GetAllPages(HttpMethod method, { var pages = new List(); var page = "1"; - const int limit = 100; - if (!uri.Contains('?')) - { - uri += "?"; - } - else - { - uri += "&"; - } + uri += !uri.Contains('?') ? "?" : "&"; do { - var offset = pages.Count * limit; - var request = new HttpRequestMessage(method, $"{uri}per_page={limit}&offset={offset}&page={page}"); + var offset = pages.Count * Limit; + var request = new HttpRequestMessage(method, $"{uri}per_page={Limit}&offset={offset}&page={page}"); var (response, value) = await Client.SendAsync(request); var links = _headerConverter.ConvertFrom(response); @@ -49,7 +42,7 @@ public async Task> GetAllPages(HttpMethod method, var query = HttpUtility.ParseQueryString(new Uri(links.NextLink).Query); page = query["page"]; - } while (pages.Count * limit % limit == 0); + } while (pages.Count * Limit % Limit == 0); return pages; } From 57b79220699e47ace60cdf8cec4610b809c0b156 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 20:29:23 +0100 Subject: [PATCH 14/16] Reduce loop complexity --- Epsilon/Export/ModuleExporterCollection.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Epsilon/Export/ModuleExporterCollection.cs b/Epsilon/Export/ModuleExporterCollection.cs index bd065893..d494ba4c 100644 --- a/Epsilon/Export/ModuleExporterCollection.cs +++ b/Epsilon/Export/ModuleExporterCollection.cs @@ -19,17 +19,14 @@ public IEnumerable Formats() public IDictionary DetermineExporters(IEnumerable formats) { - var formatsArray = formats as string[] ?? formats.ToArray(); // To prevent multiple enumeration + var formatsArray = formats.ToArray(); // To prevent multiple enumeration var foundExporters = new Dictionary(); foreach (var exporter in _exporters) { - foreach (var format in formatsArray) + foreach (var format in formatsArray.Where(f => exporter.Formats.Contains(f.ToLower()))) { - if (exporter.Formats.Contains(format.ToLower())) - { - foundExporters.Add(format, exporter); - } + foundExporters.Add(format, exporter); } } From a33f7fe31f072a8fb22bdc9bb434233df3de20e5 Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 20:30:47 +0100 Subject: [PATCH 15/16] Use project name constant in output name export option --- Epsilon/Export/ExportOptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Epsilon/Export/ExportOptions.cs b/Epsilon/Export/ExportOptions.cs index be4ed1cb..c8a329fb 100644 --- a/Epsilon/Export/ExportOptions.cs +++ b/Epsilon/Export/ExportOptions.cs @@ -2,7 +2,7 @@ public class ExportOptions { - public string OutputName { get; set; } = "Epsilon-Export-{DateTime}"; + public string OutputName { get; set; } = $"{Constants.ProjectName}-Export-{{DateTime}}"; public string Formats { get; set; } = "console"; From fe16b4fbcce73cb01b3166a5e6156fa3af6699ad Mon Sep 17 00:00:00 2001 From: Jelle Maas Date: Mon, 7 Nov 2022 20:46:06 +0100 Subject: [PATCH 16/16] Update README.md application demo gif --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb78a9c8..87f792aa 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ These students usually have a personal course within Canvas (from Instructure), During each semester, it is requested to take note of all KPI's which have been proven. To aid in these efforts, this application will gather all your mastered/proven [KPI's](https://hbo-i.nl/domeinbeschrijving/) and export your KPI's to a file format (e.g., JSON, Exel, CSV). -![Application demo](https://user-images.githubusercontent.com/12190745/176268592-e863e4c3-47b4-4af5-aeca-298d53a37c33.gif) +![Application demo](https://user-images.githubusercontent.com/12190745/200400486-a7c6a166-cb42-4da4-a6be-855bedf1bfc6.gif) ## Usage Read how to use the application in our Wiki located [here](https://github.com/Typiqally/epsilon/wiki/How-to-use).