From 93e456b4b02080150a0fa3d6afa279adf2fecde9 Mon Sep 17 00:00:00 2001 From: Sven Date: Fri, 16 Jun 2023 09:02:01 +0200 Subject: [PATCH 01/20] Remove unused imports --- Epsilon.Abstractions/Component/CompetenceProfile.cs | 1 - Epsilon.Abstractions/Service/IFilterService.cs | 1 - Epsilon/Service/FilterService.cs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Epsilon.Abstractions/Component/CompetenceProfile.cs b/Epsilon.Abstractions/Component/CompetenceProfile.cs index a46d8379..696d5676 100644 --- a/Epsilon.Abstractions/Component/CompetenceProfile.cs +++ b/Epsilon.Abstractions/Component/CompetenceProfile.cs @@ -1,7 +1,6 @@ using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; using Epsilon.Abstractions.Model; -using Epsilon.Canvas.Abstractions.Model; namespace Epsilon.Abstractions.Component; diff --git a/Epsilon.Abstractions/Service/IFilterService.cs b/Epsilon.Abstractions/Service/IFilterService.cs index 5f11bac0..604629ed 100644 --- a/Epsilon.Abstractions/Service/IFilterService.cs +++ b/Epsilon.Abstractions/Service/IFilterService.cs @@ -1,4 +1,3 @@ -using Epsilon.Abstractions.Component; using Epsilon.Canvas.Abstractions.Model; namespace Epsilon.Abstractions.Service; diff --git a/Epsilon/Service/FilterService.cs b/Epsilon/Service/FilterService.cs index 2e6bbc8e..1afb4fb3 100644 --- a/Epsilon/Service/FilterService.cs +++ b/Epsilon/Service/FilterService.cs @@ -46,7 +46,7 @@ public async Task> GetParticipatedTerms() return Enumerable.Empty(); } - var submissions = response.Data.Courses!.SelectMany(c => c.SubmissionsConnection!.Nodes); + var submissions = response.Data.Courses!.SelectMany(static c => c.SubmissionsConnection!.Nodes); var participatedTerms = allTerms! .Where(static term => term is { StartAt: not null, EndAt: not null, }) From 1fd27fc66610930221c0e2808a5ab451f14d07c8 Mon Sep 17 00:00:00 2001 From: Sven Date: Fri, 16 Jun 2023 15:33:09 +0200 Subject: [PATCH 02/20] Add KPI table data model & component fetcher --- Epsilon.Abstractions/Component/KpiTable.cs | 25 +++ .../Component/KpiTableEntry.cs | 6 + .../Component/KpiTableEntryAssignment.cs | 7 + .../Model/GraphQl/Assignment.cs | 1 + Epsilon.Host.WebApi/Program.cs | 4 +- Epsilon/Component/KpiTableComponentFetcher.cs | 150 ++++++++++++++++++ 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 Epsilon.Abstractions/Component/KpiTable.cs create mode 100644 Epsilon.Abstractions/Component/KpiTableEntry.cs create mode 100644 Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs create mode 100644 Epsilon/Component/KpiTableComponentFetcher.cs diff --git a/Epsilon.Abstractions/Component/KpiTable.cs b/Epsilon.Abstractions/Component/KpiTable.cs new file mode 100644 index 00000000..072ef8a8 --- /dev/null +++ b/Epsilon.Abstractions/Component/KpiTable.cs @@ -0,0 +1,25 @@ +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; + +namespace Epsilon.Abstractions.Component; + +public record KpiTable( + IEnumerable Entries +) : IWordCompetenceComponent +{ + public void AddToWordDocument(MainDocumentPart mainDocumentPart) + { + // TODO: This is simply an example to show the capability of the component architecture + var body = new Body(); + + body.AppendChild( + new Paragraph( + new Run( + new Text("Kpi table") + ) + ) + ); + + mainDocumentPart.Document.AppendChild(body); + } +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/KpiTableEntry.cs b/Epsilon.Abstractions/Component/KpiTableEntry.cs new file mode 100644 index 00000000..8024ea6c --- /dev/null +++ b/Epsilon.Abstractions/Component/KpiTableEntry.cs @@ -0,0 +1,6 @@ +namespace Epsilon.Abstractions.Component; + +public record KpiTableEntry { + public string Kpi { get; set; } + public IEnumerable Assignments { get; set; } +} diff --git a/Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs b/Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs new file mode 100644 index 00000000..f8f68272 --- /dev/null +++ b/Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs @@ -0,0 +1,7 @@ +namespace Epsilon.Abstractions.Component; + +public record KpiTableEntryAssignment( + string Name, + double Grade, + Uri Link +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs index ebc20383..50fed186 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs @@ -4,6 +4,7 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl; public record Assignment( [property: JsonPropertyName("name")] string? Name, + [property: JsonPropertyName("htmlUrl")] Uri? HtmlUrl, [property: JsonPropertyName("modules")] IEnumerable? Modules , [property: JsonPropertyName("rubric")] Rubric? Rubric ); \ No newline at end of file diff --git a/Epsilon.Host.WebApi/Program.cs b/Epsilon.Host.WebApi/Program.cs index 2cf9bb4b..d6fd6521 100644 --- a/Epsilon.Host.WebApi/Program.cs +++ b/Epsilon.Host.WebApi/Program.cs @@ -19,6 +19,7 @@ }); builder.Services.AddControllers(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); @@ -33,12 +34,14 @@ { "persona_page", services.GetRequiredService>() }, { "competence_profile", services.GetRequiredService>() }, { "kpi_matrix", services.GetRequiredService>() }, + { "kpi_table", services.GetRequiredService>() }, } )); builder.Services.AddScoped, PersonaPageComponentFetcher>(); builder.Services.AddScoped, CompetenceProfileComponentFetcher>(); builder.Services.AddScoped, KpiMatrixComponentFetcher>(); +builder.Services.AddScoped, KpiTableComponentFetcher>(); var app = builder.Build(); @@ -50,7 +53,6 @@ app.UseSwagger(); - app.UseHttpsRedirection(); app.UseCors(); diff --git a/Epsilon/Component/KpiTableComponentFetcher.cs b/Epsilon/Component/KpiTableComponentFetcher.cs new file mode 100644 index 00000000..1e8d31c4 --- /dev/null +++ b/Epsilon/Component/KpiTableComponentFetcher.cs @@ -0,0 +1,150 @@ +using Epsilon.Abstractions.Component; +using Epsilon.Canvas.Abstractions.Model.GraphQl; +using Epsilon.Canvas.Abstractions.Service; +using Microsoft.Extensions.Configuration; + +namespace Epsilon.Component; + +public class KpiTableComponentFetcher : CompetenceComponentFetcher +{ + private const string GetUserKpis = @" + query GetUserKpis { + allCourses { + submissionsConnection(studentIds: $studentIds) { + nodes { + submissionHistoriesConnection { + nodes { + rubricAssessmentsConnection { + nodes { + assessmentRatings { + criterion { + outcome { + _id + title + } + masteryPoints + } + points + } + } + } + attempt + submittedAt + assignment { + name + htmlUrl + rubric { + criteria { + outcome { + title + _id + masteryPoints + } + } + } + } + } + } + postedAt + } + } + } + } + "; + + private readonly IConfiguration _configuration; + private readonly IGraphQlHttpService _graphQlService; + + public KpiTableComponentFetcher( + IGraphQlHttpService graphQlService, + IConfiguration configuration + ) + { + _graphQlService = graphQlService; + _configuration = configuration; + } + + public override async Task Fetch(DateTime startDate, DateTime endDate) + { + var studentId = _configuration["Canvas:StudentId"]; + var outcomesQuery = GetUserKpis.Replace("$studentIds", $"{studentId}", StringComparison.InvariantCultureIgnoreCase); + var outcomes = await _graphQlService.Query(outcomesQuery); + + var kpiTableEntries = new List(); + + foreach (var course in outcomes.Data!.Courses!) + { + foreach (var submissionsConnection in course.SubmissionsConnection!.Nodes) + { + var submission = submissionsConnection.SubmissionsHistories.Nodes + .Where(sub => sub.SubmittedAt > startDate && sub.SubmittedAt < endDate) + .MaxBy(static h => h.Attempt); + + if (submission is + { + Assignment.Rubric: not null, + RubricAssessments.Nodes: not null, + } + ) + { + var rubricCriteria = submission.Assignment.Rubric.Criteria?.ToArray(); + + if (rubricCriteria != null) + { + foreach (var outcome in rubricCriteria.Select(static criteria => criteria.Outcome)) + { + // Validate that outcome is a HboI KPI + if (outcome != null + && (FhictConstants.ProfessionalTasks.ContainsKey(outcome.Id) || FhictConstants.ProfessionalSkills.ContainsKey(outcome.Id)) + && rubricCriteria.Any()) + { + var assessmentRatings = submission.RubricAssessments.Nodes.FirstOrDefault()?.AssessmentRatings; + + if (assessmentRatings != null) + { + var gradeStatus = assessmentRatings + .Where(ar => ar?.Criterion?.Outcome?.Id == outcome.Id) + .Select(static ar => ar.Points) + .FirstOrDefault(); + + if (gradeStatus != null) + { + var kpiName = outcome.Title; + var assignmentName = submission.Assignment.Name; + var htmlUrl = submission.Assignment.HtmlUrl; + + // Check if the KPI entry already exists + var kpiTableEntryIndex = kpiTableEntries.FindIndex(kte => kte.Kpi == kpiName); + + if (kpiTableEntryIndex > -1) + { + // Add assignment to existing KPI entry + kpiTableEntries[kpiTableEntryIndex].Assignments = kpiTableEntries[kpiTableEntryIndex].Assignments.Append(new KpiTableEntryAssignment(assignmentName, gradeStatus.Value, htmlUrl)); + } + else + { + // Create a new KPI entry + var newKpiTableEntry = new KpiTableEntry { + Kpi = kpiName, + Assignments = new List + { + new KpiTableEntryAssignment(assignmentName, gradeStatus.Value, htmlUrl), + }, + }; + + kpiTableEntries.Add(newKpiTableEntry); + } + } + } + } + } + } + } + } + } + + kpiTableEntries = kpiTableEntries.OrderByDescending(static kte => kte.Assignments.Count()).ToList(); + + return new KpiTable(kpiTableEntries); + } +} \ No newline at end of file From 27e8512f59d1a1a9347c2fe50db569c5e12d28a4 Mon Sep 17 00:00:00 2001 From: Sven Date: Tue, 20 Jun 2023 19:58:44 +0200 Subject: [PATCH 03/20] Add legenda data and frontend component --- Epsilon.Abstractions/Component/KpiTable.cs | 139 ++++++++++++++++-- .../Component/KpiTableEntryAssignment.cs | 8 +- .../KpiTableEntryAssignmentGradeStatus.cs | 6 + .../Model/OutcomeGradeLevel.cs | 9 ++ .../src/components/KpiTable.vue | 87 +++++++++++ .../src/views/PerformanceDashboard.vue | 6 +- Epsilon/Component/KpiTableComponentFetcher.cs | 4 +- 7 files changed, 243 insertions(+), 16 deletions(-) create mode 100644 Epsilon.Abstractions/Component/KpiTableEntryAssignmentGradeStatus.cs create mode 100644 Epsilon.Canvas.Abstractions/Model/OutcomeGradeLevel.cs create mode 100644 Epsilon.Host.Frontend/src/components/KpiTable.vue diff --git a/Epsilon.Abstractions/Component/KpiTable.cs b/Epsilon.Abstractions/Component/KpiTable.cs index 072ef8a8..60391dfc 100644 --- a/Epsilon.Abstractions/Component/KpiTable.cs +++ b/Epsilon.Abstractions/Component/KpiTable.cs @@ -1,25 +1,144 @@ +using System.Globalization; +using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; +using Epsilon.Canvas.Abstractions.Model; namespace Epsilon.Abstractions.Component; public record KpiTable( - IEnumerable Entries + IEnumerable Entries, + IDictionary GradeStatus ) : IWordCompetenceComponent { + public static readonly IDictionary DefaultGradeStatus = new Dictionary + { + { + OutcomeGradeLevel.One, new KpiTableEntryAssignmentGradeStatus("One", "CBF5DD") + }, + { + OutcomeGradeLevel.Two, new KpiTableEntryAssignmentGradeStatus("Two", "64E3A1") + }, + { + OutcomeGradeLevel.Three, new KpiTableEntryAssignmentGradeStatus("Three", "27A567") + }, + { + OutcomeGradeLevel.Four, new KpiTableEntryAssignmentGradeStatus("Four", "198450") + }, + }; + public void AddToWordDocument(MainDocumentPart mainDocumentPart) { - // TODO: This is simply an example to show the capability of the component architecture var body = new Body(); - - body.AppendChild( - new Paragraph( - new Run( - new Text("Kpi table") - ) - ) - ); + + // Create a table, with columns for the outcomes and corresponding assignments and grades + var table = new Table(); + + // Columns header texts + var columnsHeaders = new List { "KPI", "Assignments", "Grades", }; + + // Create the table header row + var headerRow = new TableRow(); + + // Create the header cells + foreach (var columnHeader in columnsHeaders) + { + headerRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(new Run(new Text(columnHeader))))); + } + + // Add the header row to the table + table.AppendChild(headerRow); + + // Create the table body rows and cells in which the first cell is the outcome and the rest are the assignments and grades + foreach (var entry in Entries) + { + var tableRow = new TableRow(); + + tableRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(new Run(new Text(entry.Kpi))))); + + var assignmentParagraphs = entry.Assignments + .Select(static a => + new Paragraph(new Run(new Hyperlink(new Run(new Text(a.Name))) + { + Anchor = a.Link.AbsoluteUri, + }))); + + tableRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(assignmentParagraphs))); + tableRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(new Run(string.Join("\n", entry.Assignments.Select(static a => a.Grade.ToString(CultureInfo.InvariantCulture))))))); + + table.AppendChild(tableRow); + } + + body.AppendChild(GetLegend()); + body.Append(new Paragraph(new Run(new Text("")))); + body.AppendChild(table); mainDocumentPart.Document.AppendChild(body); } + + private OpenXmlElement GetLegend() + { + var table = new Table(); + + foreach (var status in GradeStatus) + { + var row = new TableRow(); + var cellName = CreateTableCellWithBorders("200"); + cellName.Append(new Paragraph(new Run(new Text(status.Value.Level)))); + + var cellValue = CreateTableCellWithBorders("200"); + cellValue.Append(new Paragraph(new Run(new Text("")))); + cellValue.FirstChild?.Append(new Shading + { + Fill = status.Value.Color, + }); + row.AppendChild(cellName); + row.AppendChild(cellValue); + table.AppendChild(row); + } + + return table; + } + + private static TableCell CreateTableCellWithBorders(string? width, params OpenXmlElement[] elements) + { + var cell = new TableCell(); + var cellProperties = new TableCellProperties(); + var borders = new TableCellBorders( + new LeftBorder + { + Val = BorderValues.Single, + }, + new RightBorder + { + Val = BorderValues.Single, + }, + new TopBorder + { + Val = BorderValues.Single, + }, + new BottomBorder + { + Val = BorderValues.Single, + }); + + foreach (var element in elements) + { + cell.Append(element); + } + + if (width != null) + { + cellProperties.Append(new TableCellWidth + { + Type = TableWidthUnitValues.Dxa, + Width = width, + }); + } + + cellProperties.Append(borders); + cell.PrependChild(cellProperties); + + return cell; + } } \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs b/Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs index f8f68272..c92a12f1 100644 --- a/Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs +++ b/Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs @@ -2,6 +2,10 @@ namespace Epsilon.Abstractions.Component; public record KpiTableEntryAssignment( string Name, - double Grade, + string Grade, + KpiTableEntryAssignmentGradeStatus GradeStatus, Uri Link -); \ No newline at end of file +) +{ + public void SetGrade(double grade) => Grade = grade.ToString("0.00"); +} \ No newline at end of file diff --git a/Epsilon.Abstractions/Component/KpiTableEntryAssignmentGradeStatus.cs b/Epsilon.Abstractions/Component/KpiTableEntryAssignmentGradeStatus.cs new file mode 100644 index 00000000..7d5a599a --- /dev/null +++ b/Epsilon.Abstractions/Component/KpiTableEntryAssignmentGradeStatus.cs @@ -0,0 +1,6 @@ +namespace Epsilon.Abstractions.Component; + +public record KpiTableEntryAssignmentGradeStatus( + string Level, + string Color +); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeGradeLevel.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeGradeLevel.cs new file mode 100644 index 00000000..9a7e2662 --- /dev/null +++ b/Epsilon.Canvas.Abstractions/Model/OutcomeGradeLevel.cs @@ -0,0 +1,9 @@ +namespace Epsilon.Canvas.Abstractions.Model; + +public enum OutcomeGradeLevel +{ + One, + Two, + Three, + Four, +} \ No newline at end of file diff --git a/Epsilon.Host.Frontend/src/components/KpiTable.vue b/Epsilon.Host.Frontend/src/components/KpiTable.vue new file mode 100644 index 00000000..b2b77439 --- /dev/null +++ b/Epsilon.Host.Frontend/src/components/KpiTable.vue @@ -0,0 +1,87 @@ + + + + + + + diff --git a/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue b/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue index 39c8dd33..889a11f0 100644 --- a/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue +++ b/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue @@ -1,5 +1,5 @@ From eb9ed935970f02e7150ab343831a0964a6952b14 Mon Sep 17 00:00:00 2001 From: Sven Date: Fri, 23 Jun 2023 13:47:39 +0200 Subject: [PATCH 07/20] Add hyperlink to assignmentName in KpiTable Word Export & Cleanup KpiTableComponentFetcher --- Epsilon.Abstractions/Component/KpiTable.cs | 104 +++++++++++------- Epsilon/Component/KpiTableComponentFetcher.cs | 77 ++++++------- 2 files changed, 106 insertions(+), 75 deletions(-) diff --git a/Epsilon.Abstractions/Component/KpiTable.cs b/Epsilon.Abstractions/Component/KpiTable.cs index 083a10e0..c1fb2766 100644 --- a/Epsilon.Abstractions/Component/KpiTable.cs +++ b/Epsilon.Abstractions/Component/KpiTable.cs @@ -1,4 +1,3 @@ -using System.Globalization; using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; @@ -8,7 +7,7 @@ namespace Epsilon.Abstractions.Component; public record KpiTable( IEnumerable Entries, - IDictionary GradeStatus + IDictionary GradeStatus ) : IWordCompetenceComponent { public static readonly IDictionary DefaultGradeStatus = new Dictionary @@ -53,52 +52,81 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) foreach (var entry in Entries) { var tableRow = new TableRow(); - + + // KPI column tableRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(new Run(new Text(entry.Kpi))))); - - var assignmentParagraphs = entry.Assignments - .Select(static a => - new Paragraph(new Run(new Hyperlink(new Run(new Text(a.Name))) - { - Anchor = a.Link.AbsoluteUri, - }))); - - tableRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(assignmentParagraphs))); - tableRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(new Run(string.Join("\n", entry.Assignments.Select(static a => a.Grade.ToString(CultureInfo.InvariantCulture))))))); - + + // Assignments column + var aParagraph = new Paragraph(); + var aRun = aParagraph.AppendChild(new Run()); + + foreach (var assignment in entry.Assignments) + { + var runProperties = new RunProperties(); + var underline = new Underline { Val = UnderlineValues.Single, }; + + runProperties.Append(underline); + + var rel = mainDocumentPart.AddHyperlinkRelationship(assignment.Link, true); + var relationshipId = rel.Id; + + aRun.AppendChild(new Hyperlink(new Run(runProperties, new Text(assignment.Name))) + { + History = OnOffValue.FromBoolean(true), Id = relationshipId, + }); + + aRun.AppendChild(new Break()); + } + + tableRow.AppendChild(CreateTableCellWithBorders("3000", aParagraph)); + + // Grades column + var grades = entry.Assignments.Select(static a => a.Grade); + var gParagraph = new Paragraph(); + var gRun = gParagraph.AppendChild(new Run()); + + foreach (var grade in grades) + { + gRun.AppendChild(new Text(grade)); + gRun.AppendChild(new Break()); + } + + tableRow.AppendChild(CreateTableCellWithBorders("3000", gParagraph)); + + // Add the row to the table table.AppendChild(tableRow); } - body.AppendChild(GetLegend()); + // body.AppendChild(GetLegend()); body.Append(new Paragraph(new Run(new Text("")))); body.AppendChild(table); mainDocumentPart.Document.AppendChild(body); } - private OpenXmlElement GetLegend() - { - var table = new Table(); - - foreach (var status in GradeStatus) - { - var row = new TableRow(); - var cellName = CreateTableCellWithBorders("200"); - cellName.Append(new Paragraph(new Run(new Text(status.Value.Level)))); - - var cellValue = CreateTableCellWithBorders("200"); - cellValue.Append(new Paragraph(new Run(new Text("")))); - cellValue.FirstChild?.Append(new Shading - { - Fill = status.Value.Color, - }); - row.AppendChild(cellName); - row.AppendChild(cellValue); - table.AppendChild(row); - } - - return table; - } + // private OpenXmlElement GetLegend() + // { + // var table = new Table(); + // + // foreach (var status in GradeStatus) + // { + // var row = new TableRow(); + // var cellName = CreateTableCellWithBorders("200"); + // cellName.Append(new Paragraph(new Run(new Text(status.Value.Level)))); + // + // var cellValue = CreateTableCellWithBorders("200"); + // cellValue.Append(new Paragraph(new Run(new Text("")))); + // cellValue.FirstChild?.Append(new Shading + // { + // Fill = status.Value.Color, + // }); + // row.AppendChild(cellName); + // row.AppendChild(cellValue); + // table.AppendChild(row); + // } + // + // return table; + // } private static TableCell CreateTableCellWithBorders(string? width, params OpenXmlElement[] elements) { diff --git a/Epsilon/Component/KpiTableComponentFetcher.cs b/Epsilon/Component/KpiTableComponentFetcher.cs index bf6f5680..59f62e07 100644 --- a/Epsilon/Component/KpiTableComponentFetcher.cs +++ b/Epsilon/Component/KpiTableComponentFetcher.cs @@ -95,7 +95,7 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) .Where(ar => ar?.Criterion?.Outcome?.Id == outcome.Id) .Select(static ar => ar.Points) .FirstOrDefault(); - + if (gradeStatus != null) { var kpiName = outcome.Title; @@ -103,43 +103,19 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) var htmlUrl = submission.Assignment.HtmlUrl; var assessmentRating = assessmentRatings.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome.Id); var outcomeGradeLevel = GetMasteryLevel(assessmentRating); - + var kpiTableEntryIndex = kpiTableEntries.FindIndex(kte => kte.Kpi == kpiName); - if (kpiTableEntryIndex > -1) + if (outcomeGradeLevel is not null) { - if (outcomeGradeLevel is not null) + if (kpiTableEntryIndex > -1) { - var existingEntry = kpiTableEntries[kpiTableEntryIndex]; - var updatedAssignments = existingEntry.Assignments.Append( - new KpiTableEntryAssignment( - assignmentName, - GetGradeStatus(gradeStatus.Value), - htmlUrl - )); - - var updatedEntry = existingEntry with - { - Assignments = updatedAssignments, - }; - - kpiTableEntries[kpiTableEntryIndex] = updatedEntry; + UpdateKpiTableEntry(kpiTableEntries, kpiTableEntryIndex, assignmentName, GetGradeStatus(gradeStatus.Value), htmlUrl); + } + else + { + AddKpiTableEntry(kpiTableEntries, kpiName, outcomeGradeLevel.Value, assignmentName, GetGradeStatus(gradeStatus.Value), htmlUrl); } - } - else if (outcomeGradeLevel is not null) - { - kpiTableEntries.Add(new KpiTableEntry( - kpiName, - KpiTable.DefaultGradeStatus[outcomeGradeLevel.Value + 1], - new List - { - new KpiTableEntryAssignment( - assignmentName, - GetGradeStatus(gradeStatus.Value), - htmlUrl - ), - } - )); } } } @@ -154,6 +130,33 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) return new KpiTable(kpiTableEntries, KpiTable.DefaultGradeStatus); } + private static void UpdateKpiTableEntry(IList kpiTableEntries, int index, string assignmentName, string gradeStatus, Uri htmlUrl) + { + var existingEntry = kpiTableEntries[index]; + var updatedAssignments = existingEntry.Assignments.Append( + new KpiTableEntryAssignment(assignmentName, gradeStatus, htmlUrl) + ); + + var updatedEntry = existingEntry with + { + Assignments = updatedAssignments, + }; + + kpiTableEntries[index] = updatedEntry; + } + + private static void AddKpiTableEntry(ICollection kpiTableEntries, string kpiName, OutcomeGradeLevel kpiLevel, string assignmentName, string gradeStatus, Uri htmlUrl) + { + kpiTableEntries.Add(new KpiTableEntry( + kpiName, + KpiTable.DefaultGradeStatus[kpiLevel], + new List + { + new KpiTableEntryAssignment(assignmentName, gradeStatus, htmlUrl), + } + )); + } + private static OutcomeGradeLevel? GetMasteryLevel(AssessmentRating? assessmentRating) { if (assessmentRating != null) @@ -162,10 +165,10 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) { return professionalTask.MasteryLevel switch { - 1 => OutcomeGradeLevel.One, - 2 => OutcomeGradeLevel.Two, - 3 => OutcomeGradeLevel.Three, - 4 => OutcomeGradeLevel.Four, + 0 => OutcomeGradeLevel.One, + 1 => OutcomeGradeLevel.Two, + 2 => OutcomeGradeLevel.Three, + 3 => OutcomeGradeLevel.Four, _ => null, }; } From fc40e1b6edf3bc184f6b345f63c74173e43e09c4 Mon Sep 17 00:00:00 2001 From: Sven Date: Fri, 23 Jun 2023 14:25:57 +0200 Subject: [PATCH 08/20] Add support for personal development kpi's to KPI Table --- Epsilon.Abstractions/Component/KpiTable.cs | 8 +- Epsilon/Component/KpiTableComponentFetcher.cs | 74 ++++++++++++------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/Epsilon.Abstractions/Component/KpiTable.cs b/Epsilon.Abstractions/Component/KpiTable.cs index c1fb2766..b7f37f4b 100644 --- a/Epsilon.Abstractions/Component/KpiTable.cs +++ b/Epsilon.Abstractions/Component/KpiTable.cs @@ -62,14 +62,12 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) foreach (var assignment in entry.Assignments) { - var runProperties = new RunProperties(); - var underline = new Underline { Val = UnderlineValues.Single, }; - - runProperties.Append(underline); - var rel = mainDocumentPart.AddHyperlinkRelationship(assignment.Link, true); var relationshipId = rel.Id; + var runProperties = new RunProperties( + new Underline { Val = UnderlineValues.Single, }); + aRun.AppendChild(new Hyperlink(new Run(runProperties, new Text(assignment.Name))) { History = OnOffValue.FromBoolean(true), Id = relationshipId, diff --git a/Epsilon/Component/KpiTableComponentFetcher.cs b/Epsilon/Component/KpiTableComponentFetcher.cs index 59f62e07..163001d2 100644 --- a/Epsilon/Component/KpiTableComponentFetcher.cs +++ b/Epsilon/Component/KpiTableComponentFetcher.cs @@ -81,40 +81,52 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) .Where(sub => sub.SubmittedAt > startDate && sub.SubmittedAt < endDate) .MaxBy(static h => h.Attempt); - if (submission?.Assignment.Rubric is { Criteria: { } rubricCriteria, }) + if (submission is + { + Assignment.Rubric: not null, + RubricAssessments.Nodes: not null, + }) { - foreach (var outcome in rubricCriteria.Select(static criteria => criteria.Outcome)) + var rubricCriteria = submission.Assignment.Rubric.Criteria?.ToArray(); + + if (rubricCriteria != null) { - if (outcome is not null && FhictConstants.ProfessionalTasks.ContainsKey(outcome.Id) && rubricCriteria.Any()) + foreach (var outcome in rubricCriteria.Select(static criteria => criteria.Outcome).Where(static c => c != null)) { - var assessmentRatings = submission.RubricAssessments.Nodes.FirstOrDefault()?.AssessmentRatings; - - if (assessmentRatings is not null) + //Validate that outcome is a HboI KPI + if (outcome != null + && (FhictConstants.ProfessionalTasks.ContainsKey(outcome.Id) || FhictConstants.ProfessionalSkills.ContainsKey(outcome.Id)) + && rubricCriteria.Any()) { - var gradeStatus = assessmentRatings - .Where(ar => ar?.Criterion?.Outcome?.Id == outcome.Id) - .Select(static ar => ar.Points) - .FirstOrDefault(); + var assessmentRatings = submission.RubricAssessments.Nodes.FirstOrDefault()?.AssessmentRatings; - if (gradeStatus != null) + if (assessmentRatings is not null) { - var kpiName = outcome.Title; - var assignmentName = submission.Assignment.Name; - var htmlUrl = submission.Assignment.HtmlUrl; - var assessmentRating = assessmentRatings.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome.Id); - var outcomeGradeLevel = GetMasteryLevel(assessmentRating); + var grade = assessmentRatings + .Where(ar => ar?.Criterion?.Outcome?.Id == outcome.Id) + .Select(static ar => ar.Points) + .FirstOrDefault(); - var kpiTableEntryIndex = kpiTableEntries.FindIndex(kte => kte.Kpi == kpiName); - - if (outcomeGradeLevel is not null) + if (grade != null) { - if (kpiTableEntryIndex > -1) - { - UpdateKpiTableEntry(kpiTableEntries, kpiTableEntryIndex, assignmentName, GetGradeStatus(gradeStatus.Value), htmlUrl); - } - else + var kpiName = outcome.Title; + var assignmentName = submission.Assignment.Name; + var htmlUrl = submission.Assignment.HtmlUrl; + var assessmentRating = assessmentRatings.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome.Id); + var outcomeGradeLevel = GetMasteryLevel(assessmentRating); + + var kpiTableEntryIndex = kpiTableEntries.FindIndex(kte => kte.Kpi == kpiName); + + if (outcomeGradeLevel is not null) { - AddKpiTableEntry(kpiTableEntries, kpiName, outcomeGradeLevel.Value, assignmentName, GetGradeStatus(gradeStatus.Value), htmlUrl); + if (kpiTableEntryIndex > -1) + { + UpdateKpiTableEntry(kpiTableEntries, kpiTableEntryIndex, assignmentName, GetGradeStatus(grade.Value), htmlUrl); + } + else + { + AddKpiTableEntry(kpiTableEntries, kpiName, outcomeGradeLevel.Value, assignmentName, GetGradeStatus(grade.Value), htmlUrl); + } } } } @@ -172,6 +184,18 @@ private static void AddKpiTableEntry(ICollection kpiTableEntries, _ => null, }; } + + if (FhictConstants.ProfessionalSkills.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalSkill)) + { + return professionalSkill.MasteryLevel switch + { + 0 => OutcomeGradeLevel.One, + 1 => OutcomeGradeLevel.Two, + 2 => OutcomeGradeLevel.Three, + 3 => OutcomeGradeLevel.Four, + _ => null, + }; + } } return null; From d39e859c7543176c4bb432ab77a243327787f89c Mon Sep 17 00:00:00 2001 From: Sven Hansen <76601644+SyntaxSven@users.noreply.github.com> Date: Mon, 26 Jun 2023 15:02:00 +0200 Subject: [PATCH 09/20] Move assessmentRating points to grade logic to model --- .../Model/GraphQl/AssessmentRating.cs | 9 ++++ Epsilon/Component/KpiTableComponentFetcher.cs | 53 +++++++------------ 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs index 8d570e6a..3b90f8bf 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs @@ -8,4 +8,13 @@ public record AssessmentRating( ) { public bool IsMastery => Points >= Criterion?.MasteryPoints; + + public string Grade => Points switch + { + >= 5.0 => "O", + >= 4.0 => "G", + >= 3.0 => "S", + >= 0.0 => "U", + _ => "-", + }; } \ No newline at end of file diff --git a/Epsilon/Component/KpiTableComponentFetcher.cs b/Epsilon/Component/KpiTableComponentFetcher.cs index 163001d2..67a639ec 100644 --- a/Epsilon/Component/KpiTableComponentFetcher.cs +++ b/Epsilon/Component/KpiTableComponentFetcher.cs @@ -102,10 +102,7 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) if (assessmentRatings is not null) { - var grade = assessmentRatings - .Where(ar => ar?.Criterion?.Outcome?.Id == outcome.Id) - .Select(static ar => ar.Points) - .FirstOrDefault(); + var grade = assessmentRatings.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome.Id)?.Grade; if (grade != null) { @@ -121,11 +118,11 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) { if (kpiTableEntryIndex > -1) { - UpdateKpiTableEntry(kpiTableEntries, kpiTableEntryIndex, assignmentName, GetGradeStatus(grade.Value), htmlUrl); + UpdateKpiTableEntry(kpiTableEntries, kpiTableEntryIndex, assignmentName, grade, htmlUrl); } else { - AddKpiTableEntry(kpiTableEntries, kpiName, outcomeGradeLevel.Value, assignmentName, GetGradeStatus(grade.Value), htmlUrl); + AddKpiTableEntry(kpiTableEntries, kpiName, outcomeGradeLevel.Value, assignmentName, grade, htmlUrl); } } } @@ -171,45 +168,31 @@ private static void AddKpiTableEntry(ICollection kpiTableEntries, private static OutcomeGradeLevel? GetMasteryLevel(AssessmentRating? assessmentRating) { + static OutcomeGradeLevel? GetGradeLevel(int masteryLevel) + { + return masteryLevel switch + { + 0 => OutcomeGradeLevel.One, + 1 => OutcomeGradeLevel.Two, + 2 => OutcomeGradeLevel.Three, + 3 => OutcomeGradeLevel.Four, + _ => null, + }; + } + if (assessmentRating != null) { if (FhictConstants.ProfessionalTasks.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalTask)) { - return professionalTask.MasteryLevel switch - { - 0 => OutcomeGradeLevel.One, - 1 => OutcomeGradeLevel.Two, - 2 => OutcomeGradeLevel.Three, - 3 => OutcomeGradeLevel.Four, - _ => null, - }; + return GetGradeLevel(professionalTask.MasteryLevel); } - + if (FhictConstants.ProfessionalSkills.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalSkill)) { - return professionalSkill.MasteryLevel switch - { - 0 => OutcomeGradeLevel.One, - 1 => OutcomeGradeLevel.Two, - 2 => OutcomeGradeLevel.Three, - 3 => OutcomeGradeLevel.Four, - _ => null, - }; + return GetGradeLevel(professionalSkill.MasteryLevel); } } return null; } - - private static string GetGradeStatus(double grade) - { - return grade switch - { - >= 5.0 => "O", - >= 4.0 => "G", - >= 3.0 => "S", - >= 0.0 => "U", - _ => "-", - }; - } } \ No newline at end of file From c02e970cc49cabc5a6d8a103564a124b797e16d6 Mon Sep 17 00:00:00 2001 From: Sven Hansen <76601644+SyntaxSven@users.noreply.github.com> Date: Mon, 26 Jun 2023 15:09:46 +0200 Subject: [PATCH 10/20] Remove commented code --- Epsilon.Abstractions/Component/KpiTable.cs | 69 ++++++++-------------- 1 file changed, 24 insertions(+), 45 deletions(-) diff --git a/Epsilon.Abstractions/Component/KpiTable.cs b/Epsilon.Abstractions/Component/KpiTable.cs index b7f37f4b..790567cf 100644 --- a/Epsilon.Abstractions/Component/KpiTable.cs +++ b/Epsilon.Abstractions/Component/KpiTable.cs @@ -30,10 +30,10 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) { var body = new Body(); - // Create a table, with columns for the outcomes and corresponding assignments and grades + // Create a table to display outcomes, assignments, and grades var table = new Table(); - // Columns header texts + // Define column header texts var columnsHeaders = new List { "KPI", "Assignments", "Grades", }; // Create the table header row @@ -48,17 +48,17 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) // Add the header row to the table table.AppendChild(headerRow); - // Create the table body rows and cells in which the first cell is the outcome and the rest are the assignments and grades + // Create the table body rows and cells foreach (var entry in Entries) { var tableRow = new TableRow(); - // KPI column + // Outcome (KPI) column tableRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(new Run(new Text(entry.Kpi))))); // Assignments column - var aParagraph = new Paragraph(); - var aRun = aParagraph.AppendChild(new Run()); + var assignmentsParagraph = new Paragraph(); + var assignmentsRun = assignmentsParagraph.AppendChild(new Run()); foreach (var assignment in entry.Assignments) { @@ -68,63 +68,42 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) var runProperties = new RunProperties( new Underline { Val = UnderlineValues.Single, }); - aRun.AppendChild(new Hyperlink(new Run(runProperties, new Text(assignment.Name))) + assignmentsRun.AppendChild(new Hyperlink(new Run(runProperties, new Text(assignment.Name))) { - History = OnOffValue.FromBoolean(true), Id = relationshipId, + History = OnOffValue.FromBoolean(true), + Id = relationshipId, }); - - aRun.AppendChild(new Break()); - } - tableRow.AppendChild(CreateTableCellWithBorders("3000", aParagraph)); + assignmentsRun.AppendChild(new Break()); + } + tableRow.AppendChild(CreateTableCellWithBorders("3000", assignmentsParagraph)); + // Grades column var grades = entry.Assignments.Select(static a => a.Grade); - var gParagraph = new Paragraph(); - var gRun = gParagraph.AppendChild(new Run()); - + var gradesParagraph = new Paragraph(); + var gradesRun = gradesParagraph.AppendChild(new Run()); + foreach (var grade in grades) { - gRun.AppendChild(new Text(grade)); - gRun.AppendChild(new Break()); + gradesRun.AppendChild(new Text(grade)); + gradesRun.AppendChild(new Break()); } - - tableRow.AppendChild(CreateTableCellWithBorders("3000", gParagraph)); + + tableRow.AppendChild(CreateTableCellWithBorders("3000", gradesParagraph)); // Add the row to the table table.AppendChild(tableRow); } - - // body.AppendChild(GetLegend()); + + // Newline to separate the table from the rest of the document body.Append(new Paragraph(new Run(new Text("")))); + + // Add the table to the document body.AppendChild(table); mainDocumentPart.Document.AppendChild(body); } - - // private OpenXmlElement GetLegend() - // { - // var table = new Table(); - // - // foreach (var status in GradeStatus) - // { - // var row = new TableRow(); - // var cellName = CreateTableCellWithBorders("200"); - // cellName.Append(new Paragraph(new Run(new Text(status.Value.Level)))); - // - // var cellValue = CreateTableCellWithBorders("200"); - // cellValue.Append(new Paragraph(new Run(new Text("")))); - // cellValue.FirstChild?.Append(new Shading - // { - // Fill = status.Value.Color, - // }); - // row.AppendChild(cellName); - // row.AppendChild(cellValue); - // table.AppendChild(row); - // } - // - // return table; - // } private static TableCell CreateTableCellWithBorders(string? width, params OpenXmlElement[] elements) { From 22268f9626060c0ab101a1fe8d4ab91b60bf26a9 Mon Sep 17 00:00:00 2001 From: Sven Hansen <76601644+SyntaxSven@users.noreply.github.com> Date: Mon, 26 Jun 2023 22:29:43 +0200 Subject: [PATCH 11/20] Use existing records to determine order, color and name & word export column width fix --- Epsilon.Abstractions/Component/KpiTable.cs | 28 +---- .../Component/KpiTableEntry.cs | 4 +- .../Component/KpiTableEntryLevel.cs | 6 - .../Model/GraphQl/AssessmentRating.cs | 4 +- .../Model/OutcomeGradeLevel.cs | 9 -- .../src/components/KpiTable.vue | 2 +- Epsilon/Component/KpiTableComponentFetcher.cs | 108 +++++++----------- 7 files changed, 54 insertions(+), 107 deletions(-) delete mode 100644 Epsilon.Abstractions/Component/KpiTableEntryLevel.cs delete mode 100644 Epsilon.Canvas.Abstractions/Model/OutcomeGradeLevel.cs diff --git a/Epsilon.Abstractions/Component/KpiTable.cs b/Epsilon.Abstractions/Component/KpiTable.cs index 790567cf..403ce2bf 100644 --- a/Epsilon.Abstractions/Component/KpiTable.cs +++ b/Epsilon.Abstractions/Component/KpiTable.cs @@ -1,31 +1,13 @@ using DocumentFormat.OpenXml; using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Wordprocessing; -using Epsilon.Canvas.Abstractions.Model; namespace Epsilon.Abstractions.Component; public record KpiTable( - IEnumerable Entries, - IDictionary GradeStatus + IEnumerable Entries ) : IWordCompetenceComponent { - public static readonly IDictionary DefaultGradeStatus = new Dictionary - { - { - OutcomeGradeLevel.One, new KpiTableEntryLevel("One", "CBF5DD") - }, - { - OutcomeGradeLevel.Two, new KpiTableEntryLevel("Two", "64E3A1") - }, - { - OutcomeGradeLevel.Three, new KpiTableEntryLevel("Three", "27A567") - }, - { - OutcomeGradeLevel.Four, new KpiTableEntryLevel("Four", "198450") - }, - }; - public void AddToWordDocument(MainDocumentPart mainDocumentPart) { var body = new Body(); @@ -34,7 +16,7 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) var table = new Table(); // Define column header texts - var columnsHeaders = new List { "KPI", "Assignments", "Grades", }; + var columnsHeaders = new Dictionary { { "KPI", "3000" }, { "Assignments", "5000" }, { "Grades", "1000" }, }; // Create the table header row var headerRow = new TableRow(); @@ -42,7 +24,7 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) // Create the header cells foreach (var columnHeader in columnsHeaders) { - headerRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(new Run(new Text(columnHeader))))); + headerRow.AppendChild(CreateTableCellWithBorders(columnHeader.Value, new Paragraph(new Run(new Text(columnHeader.Key))))); } // Add the header row to the table @@ -77,7 +59,7 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) assignmentsRun.AppendChild(new Break()); } - tableRow.AppendChild(CreateTableCellWithBorders("3000", assignmentsParagraph)); + tableRow.AppendChild(CreateTableCellWithBorders("5000", assignmentsParagraph)); // Grades column var grades = entry.Assignments.Select(static a => a.Grade); @@ -90,7 +72,7 @@ public void AddToWordDocument(MainDocumentPart mainDocumentPart) gradesRun.AppendChild(new Break()); } - tableRow.AppendChild(CreateTableCellWithBorders("3000", gradesParagraph)); + tableRow.AppendChild(CreateTableCellWithBorders("1000", gradesParagraph)); // Add the row to the table table.AppendChild(tableRow); diff --git a/Epsilon.Abstractions/Component/KpiTableEntry.cs b/Epsilon.Abstractions/Component/KpiTableEntry.cs index a00045c3..613c403e 100644 --- a/Epsilon.Abstractions/Component/KpiTableEntry.cs +++ b/Epsilon.Abstractions/Component/KpiTableEntry.cs @@ -1,7 +1,9 @@ +using Epsilon.Abstractions.Model; + namespace Epsilon.Abstractions.Component; public record KpiTableEntry( string Kpi, - KpiTableEntryLevel Level, + MasteryLevel MasteryLevel, IEnumerable Assignments ); diff --git a/Epsilon.Abstractions/Component/KpiTableEntryLevel.cs b/Epsilon.Abstractions/Component/KpiTableEntryLevel.cs deleted file mode 100644 index bd33005e..00000000 --- a/Epsilon.Abstractions/Component/KpiTableEntryLevel.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Epsilon.Abstractions.Component; - -public record KpiTableEntryLevel( - string Level, - string Color -); \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs b/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs index 3b90f8bf..2b2034b0 100644 --- a/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs +++ b/Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs @@ -9,12 +9,12 @@ public record AssessmentRating( { public bool IsMastery => Points >= Criterion?.MasteryPoints; - public string Grade => Points switch + public string? Grade => Points switch { >= 5.0 => "O", >= 4.0 => "G", >= 3.0 => "S", >= 0.0 => "U", - _ => "-", + _ => null, }; } \ No newline at end of file diff --git a/Epsilon.Canvas.Abstractions/Model/OutcomeGradeLevel.cs b/Epsilon.Canvas.Abstractions/Model/OutcomeGradeLevel.cs deleted file mode 100644 index 9a7e2662..00000000 --- a/Epsilon.Canvas.Abstractions/Model/OutcomeGradeLevel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Epsilon.Canvas.Abstractions.Model; - -public enum OutcomeGradeLevel -{ - One, - Two, - Three, - Four, -} \ No newline at end of file diff --git a/Epsilon.Host.Frontend/src/components/KpiTable.vue b/Epsilon.Host.Frontend/src/components/KpiTable.vue index bbbb90a0..bad01d0e 100644 --- a/Epsilon.Host.Frontend/src/components/KpiTable.vue +++ b/Epsilon.Host.Frontend/src/components/KpiTable.vue @@ -37,7 +37,7 @@ function formatDate(date: Date): string { {{ KPI.kpi }} diff --git a/Epsilon/Component/KpiTableComponentFetcher.cs b/Epsilon/Component/KpiTableComponentFetcher.cs index 67a639ec..45231c83 100644 --- a/Epsilon/Component/KpiTableComponentFetcher.cs +++ b/Epsilon/Component/KpiTableComponentFetcher.cs @@ -1,5 +1,5 @@ using Epsilon.Abstractions.Component; -using Epsilon.Canvas.Abstractions.Model; +using Epsilon.Abstractions.Model; using Epsilon.Canvas.Abstractions.Model.GraphQl; using Epsilon.Canvas.Abstractions.Service; using Microsoft.Extensions.Configuration; @@ -89,42 +89,31 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) { var rubricCriteria = submission.Assignment.Rubric.Criteria?.ToArray(); - if (rubricCriteria != null) + if (rubricCriteria is not null) { - foreach (var outcome in rubricCriteria.Select(static criteria => criteria.Outcome).Where(static c => c != null)) + foreach (var outcome in GetValidOutcomes(rubricCriteria)) { - //Validate that outcome is a HboI KPI - if (outcome != null - && (FhictConstants.ProfessionalTasks.ContainsKey(outcome.Id) || FhictConstants.ProfessionalSkills.ContainsKey(outcome.Id)) - && rubricCriteria.Any()) + var assessmentRatings = submission.RubricAssessments.Nodes.FirstOrDefault()?.AssessmentRatings; + + var grade = assessmentRatings?.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome?.Id)?.Grade; + + if (grade != null) { - var assessmentRatings = submission.RubricAssessments.Nodes.FirstOrDefault()?.AssessmentRatings; + var assignmentName = submission.Assignment.Name; + var htmlUrl = submission.Assignment.HtmlUrl; + var assessmentRating = assessmentRatings?.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome?.Id); - if (assessmentRatings is not null) + if (assessmentRating is not null) { - var grade = assessmentRatings.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome.Id)?.Grade; + var kpiTableEntryIndex = kpiTableEntries.FindIndex(kte => kte.Kpi == outcome?.Title); - if (grade != null) + if (kpiTableEntryIndex > -1) { - var kpiName = outcome.Title; - var assignmentName = submission.Assignment.Name; - var htmlUrl = submission.Assignment.HtmlUrl; - var assessmentRating = assessmentRatings.FirstOrDefault(ar => ar?.Criterion?.Outcome?.Id == outcome.Id); - var outcomeGradeLevel = GetMasteryLevel(assessmentRating); - - var kpiTableEntryIndex = kpiTableEntries.FindIndex(kte => kte.Kpi == kpiName); - - if (outcomeGradeLevel is not null) - { - if (kpiTableEntryIndex > -1) - { - UpdateKpiTableEntry(kpiTableEntries, kpiTableEntryIndex, assignmentName, grade, htmlUrl); - } - else - { - AddKpiTableEntry(kpiTableEntries, kpiName, outcomeGradeLevel.Value, assignmentName, grade, htmlUrl); - } - } + UpdateKpiTableEntry(kpiTableEntries, kpiTableEntryIndex, assignmentName, grade, htmlUrl); + } + else + { + AddKpiTableEntry(kpiTableEntries, outcome, assignmentName, grade, htmlUrl); } } } @@ -134,9 +123,21 @@ public override async Task Fetch(DateTime startDate, DateTime endDate) } } - kpiTableEntries = kpiTableEntries.OrderBy(static kte => kte.Kpi).ToList(); + kpiTableEntries = kpiTableEntries + .OrderBy(static kte => kte.MasteryLevel.Level) + .ThenBy(static kte => kte.Kpi) + .ToList(); - return new KpiTable(kpiTableEntries, KpiTable.DefaultGradeStatus); + return new KpiTable(kpiTableEntries); + } + + private static IEnumerable GetValidOutcomes(IEnumerable rubricCriteria) + { + return rubricCriteria + .Select(static criteria => criteria.Outcome) + .Where(static outcome => outcome is not null && + (FhictConstants.ProfessionalTasks.ContainsKey(outcome.Id) || + FhictConstants.ProfessionalSkills.ContainsKey(outcome.Id))); } private static void UpdateKpiTableEntry(IList kpiTableEntries, int index, string assignmentName, string gradeStatus, Uri htmlUrl) @@ -154,45 +155,22 @@ private static void UpdateKpiTableEntry(IList kpiTableEntries, in kpiTableEntries[index] = updatedEntry; } - private static void AddKpiTableEntry(ICollection kpiTableEntries, string kpiName, OutcomeGradeLevel kpiLevel, string assignmentName, string gradeStatus, Uri htmlUrl) + private static void AddKpiTableEntry(ICollection kpiTableEntries, Outcome outcome, string assignmentName, string gradeStatus, Uri htmlUrl) { - kpiTableEntries.Add(new KpiTableEntry( - kpiName, - KpiTable.DefaultGradeStatus[kpiLevel], - new List - { - new KpiTableEntryAssignment(assignmentName, gradeStatus, htmlUrl), - } - )); - } + MasteryLevel? masteryLevel = null; + ProfessionalSkillLevel? professionalSkill = null; - private static OutcomeGradeLevel? GetMasteryLevel(AssessmentRating? assessmentRating) - { - static OutcomeGradeLevel? GetGradeLevel(int masteryLevel) + if (FhictConstants.ProfessionalTasks.TryGetValue(outcome.Id, out var professionalTask) + || FhictConstants.ProfessionalSkills.TryGetValue(outcome.Id, out professionalSkill)) { - return masteryLevel switch - { - 0 => OutcomeGradeLevel.One, - 1 => OutcomeGradeLevel.Two, - 2 => OutcomeGradeLevel.Three, - 3 => OutcomeGradeLevel.Four, - _ => null, - }; + masteryLevel = new HboIDomain2018().MasteryLevels.FirstOrDefault(ml => ml.Id == (professionalTask?.MasteryLevel ?? professionalSkill?.MasteryLevel)); } - if (assessmentRating != null) + if (masteryLevel != null) { - if (FhictConstants.ProfessionalTasks.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalTask)) - { - return GetGradeLevel(professionalTask.MasteryLevel); - } - - if (FhictConstants.ProfessionalSkills.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalSkill)) - { - return GetGradeLevel(professionalSkill.MasteryLevel); - } + var assignment = new KpiTableEntryAssignment(assignmentName, gradeStatus, htmlUrl); + var kpiTableEntry = new KpiTableEntry(outcome.Title, masteryLevel, new List { assignment, }); + kpiTableEntries.Add(kpiTableEntry); } - - return null; } } \ No newline at end of file From 8c01c63e0ba205b5ea877855961f42f078927c32 Mon Sep 17 00:00:00 2001 From: Sven Hansen <76601644+SyntaxSven@users.noreply.github.com> Date: Tue, 27 Jun 2023 14:00:01 +0200 Subject: [PATCH 12/20] Fix lint issues --- Epsilon.Host.Frontend/src/components/KpiTable.vue | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Epsilon.Host.Frontend/src/components/KpiTable.vue b/Epsilon.Host.Frontend/src/components/KpiTable.vue index bad01d0e..a8184b62 100644 --- a/Epsilon.Host.Frontend/src/components/KpiTable.vue +++ b/Epsilon.Host.Frontend/src/components/KpiTable.vue @@ -36,21 +36,22 @@ function formatDate(date: Date): string { Grades - + {{ KPI.kpi }}
+ :key="assignment" + :style="{ textAlign: 'start' }"> {{ assignment.name }}
-
+
{{ assignment.grade }}
From 96c52068740947b9b160cad4695d790fd469b21d Mon Sep 17 00:00:00 2001 From: Sven Hansen <76601644+SyntaxSven@users.noreply.github.com> Date: Tue, 27 Jun 2023 14:13:24 +0200 Subject: [PATCH 13/20] Fix lint issues --- .../src/views/PerformanceDashboard.vue | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue b/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue index e1ed4e4b..01cd1378 100644 --- a/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue +++ b/Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue @@ -1,8 +1,8 @@