From 17734ab6416e8b6dc83947120bf3ba194ea48473 Mon Sep 17 00:00:00 2001 From: Alex Mickelson Date: Mon, 31 Jul 2023 16:27:38 -0600 Subject: [PATCH] got rubric creation working --- .../Module/Assignment/AssignmentDetails.razor | 14 +-- .../Features/Configuration/CoursePlanner.cs | 99 +++++++++---------- .../CanvasModels/Assignments/CanvasRubric.cs | 37 +++++++ .../Assignments/CanvasRubricAssociation.cs | 35 +++++++ Management/Models/Local/AssignmentTemplate.cs | 18 ++-- Management/Models/Local/LocalAssignment.cs | 37 +++++++ .../Canvas/CanvasAssignmentService.cs | 77 +++++++++++++-- .../CanvasRubricCreationResponse.cs | 7 ++ .../Services/Canvas/CanvasServiceUtils.cs | 7 +- README.md | 25 ++++- 10 files changed, 276 insertions(+), 80 deletions(-) create mode 100644 Management/Models/CanvasModels/Assignments/CanvasRubric.cs create mode 100644 Management/Models/CanvasModels/Assignments/CanvasRubricAssociation.cs create mode 100644 Management/Services/Canvas/CanvasRequests/CanvasRubricCreationResponse.cs diff --git a/Management.Web/Shared/Module/Assignment/AssignmentDetails.razor b/Management.Web/Shared/Module/Assignment/AssignmentDetails.razor index 01eb365..416aff0 100644 --- a/Management.Web/Shared/Module/Assignment/AssignmentDetails.razor +++ b/Management.Web/Shared/Module/Assignment/AssignmentDetails.razor @@ -147,7 +147,7 @@ && planner.AssignmentNeedsUpdates(Assignment) ) { -
need to update canvas
+
Need to update canvas
} else { @@ -164,16 +164,8 @@
@if(Assignment.use_template) { - var template = planner.LocalCourse?.AssignmentTemplates.First((t) => t.Id == Assignment.template_id); - if(template == null) - { - System.Console.WriteLine($"Could not find template fof assignment, {Assignment.template_id}"); - } - else - { - var html = AssignmentTemplate.GetHtml(template, Assignment); - @((MarkupString) html) - } + var html = Assignment.GetDescriptionHtml(planner.LocalCourse?.AssignmentTemplates); + @((MarkupString) html) } else { diff --git a/Management/Features/Configuration/CoursePlanner.cs b/Management/Features/Configuration/CoursePlanner.cs index 483fd59..008aa6a 100644 --- a/Management/Features/Configuration/CoursePlanner.cs +++ b/Management/Features/Configuration/CoursePlanner.cs @@ -103,6 +103,7 @@ public class CoursePlanner ) return; + Console.WriteLine("syncing with canvas"); LoadingCanvasData = true; StateHasChanged?.Invoke(); @@ -111,10 +112,15 @@ public class CoursePlanner await ensureAllModulesCreated(canvasId); await reloadModules_UpdateLocalModulesWithNewId(canvasId); - await ensureAllAssignmentsCreated_updateIds(canvasId); + await syncAssignmentsWithCanvas(canvasId); + + + CanvasAssignments = await canvas.Assignments.GetAll(canvasId); + CanvasModules = await canvas.GetModules(canvasId); LoadingCanvasData = false; StateHasChanged?.Invoke(); + Console.WriteLine("done syncing with canvas\n"); } private async Task reloadModules_UpdateLocalModulesWithNewId(ulong canvasId) @@ -148,7 +154,7 @@ public class CoursePlanner } } - private async Task ensureAllAssignmentsCreated_updateIds(ulong canvasId) + private async Task syncAssignmentsWithCanvas(ulong canvasId) { if ( LocalCourse == null @@ -160,7 +166,7 @@ public class CoursePlanner var moduleTasks = LocalCourse.Modules.Select(async m => { - var assignmentTasks = m.Assignments.Select(ensureAssignmentInCanvas_returnUpdated); + var assignmentTasks = m.Assignments.Select(syncAssignmentToCanvas); var assignments = await Task.WhenAll(assignmentTasks); return m with { Assignments = assignments }; }); @@ -169,7 +175,7 @@ public class CoursePlanner LocalCourse = LocalCourse with { Modules = modules }; } - private async Task ensureAssignmentInCanvas_returnUpdated( + private async Task syncAssignmentToCanvas( LocalAssignment localAssignment ) { @@ -187,11 +193,13 @@ public class CoursePlanner var canvasAssignment = CanvasAssignments.FirstOrDefault( ca => ca.Id == localAssignment.canvasId ); - string localHtmlDescription = getAssignmentDescriptionHtml(localAssignment); + string localHtmlDescription = localAssignment.GetDescriptionHtml( + LocalCourse.AssignmentTemplates + ); if (canvasAssignment != null) { - var assignmentNeedsUpdates = AssignmentNeedsUpdates(localAssignment); + var assignmentNeedsUpdates = AssignmentNeedsUpdates(localAssignment, quiet: false); if (assignmentNeedsUpdates) { await canvas.Assignments.Update(courseId: canvasId, localAssignment, localHtmlDescription); @@ -217,21 +225,7 @@ public class CoursePlanner } } - private string getAssignmentDescriptionHtml(LocalAssignment localAssignment) - { - if (LocalCourse == null) - throw new Exception( - "cannot get assignment description if local course is null or other values not set" - ); - return localAssignment.use_template - ? AssignmentTemplate.GetHtml( - LocalCourse.AssignmentTemplates.First(t => t.Id == localAssignment.template_id), - localAssignment - ) - : Markdig.Markdown.ToHtml(localAssignment.description); - } - - public bool AssignmentNeedsUpdates(LocalAssignment localAssignment) + public bool AssignmentNeedsUpdates(LocalAssignment localAssignment, bool quiet = true) { if ( LocalCourse == null @@ -245,7 +239,7 @@ public class CoursePlanner var canvasAssignment = CanvasAssignments.First(ca => ca.Id == localAssignment.canvasId); - var localHtmlDescription = getAssignmentDescriptionHtml(localAssignment); + var localHtmlDescription = localAssignment.GetDescriptionHtml(LocalCourse.AssignmentTemplates); var canvasHtmlDescription = canvasAssignment.Description; canvasHtmlDescription = Regex.Replace(canvasHtmlDescription, "", ""); @@ -259,37 +253,40 @@ public class CoursePlanner var submissionTypesSame = canvasAssignment.SubmissionTypes.SequenceEqual( localAssignment.submission_types.Select(t => t.ToString()) ); - - if (!dueDatesSame) - Console.WriteLine( - $"Due dates different for {localAssignment.name}, local: {localAssignment.due_at}, in canvas {canvasAssignment.DueAt}" - ); - - if (!descriptionSame) + + if (!quiet) { - Console.WriteLine($"descriptions different for {localAssignment.name}"); - Console.WriteLine("Local Description:"); - Console.WriteLine(localHtmlDescription); - Console.WriteLine("Canvas Description: "); - Console.WriteLine(canvasHtmlDescription); - } + if (!dueDatesSame) + Console.WriteLine( + $"Due dates different for {localAssignment.name}, local: {localAssignment.due_at}, in canvas {canvasAssignment.DueAt}" + ); - if (!nameSame) - Console.WriteLine( - $"names different for {localAssignment.name}, local: {localAssignment.name}, in canvas {canvasAssignment.Name}" - ); - if (!lockDateSame) - Console.WriteLine( - $"Lock Dates different for {localAssignment.name}, local: {localAssignment.lock_at}, in canvas {canvasAssignment.LockAt}" - ); - if (!pointsSame) - Console.WriteLine( - $"Points different for {localAssignment.name}, local: {localAssignment.points_possible}, in canvas {canvasAssignment.PointsPossible}" - ); - if (!submissionTypesSame) - Console.WriteLine( - $"Submission Types different for {localAssignment.name}, local: {JsonSerializer.Serialize(localAssignment.submission_types.Select(t => t.ToString()))}, in canvas {JsonSerializer.Serialize(canvasAssignment.SubmissionTypes)}" - ); + if (!descriptionSame) + { + Console.WriteLine($"descriptions different for {localAssignment.name}"); + Console.WriteLine("Local Description:"); + Console.WriteLine(localHtmlDescription); + Console.WriteLine("Canvas Description: "); + Console.WriteLine(canvasHtmlDescription); + } + + if (!nameSame) + Console.WriteLine( + $"names different for {localAssignment.name}, local: {localAssignment.name}, in canvas {canvasAssignment.Name}" + ); + if (!lockDateSame) + Console.WriteLine( + $"Lock Dates different for {localAssignment.name}, local: {localAssignment.lock_at}, in canvas {canvasAssignment.LockAt}" + ); + if (!pointsSame) + Console.WriteLine( + $"Points different for {localAssignment.name}, local: {localAssignment.points_possible}, in canvas {canvasAssignment.PointsPossible}" + ); + if (!submissionTypesSame) + Console.WriteLine( + $"Submission Types different for {localAssignment.name}, local: {JsonSerializer.Serialize(localAssignment.submission_types.Select(t => t.ToString()))}, in canvas {JsonSerializer.Serialize(canvasAssignment.SubmissionTypes)}" + ); + } return !nameSame || !dueDatesSame diff --git a/Management/Models/CanvasModels/Assignments/CanvasRubric.cs b/Management/Models/CanvasModels/Assignments/CanvasRubric.cs new file mode 100644 index 0000000..7fff6dd --- /dev/null +++ b/Management/Models/CanvasModels/Assignments/CanvasRubric.cs @@ -0,0 +1,37 @@ +namespace CanvasModel.Assignments; + +public record CanvasRubric +{ + [JsonPropertyName("id")] + public ulong? Id { get; set; } + + [JsonPropertyName("title")] + public required string Title { get; set; } + + [JsonPropertyName("context_id")] + public ulong ContextId { get; set; } + + [JsonPropertyName("context_type")] + public required string ContextType { get; set; } + + [JsonPropertyName("points_possible")] + public double PointsPossible { get; set; } + + [JsonPropertyName("reusable")] + public bool Reusable { get; set; } + + [JsonPropertyName("read_only")] + public bool ReadOnly { get; set; } + + // [JsonPropertyName("free_form_criterion_comments")] + // public bool? FreeFormCriterionComments { get; set; } + + [JsonPropertyName("hide_score_total")] + public bool? HideScoreTotal { get; set; } + + // [JsonPropertyName("data")] + // public required IEnumerable Data { get; set; } + + // assessments + // associations +} \ No newline at end of file diff --git a/Management/Models/CanvasModels/Assignments/CanvasRubricAssociation.cs b/Management/Models/CanvasModels/Assignments/CanvasRubricAssociation.cs new file mode 100644 index 0000000..8738571 --- /dev/null +++ b/Management/Models/CanvasModels/Assignments/CanvasRubricAssociation.cs @@ -0,0 +1,35 @@ +namespace CanvasModel.Assignments; + +public record CanvasRubricAssociation +{ + [JsonPropertyName("id")] + public ulong Id { get; set; } + + [JsonPropertyName("rubrid_id")] + public ulong RubricId { get; set; } + + [JsonPropertyName("association_id")] + public ulong AssociationId { get; set; } + + [JsonPropertyName("association_type")] + public required string AssociationType { get; set; } + + [JsonPropertyName("use_for_grading")] + public bool UseForGrading { get; set; } + + [JsonPropertyName("summary_data")] + public string? SummaryDaata { get; set; } + + [JsonPropertyName("purpose")] + public required string Purpose { get; set; } + + [JsonPropertyName("hide_score_total")] + public bool? HideScoreTotal { get; set; } + + [JsonPropertyName("hide_points")] + public bool HidePoints { get; set; } + + [JsonPropertyName("hide_outcome-results")] + public bool HideOUtcomeResult { get; set; } + +} \ No newline at end of file diff --git a/Management/Models/Local/AssignmentTemplate.cs b/Management/Models/Local/AssignmentTemplate.cs index 4d214cf..5d94d31 100644 --- a/Management/Models/Local/AssignmentTemplate.cs +++ b/Management/Models/Local/AssignmentTemplate.cs @@ -15,16 +15,16 @@ public record AssignmentTemplate return matches.Select(match => match.Groups[1].Value); } - public static string GetHtml(AssignmentTemplate template, LocalAssignment assignment) - { + // public static string GetHtml(AssignmentTemplate template, LocalAssignment assignment) + // { - var html = Markdig.Markdown.ToHtml(template.Markdown); + // var html = Markdig.Markdown.ToHtml(template.Markdown); - foreach (KeyValuePair entry in assignment.template_variables) - { - html = html.Replace($"%7B%7B{entry.Key}%7D%7D", entry.Value); - } - return html; - } + // foreach (KeyValuePair entry in assignment.template_variables) + // { + // html = html.Replace($"%7B%7B{entry.Key}%7D%7D", entry.Value); + // } + // return html; + // } } diff --git a/Management/Models/Local/LocalAssignment.cs b/Management/Models/Local/LocalAssignment.cs index cd86d00..3606b3f 100644 --- a/Management/Models/Local/LocalAssignment.cs +++ b/Management/Models/Local/LocalAssignment.cs @@ -38,4 +38,41 @@ public record LocalAssignment public DateTime due_at { get; init; } public int points_possible { get; init; } public IEnumerable submission_types { get; init; } = new SubmissionType[] { }; + + public string GetRubricHtml() + { + var output = "

Rubric

[\n";
+
+    var lineStrings = rubric.Select(
+      item => $"  {{\"label\": \"{item.Label}\", \"points\": {item.Points}}}"
+    );
+    output += string.Join(",\n", lineStrings);
+    output += "\n]
"; + return output; + } + + public string GetDescriptionHtml(IEnumerable? templates) + { + if (use_template && templates == null) + throw new Exception("cannot get description for assignment if templates not provided"); + + var rubricHtml = GetRubricHtml(); + + if (use_template) + { + var template = templates?.FirstOrDefault(t => t.Id == template_id); + if (template == null) + throw new Exception($"Could not find template with id {template_id}"); + + var html = Markdig.Markdown.ToHtml(template.Markdown); + + foreach (KeyValuePair entry in template_variables) + { + html = html.Replace($"%7B%7B{entry.Key}%7D%7D", entry.Value); + } + return html + "
" + rubricHtml; + } + + return Markdig.Markdown.ToHtml(description) + "
" + rubricHtml; + } } diff --git a/Management/Services/Canvas/CanvasAssignmentService.cs b/Management/Services/Canvas/CanvasAssignmentService.cs index eaa914e..1b7a2d2 100644 --- a/Management/Services/Canvas/CanvasAssignmentService.cs +++ b/Management/Services/Canvas/CanvasAssignmentService.cs @@ -22,12 +22,7 @@ public class CanvasAssignmentService return assignmentResponse.SelectMany( assignments => assignments.Select( - a => - a with - { - DueAt = a.DueAt?.ToLocalTime(), - LockAt = a.LockAt?.ToLocalTime() - } + a => a with { DueAt = a.DueAt?.ToLocalTime(), LockAt = a.LockAt?.ToLocalTime() } ) ); } @@ -85,5 +80,75 @@ public class CanvasAssignmentService var bodyObj = new { assignment = body }; request.AddBody(bodyObj); await webRequestor.PutAsync(request); + + await CreateRubric(courseId, localAssignment); + } + + public async Task CreateRubric(ulong courseId, LocalAssignment localAssignment) + { + if (localAssignment.canvasId == null) + throw new Exception("cannot create rubric if no canvas id in assignment"); + + var criterion = new Dictionary(); + + var i = 0; + foreach (var rubricItem in localAssignment.rubric) + { + var ratings = new Dictionary + { + { 0, new { description = "Full Marks", points = rubricItem.Points } }, + { 1, new { description = "No Marks", points = 0 } }, + }; + criterion[i] = new + { + description = rubricItem.Label, + points = rubricItem.Points, + ratings = ratings + }; + i++; + } + + // https://canvas.instructure.com/doc/api/rubrics.html#method.rubrics.create + var body = new + { + rubric_association_id = localAssignment.canvasId, + rubric = new + { + title = $"Rubric for Assignment: {localAssignment.name}", + association_id = localAssignment.canvasId, + association_type = "Assignment", + use_for_grading = true, + criteria = criterion, + }, + rubric_association = new + { + association_id = localAssignment.canvasId, + association_type = "Assignment", + purpose = "grading", + use_for_grading = true, + } + }; + var creationUrl = $"courses/{courseId}/rubrics"; + var rubricCreationRequest = new RestRequest(creationUrl); + rubricCreationRequest.AddBody(body); + rubricCreationRequest.AddHeader("Content-Type", "application/json"); + + var (rubricCreationResponse, creationResponse) = + await webRequestor.PostAsync(rubricCreationRequest); + + if (rubricCreationResponse == null) + throw new Exception("failed to create rubric before association"); + + var assignmentPointCorrectionBody = new + { + assignment = new { points_possible = localAssignment.points_possible } + }; + var adjustmentUrl = $"courses/{courseId}/assignments/{localAssignment.canvasId}"; + var pointAdjustmentRequest = new RestRequest(adjustmentUrl); + pointAdjustmentRequest.AddBody(assignmentPointCorrectionBody); + pointAdjustmentRequest.AddHeader("Content-Type", "application/json"); + var (updatedAssignment, adjustmentResponse) = await webRequestor.PutAsync( + pointAdjustmentRequest + ); } } diff --git a/Management/Services/Canvas/CanvasRequests/CanvasRubricCreationResponse.cs b/Management/Services/Canvas/CanvasRequests/CanvasRubricCreationResponse.cs new file mode 100644 index 0000000..65cffa4 --- /dev/null +++ b/Management/Services/Canvas/CanvasRequests/CanvasRubricCreationResponse.cs @@ -0,0 +1,7 @@ +using CanvasModel.Assignments; + +public record CanvasRubricCreationResponse +{ + public required CanvasRubric rubric { get; set; } + public CanvasRubricAssociation? rubric_association { get; set; } +} diff --git a/Management/Services/Canvas/CanvasServiceUtils.cs b/Management/Services/Canvas/CanvasServiceUtils.cs index 1a40e20..e3f7a72 100644 --- a/Management/Services/Canvas/CanvasServiceUtils.cs +++ b/Management/Services/Canvas/CanvasServiceUtils.cs @@ -1,10 +1,12 @@ using RestSharp; namespace Management.Services.Canvas; + public class CanvasServiceUtils { private const string BaseUrl = "https://snow.instructure.com/api/v1/"; private readonly IWebRequestor webRequestor; + public CanvasServiceUtils(IWebRequestor webRequestor) { this.webRequestor = webRequestor; @@ -36,7 +38,8 @@ public class CanvasServiceUtils nextUrl = getNextUrl(nextResponse.Headers); } - System.Console.WriteLine($"Requesting {typeof(T)} took {requestCount} requests"); + if (requestCount > 1) + System.Console.WriteLine($"Requesting {typeof(T)} took {requestCount} requests"); return returnData; } @@ -55,4 +58,4 @@ public class CanvasServiceUtils .TrimStart('<') .Replace(" ", "") .Replace(BaseUrl, ""); -} \ No newline at end of file +} diff --git a/README.md b/README.md index d0da9ee..e747d46 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,27 @@ Development command: `dotnet watch --project Management.Web/` Apparently the VSCode razor extension was compiled with a preview of dotnet 6... and only uses openssl 1.1. After installing openssl1.1 you can tell vscode to provide it with `export CLR_OPENSSL_VERSION_OVERRIDE=1.1; code ~/projects/canvasManagement`. -The issue can be tracked [here](https://github.com/dotnet/razor/issues/6241) \ No newline at end of file +The issue can be tracked [here](https://github.com/dotnet/razor/issues/6241) + + + + + + +

Daily Reading

+

Read today's assignment and do well please.

+

Github Submit URL

+

Rubric

[
+  {"label": "More Reading", "points": 5},
+  {"label": "sum to 8", "points": 1},
+  {"label": "(Extra Credit) Rubric Things", "points": 2}
+]
+ +

Daily Reading

+

Read today's assignment and do well please.

+

Github Submit URL

+

Rubric

[
+  {"label": "More Reading", "points": 5},
+  {"label": "sum to 8", "points": 1},
+  {"label": "(Extra Credit) Rubric Things", "points": 2}
+]
\ No newline at end of file