This commit is contained in:
2024-02-07 16:21:04 -07:00
parent 292c06ecc9
commit 0881acd1f8
77 changed files with 239 additions and 204 deletions

View File

@@ -26,4 +26,4 @@ public class DeserializationTests
result.Should().NotBeNull(); result.Should().NotBeNull();
result?.EnrollmentTerms?.First().Id.Should().Be(1); result?.EnrollmentTerms?.First().Id.Should().Be(1);
} }
} }

View File

@@ -42,4 +42,4 @@ public class CalendarMonthTests
month.Weeks.Last().Should().BeEquivalentTo(expectedLastWeek); month.Weeks.Last().Should().BeEquivalentTo(expectedLastWeek);
} }
} }

View File

@@ -23,4 +23,4 @@
// config!.EndDate.Should().Be(endAt); // config!.EndDate.Should().Be(endAt);
// config!.Days.Should().BeEquivalentTo(daysOfWeek); // config!.Days.Should().BeEquivalentTo(daysOfWeek);
// } // }
// } // }

View File

@@ -37,4 +37,4 @@
// manager.Modules.Count().Should().Be(4); // manager.Modules.Count().Should().Be(4);
// manager.Modules.ElementAt(3).Assignments.Count().Should().Be(1); // manager.Modules.ElementAt(3).Assignments.Count().Should().Be(1);
// } // }
// } // }

View File

@@ -94,4 +94,4 @@
// var semester = new SemesterPlanner(config); // var semester = new SemesterPlanner(config);
// semester.Days.Should().BeEquivalentTo(days); // semester.Days.Should().BeEquivalentTo(days);
// } // }
// } // }

View File

@@ -7,8 +7,8 @@ public class AssignmentMarkdownTests
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
{ {
Name="test assignment", Name = "test assignment",
Description ="here is the description", Description = "here is the description",
DueAt = new DateTime(), DueAt = new DateTime(),
LockAt = new DateTime(), LockAt = new DateTime(),
SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD],
@@ -29,13 +29,13 @@ public class AssignmentMarkdownTests
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
{ {
Name="test assignment", Name = "test assignment",
Description ="here is the description", Description = "here is the description",
DueAt = new DateTime(), DueAt = new DateTime(),
LockAt = new DateTime(), LockAt = new DateTime(),
SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD],
LocalAssignmentGroupName = "Final Project", LocalAssignmentGroupName = "Final Project",
Rubric = new List<RubricItem>() {} Rubric = new List<RubricItem>() { }
}; };
var assignmentMarkdown = assignment.ToMarkdown(); var assignmentMarkdown = assignment.ToMarkdown();
@@ -48,8 +48,8 @@ public class AssignmentMarkdownTests
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
{ {
Name="test assignment", Name = "test assignment",
Description ="here is the description", Description = "here is the description",
DueAt = new DateTime(), DueAt = new DateTime(),
LockAt = new DateTime(), LockAt = new DateTime(),
SubmissionTypes = [], SubmissionTypes = [],
@@ -71,8 +71,8 @@ public class AssignmentMarkdownTests
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
{ {
Name="test assignment", Name = "test assignment",
Description ="here is the description", Description = "here is the description",
DueAt = new DateTime(), DueAt = new DateTime(),
LockAt = null, LockAt = null,
SubmissionTypes = [], SubmissionTypes = [],
@@ -94,7 +94,7 @@ public class AssignmentMarkdownTests
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
{ {
Name="test assignment", Name = "test assignment",
Description = "", Description = "",
DueAt = new DateTime(), DueAt = new DateTime(),
LockAt = new DateTime(), LockAt = new DateTime(),
@@ -116,13 +116,14 @@ public class AssignmentMarkdownTests
{ {
var assignment = new LocalAssignment() var assignment = new LocalAssignment()
{ {
Name="test assignment", Name = "test assignment",
Description = "test assignment\n---\nsomestuff", Description = "test assignment\n---\nsomestuff",
DueAt = new DateTime(), DueAt = new DateTime(),
LockAt = new DateTime(), LockAt = new DateTime(),
SubmissionTypes = [], SubmissionTypes = [],
LocalAssignmentGroupName = "Final Project", LocalAssignmentGroupName = "Final Project",
Rubric = new List<RubricItem>() { Rubric = new List<RubricItem>()
{
} }
}; };

View File

@@ -72,8 +72,10 @@ public class FileStorageTests
[Test] [Test]
public async Task CourseSettings_CanBeSavedAndLoaded() public async Task CourseSettings_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new() { LocalCourse testCourse = new()
Settings = new() { {
Settings = new()
{
AssignmentGroups = [], AssignmentGroups = [],
Name = "Test Course with settings", Name = "Test Course with settings",
DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday], DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday],
@@ -96,13 +98,15 @@ public class FileStorageTests
[Test] [Test]
public async Task EmptyCourseModules_CanBeSavedAndLoaded() public async Task EmptyCourseModules_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new() { LocalCourse testCourse = new()
{
Settings = new() { Name = "Test Course with modules" }, Settings = new() { Name = "Test Course with modules" },
Modules = [ Modules = [
new() { new()
Name="test module 1", {
Assignments= [], Name = "test module 1",
Quizzes=[] Assignments = [],
Quizzes = []
} }
] ]
}; };
@@ -118,26 +122,29 @@ public class FileStorageTests
[Test] [Test]
public async Task CourseModules_WithAssignments_CanBeSavedAndLoaded() public async Task CourseModules_WithAssignments_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new() { LocalCourse testCourse = new()
{
Settings = new() { Name = "Test Course with modules and assignments" }, Settings = new() { Name = "Test Course with modules and assignments" },
Modules = [ Modules = [
new() { new()
Name="test module 1 with assignments", {
Assignments=[ Name = "test module 1 with assignments",
new () { Assignments = [
Name="test assignment", new()
Description ="here is the description", {
Name = "test assignment",
Description = "here is the description",
DueAt = new DateTime(), DueAt = new DateTime(),
LockAt = new DateTime(), LockAt = new DateTime(),
SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD],
LocalAssignmentGroupName = "Final Project", LocalAssignmentGroupName = "Final Project",
Rubric = [ Rubric = [
new() {Points = 4, Label="do task 1"}, new() { Points = 4, Label = "do task 1" },
new() {Points = 2, Label="do task 2"}, new() { Points = 2, Label = "do task 2" },
] ]
} }
], ],
Quizzes=[] Quizzes = []
} }
] ]
}; };
@@ -156,14 +163,17 @@ public class FileStorageTests
[Test] [Test]
public async Task CourseModules_WithQuizzes_CanBeSavedAndLoaded() public async Task CourseModules_WithQuizzes_CanBeSavedAndLoaded()
{ {
LocalCourse testCourse = new() { LocalCourse testCourse = new()
{
Settings = new() { Name = "Test Course with modules and quiz" }, Settings = new() { Name = "Test Course with modules and quiz" },
Modules = [ Modules = [
new() { new()
Name="test module 1 with quiz", {
Assignments=[], Name = "test module 1 with quiz",
Quizzes=[ Assignments = [],
new() { Quizzes = [
new()
{
Name = "Test Quiz", Name = "Test Quiz",
Description = "quiz description", Description = "quiz description",
LockAt = new DateTime(2022, 10, 3, 12, 5, 0), LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
@@ -171,8 +181,9 @@ public class FileStorageTests
ShuffleAnswers = true, ShuffleAnswers = true,
OneQuestionAtATime = true, OneQuestionAtATime = true,
LocalAssignmentGroupName = "Assignments", LocalAssignmentGroupName = "Assignments",
Questions=[ Questions = [
new () { new()
{
Text = "test essay", Text = "test essay",
QuestionType = QuestionType.ESSAY, QuestionType = QuestionType.ESSAY,
Points = 1 Points = 1
@@ -196,8 +207,10 @@ public class FileStorageTests
[Test] [Test]
public async Task MarkdownStorage_FullyPopulated_DoesNotLoseData() public async Task MarkdownStorage_FullyPopulated_DoesNotLoseData()
{ {
LocalCourse testCourse = new() { LocalCourse testCourse = new()
Settings = new () { {
Settings = new()
{
AssignmentGroups = [], AssignmentGroups = [],
Name = "Test Course with lots of data", Name = "Test Course with lots of data",
DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday], DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday],
@@ -206,24 +219,27 @@ public class FileStorageTests
DefaultDueTime = new() { Hour = 1, Minute = 59 }, DefaultDueTime = new() { Hour = 1, Minute = 59 },
}, },
Modules = [ Modules = [
new() { new()
Name= "new test module", {
Name = "new test module",
Assignments = [ Assignments = [
new() { new()
Name="test assignment", {
Description ="here is the description", Name = "test assignment",
Description = "here is the description",
DueAt = new DateTime(), DueAt = new DateTime(),
LockAt = new DateTime(), LockAt = new DateTime(),
SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD], SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD],
LocalAssignmentGroupName = "Final Project", LocalAssignmentGroupName = "Final Project",
Rubric = [ Rubric = [
new() { Points = 4, Label="do task 1" }, new() { Points = 4, Label = "do task 1" },
new() { Points = 2, Label="do task 2" }, new() { Points = 2, Label = "do task 2" },
] ]
} }
], ],
Quizzes = [ Quizzes = [
new() { new()
{
Name = "Test Quiz", Name = "Test Quiz",
Description = "quiz description", Description = "quiz description",
LockAt = new DateTime(), LockAt = new DateTime(),
@@ -233,7 +249,8 @@ public class FileStorageTests
LocalAssignmentGroupName = "someId", LocalAssignmentGroupName = "someId",
AllowedAttempts = -1, AllowedAttempts = -1,
Questions = [ Questions = [
new() { new()
{
Text = "test short answer", Text = "test short answer",
QuestionType = QuestionType.SHORT_ANSWER, QuestionType = QuestionType.SHORT_ANSWER,
Points = 1 Points = 1
@@ -257,8 +274,10 @@ public class FileStorageTests
[Test] [Test]
public async Task MarkdownStorage_CanPersistPages() public async Task MarkdownStorage_CanPersistPages()
{ {
LocalCourse testCourse = new() { LocalCourse testCourse = new()
Settings = new () { {
Settings = new()
{
AssignmentGroups = [], AssignmentGroups = [],
Name = "Test Course with page", Name = "Test Course with page",
DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday], DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday],
@@ -267,10 +286,12 @@ public class FileStorageTests
DefaultDueTime = new() { Hour = 1, Minute = 59 }, DefaultDueTime = new() { Hour = 1, Minute = 59 },
}, },
Modules = [ Modules = [
new(){ new()
{
Name = "page test module", Name = "page test module",
Pages = [ Pages = [
new () { new()
{
Name = "test page persistence", Name = "test page persistence",
DueAt = new DateTime(), DueAt = new DateTime(),
Text = "this is some\n## markdown\n" Text = "this is some\n## markdown\n"

View File

@@ -80,4 +80,4 @@ Match the following terms & definitions
var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz); var quiz = LocalQuiz.ParseMarkdown(rawMarkdownQuiz);
quiz.Questions.First().Answers.First().Text.Should().Be("statement"); quiz.Questions.First().Answers.First().Text.Should().Be("statement");
} }
} }

View File

@@ -42,7 +42,7 @@ oneline question
"; ";
markdown.Should().Contain(expectedQuestionString); markdown.Should().Contain(expectedQuestionString);
} }
[Test] [Test]
public void CanParseQuestionWithMultipleAnswers() public void CanParseQuestionWithMultipleAnswers()
{ {
@@ -80,4 +80,4 @@ Which events are triggered when the user clicks on an input field?
firstQuestion.Answers.ElementAt(3).Text.Should().Be("submit"); firstQuestion.Answers.ElementAt(3).Text.Should().Be("submit");
} }
} }

View File

@@ -67,7 +67,7 @@ endline
*) true *) true
) false ) false
"; ";
var question = LocalQuizQuestion.ParseMarkdown(questionMarkdown, 0); var question = LocalQuizQuestion.ParseMarkdown(questionMarkdown, 0);
question.Answers.Count().Should().Be(2); question.Answers.Count().Should().Be(2);
} }
} }

View File

@@ -82,7 +82,7 @@ Which events are triggered when the user clicks on an input field?
short_answer"; short_answer";
questionMarkdown.Should().Contain(expectedMarkdown); questionMarkdown.Should().Contain(expectedMarkdown);
} }
[Test] [Test]
public void EssayQuestionToMarkdown_IsCorrect() public void EssayQuestionToMarkdown_IsCorrect()
{ {
@@ -111,4 +111,4 @@ Which events are triggered when the user clicks on an input field?
essay"; essay";
questionMarkdown.Should().Contain(expectedMarkdown); questionMarkdown.Should().Contain(expectedMarkdown);
} }
} }

View File

@@ -31,7 +31,7 @@ public class RubricMarkdownTests
rubric.ElementAt(1).Label.Should().Be("this is the other task"); rubric.ElementAt(1).Label.Should().Be("this is the other task");
rubric.ElementAt(1).Points.Should().Be(3); rubric.ElementAt(1).Points.Should().Be(3);
} }
[Test] [Test]
public void TestCanParseSinglePoint() public void TestCanParseSinglePoint()
{ {
@@ -68,4 +68,4 @@ public class RubricMarkdownTests
rubric.First().IsExtraCredit.Should().BeTrue(); rubric.First().IsExtraCredit.Should().BeTrue();
rubric.First().Label.Should().Be("(Extra Credit) this is the task"); rubric.First().Label.Should().Be("(Extra Credit) this is the task");
} }
} }

View File

@@ -59,7 +59,7 @@
// }; // };
// Mock<IWebRequestor> mockRequestor = getTermsMock(expectedTerms); // Mock<IWebRequestor> mockRequestor = getTermsMock(expectedTerms);
// var service = new CanvasService(mockRequestor.Object); // var service = new CanvasService(mockRequestor.Object);
// var queryDate = new DateTime(2022, 6, 1); // var queryDate = new DateTime(2022, 6, 1);
// var canvasTerms = await service.GetCurrentTermsFor(queryDate); // var canvasTerms = await service.GetCurrentTermsFor(queryDate);
@@ -83,4 +83,4 @@
// return mockRequestor; // return mockRequestor;
// } // }
// } // }

View File

@@ -1,3 +1,3 @@
global using NUnit.Framework; global using System.Text.Json;
global using FluentAssertions; global using FluentAssertions;
global using System.Text.Json; global using NUnit.Framework;

View File

@@ -1,14 +1,14 @@
public static class ConfigurationSetup public static class ConfigurationSetup
{ {
public static void Canvas(WebApplicationBuilder builder) public static void Canvas(WebApplicationBuilder builder)
{
var canvas_token = builder.Configuration["CANVAS_TOKEN"] ?? throw new Exception("CANVAS_TOKEN is null");
var canvas_url = builder.Configuration["CANVAS_URL"];
if (canvas_url == null)
{ {
var canvas_token = builder.Configuration["CANVAS_TOKEN"] ?? throw new Exception("CANVAS_TOKEN is null"); Console.WriteLine("CANVAS_URL is null, defaulting to https://snow.instructure.com");
builder.Configuration["CANVAS_URL"] = "https://snow.instructure.com";
var canvas_url = builder.Configuration["CANVAS_URL"];
if (canvas_url == null)
{
Console.WriteLine("CANVAS_URL is null, defaulting to https://snow.instructure.com");
builder.Configuration["CANVAS_URL"] = "https://snow.instructure.com";
}
} }
} }
}

View File

@@ -17,4 +17,4 @@ public class CustomConsoleExporter : BaseExporter<Activity>
} }
return ExportResult.Success; return ExportResult.Success;
} }
} }

View File

@@ -42,10 +42,11 @@ public class DroppableQuiz : ComponentBase
); );
var NewQuizList = currentModule.Quizzes var NewQuizList = currentModule.Quizzes
.Select(q => .Select(q =>
q.Name + q.Description != Quiz.Name + Quiz.Description q.Name + q.Description != Quiz.Name + Quiz.Description
? q : ? q :
q with { q with
{
DueAt = defaultDueTimeDate, DueAt = defaultDueTimeDate,
LockAt = q.LockAt > defaultDueTimeDate ? q.LockAt : defaultDueTimeDate LockAt = q.LockAt > defaultDueTimeDate ? q.LockAt : defaultDueTimeDate
} }

View File

@@ -7,20 +7,20 @@ namespace Management.Web.Pages;
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[IgnoreAntiforgeryToken] [IgnoreAntiforgeryToken]
public class ErrorModel : PageModel public class ErrorModel : PageModel
{ {
public string? RequestId { get; set; } public string? RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
private readonly ILogger<ErrorModel> _logger; private readonly ILogger<ErrorModel> _logger;
public ErrorModel(ILogger<ErrorModel> logger) public ErrorModel(ILogger<ErrorModel> logger)
{ {
_logger = logger; _logger = logger;
} }
public void OnGet() public void OnGet()
{ {
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
} }
} }

View File

@@ -1,24 +1,23 @@
global using System.Text.Json.Serialization;
global using System.Text.Json;
global using System.ComponentModel.DataAnnotations; global using System.ComponentModel.DataAnnotations;
global using Management.Services.Canvas; global using System.Text.Json;
global using Management.Services; global using System.Text.Json.Serialization;
global using CanvasModel.EnrollmentTerms;
global using CanvasModel.Courses;
global using CanvasModel; global using CanvasModel;
global using CanvasModel.Courses;
global using CanvasModel.EnrollmentTerms;
global using LocalModels; global using LocalModels;
global using Management.Planner; global using Management.Planner;
global using Management.Web.Shared.Components; global using Management.Services;
global using Management.Services.Canvas;
global using Management.Web.Shared; global using Management.Web.Shared;
global using Management.Web.Shared.Components;
using dotenv.net; using dotenv.net;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Hosting.Server.Features; using Microsoft.AspNetCore.Hosting.Server.Features;
using OpenTelemetry;
using OpenTelemetry.Logs; using OpenTelemetry.Logs;
using OpenTelemetry.Metrics; using OpenTelemetry.Metrics;
using OpenTelemetry.Resources; using OpenTelemetry.Resources;
using OpenTelemetry.Trace; using OpenTelemetry.Trace;
using OpenTelemetry;
DotEnv.Load(); DotEnv.Load();

View File

@@ -79,4 +79,4 @@ public class CalendarMonth
Year = year; Year = year;
Month = month; Month = month;
} }
} }

View File

@@ -1,14 +1,14 @@
using CanvasModel.EnrollmentTerms;
using CanvasModel.Courses;
using CanvasModel;
using LocalModels;
using CanvasModel.Assignments;
using CanvasModel.Modules;
using Management.Services.Canvas;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using CanvasModel.Quizzes; using CanvasModel;
using Management.Services; using CanvasModel.Assignments;
using CanvasModel.Courses;
using CanvasModel.EnrollmentTerms;
using CanvasModel.Modules;
using CanvasModel.Pages; using CanvasModel.Pages;
using CanvasModel.Quizzes;
using LocalModels;
using Management.Services;
using Management.Services.Canvas;
namespace Management.Planner; namespace Management.Planner;
@@ -181,13 +181,17 @@ public class CoursePlanner
CanvasAssignmentGroups = await canvas.AssignmentGroups.GetAll(canvasCourseId); CanvasAssignmentGroups = await canvas.AssignmentGroups.GetAll(canvasCourseId);
LocalCourse = LocalCourse with {Settings = LocalCourse.Settings with { LocalCourse = LocalCourse with
AssignmentGroups = LocalCourse.Settings.AssignmentGroups.Select(g => { {
var canvasGroup = CanvasAssignmentGroups.FirstOrDefault(c => c.Name == g.Name); Settings = LocalCourse.Settings with
return canvasGroup == null {
? g AssignmentGroups = LocalCourse.Settings.AssignmentGroups.Select(g =>
: g with {CanvasId = canvasGroup.Id}; {
}) var canvasGroup = CanvasAssignmentGroups.FirstOrDefault(c => c.Name == g.Name);
return canvasGroup == null
? g
: g with { CanvasId = canvasGroup.Id };
})
} }
}; };
} }

View File

@@ -112,7 +112,7 @@ public class PageEditorContext(
var canvasModule = getCurrentCanvasModule(Page, planner.LocalCourse); var canvasModule = getCurrentCanvasModule(Page, planner.LocalCourse);
if(canvasPage != null) if (canvasPage != null)
{ {
await canvas.CreatePageModuleItem( await canvas.CreatePageModuleItem(
(ulong)courseCanvasId, (ulong)courseCanvasId,

View File

@@ -37,4 +37,4 @@ public static partial class AssignmentGroupSyncronizationExtensions
return assignmentGroups; return assignmentGroups;
} }
} }

View File

@@ -140,13 +140,13 @@ public static partial class AssignmentSyncronizationExtensions
second: localAssignment.LockAt.Value.Second second: localAssignment.LockAt.Value.Second
) )
: null; : null;
if (canvasComparisonLockDate != localComparisonLockDate) if (canvasComparisonLockDate != localComparisonLockDate)
{ {
var printableLocal = localComparisonLockDate?.ToString() ?? "null"; var printableLocal = localComparisonLockDate?.ToString() ?? "null";
var printableCanvas = canvasComparisonLockDate?.ToString() ?? "null"; var printableCanvas = canvasComparisonLockDate?.ToString() ?? "null";
var reason = $"Lock dates different for assignment {localAssignment.Name}, local: {printableLocal}, in canvas {printableCanvas}"; var reason = $"Lock dates different for assignment {localAssignment.Name}, local: {printableLocal}, in canvas {printableCanvas}";
if (!quiet) if (!quiet)
{ {
// Console.WriteLine(JsonSerializer.Serialize(canvasAssignment)); // Console.WriteLine(JsonSerializer.Serialize(canvasAssignment));

View File

@@ -39,14 +39,15 @@ public static partial class ModuleSyncronizationExtensions
{ {
var canvasModuleItems = await canvas.Modules.GetModuleItems(canvasId, moduleCanvasId); var canvasModuleItems = await canvas.Modules.GetModuleItems(canvasId, moduleCanvasId);
var moduleItemsInCorrectOrder = canvasModuleItems var moduleItemsInCorrectOrder = canvasModuleItems
.OrderBy(canvasItem => { .OrderBy(canvasItem =>
{
if(canvasItem.Type == "Page") if (canvasItem.Type == "Page")
{ {
var localPage = localModule.Pages.FirstOrDefault(p => p.Name == canvasItem.Title); var localPage = localModule.Pages.FirstOrDefault(p => p.Name == canvasItem.Title);
Console.WriteLine(JsonSerializer.Serialize(localModule.Pages)); Console.WriteLine(JsonSerializer.Serialize(localModule.Pages));
if(localPage != null) if (localPage != null)
return localPage.DueAt.Date; return localPage.DueAt.Date;
} }
return canvasItem.ContentDetails?.DueAt?.Date; return canvasItem.ContentDetails?.DueAt?.Date;

View File

@@ -1,3 +1,3 @@
global using System.Text.Json.Serialization;
global using System.Text.Json; global using System.Text.Json;
global using Microsoft.Extensions.Logging; global using System.Text.Json.Serialization;
global using Microsoft.Extensions.Logging;

View File

@@ -70,7 +70,7 @@ public record CanvasAssignment
[property: JsonPropertyName("allowed_attempts")] [property: JsonPropertyName("allowed_attempts")]
int AllowedAttempts, int AllowedAttempts,
[property: JsonPropertyName("is_quiz_assignment")] [property: JsonPropertyName("is_quiz_assignment")]
bool IsQuizAssignment, bool IsQuizAssignment,
@@ -208,4 +208,4 @@ public record CanvasAssignment
[property: JsonPropertyName("anonymous_grading")] [property: JsonPropertyName("anonymous_grading")]
bool? AnonymousGrading = null bool? AnonymousGrading = null
); );

View File

@@ -19,4 +19,4 @@ public record CanvasAssignmentDate
[property: JsonPropertyName("lock_at")] [property: JsonPropertyName("lock_at")]
DateTime? LockAt = null DateTime? LockAt = null
); );

View File

@@ -26,4 +26,4 @@ public record CanvasAssignmentGroup
// [JsonPropertyName("rules")] // [JsonPropertyName("rules")]
// public object Rules { get; init; } // The specific type for 'Rules' is not detailed in the spec, so using object for now. // public object Rules { get; init; } // The specific type for 'Rules' is not detailed in the spec, so using object for now.
} }

View File

@@ -35,4 +35,4 @@ public record CanvasAssignmentOverride
[property: JsonPropertyName("lock_at")] [property: JsonPropertyName("lock_at")]
DateTime? LockAt = null DateTime? LockAt = null
); );

View File

@@ -10,4 +10,4 @@ public record CanvasExternalToolTagAttributes
[property: JsonPropertyName("new_tab")] [property: JsonPropertyName("new_tab")]
bool? NewTab = null bool? NewTab = null
); );

View File

@@ -16,4 +16,4 @@ public record CanvasLockInfo
[property: JsonPropertyName("manually_locked")] [property: JsonPropertyName("manually_locked")]
bool? ManuallyLocked = null bool? ManuallyLocked = null
); );

View File

@@ -7,4 +7,4 @@ public record CanvasNeedsGradingCount
[property: JsonPropertyName("needs_grading_count")] [property: JsonPropertyName("needs_grading_count")]
uint NeedsGradingCount uint NeedsGradingCount
); );

View File

@@ -34,4 +34,4 @@ public record CanvasRubric
// assessments // assessments
// associations // associations
} }

View File

@@ -32,4 +32,4 @@ public record CanvasRubricAssociation
[JsonPropertyName("hide_outcome-results")] [JsonPropertyName("hide_outcome-results")]
public bool HideOUtcomeResult { get; set; } public bool HideOUtcomeResult { get; set; }
} }

View File

@@ -27,4 +27,4 @@ public record CanvasRubricCriteria
[property: JsonPropertyName("ignore_for_scoring")] [property: JsonPropertyName("ignore_for_scoring")]
bool? IgnoreForScoring = null bool? IgnoreForScoring = null
); );

View File

@@ -13,4 +13,4 @@ public record CanvasRubricRating
[property: JsonPropertyName("long_description")] [property: JsonPropertyName("long_description")]
string LongDescription string LongDescription
); );

View File

@@ -25,4 +25,4 @@ public record CanvasTurnitinSettings
[property: JsonPropertyName("exclude_small_matches_value")] [property: JsonPropertyName("exclude_small_matches_value")]
uint? ExcludeSmallMatchesValue = null uint? ExcludeSmallMatchesValue = null
); );

View File

@@ -2,4 +2,4 @@ namespace CanvasModel.Courses;
public record CalendarLinkModel public record CalendarLinkModel
( (
[property: JsonPropertyName("ics")] string Ics [property: JsonPropertyName("ics")] string Ics
); );

View File

@@ -2,15 +2,15 @@
namespace CanvasModel.Courses; namespace CanvasModel.Courses;
public record CourseProgressModel public record CourseProgressModel
( (
[property: JsonPropertyName("requirement_count")] [property: JsonPropertyName("requirement_count")]
uint? RequirementCount = null, uint? RequirementCount = null,
[property: JsonPropertyName("requirement_completed_count")] [property: JsonPropertyName("requirement_completed_count")]
uint? RequirementCompletedCount = null, uint? RequirementCompletedCount = null,
[property: JsonPropertyName("next_requirement_url")] [property: JsonPropertyName("next_requirement_url")]
string? NextRequirementUrl = null, string? NextRequirementUrl = null,
[property: JsonPropertyName("completed_at")] [property: JsonPropertyName("completed_at")]
DateTime? CompletedAt = null DateTime? CompletedAt = null
); );

View File

@@ -108,4 +108,4 @@ public record DiscussionTopicModel
[property: JsonPropertyName("sort_by_rating")] [property: JsonPropertyName("sort_by_rating")]
bool? SortByRating = null bool? SortByRating = null
); );

View File

@@ -13,4 +13,4 @@ public record FileAttachmentModel
[property: JsonPropertyName("display_name")] [property: JsonPropertyName("display_name")]
string DisplayName string DisplayName
); );

View File

@@ -37,4 +37,4 @@ public record TopicEntryModel
[property: JsonPropertyName("has_more_replies")] [property: JsonPropertyName("has_more_replies")]
bool? HasMoreReplies = null bool? HasMoreReplies = null
); );

View File

@@ -25,4 +25,4 @@ public record TopicReplyModel
[property: JsonPropertyName("forced_read_state")] [property: JsonPropertyName("forced_read_state")]
bool? ForcedReadState = null bool? ForcedReadState = null
); );

View File

@@ -2,6 +2,6 @@ namespace CanvasModel.EnrollmentTerms;
public record RedundantEnrollmentTermsResponse public record RedundantEnrollmentTermsResponse
( (
[property: JsonPropertyName("enrollment_terms")] [property: JsonPropertyName("enrollment_terms")]
IEnumerable<EnrollmentTermModel> EnrollmentTerms IEnumerable<EnrollmentTermModel> EnrollmentTerms
); );

View File

@@ -132,4 +132,4 @@ public record EnrollmentModel
[property: JsonPropertyName("current_period_unposted_final_grade")] [property: JsonPropertyName("current_period_unposted_final_grade")]
string? CurrentPeriodUnpostedFinalGrade = null string? CurrentPeriodUnpostedFinalGrade = null
); );

View File

@@ -30,4 +30,4 @@ public record GradeModel
[property: JsonPropertyName("unposted_final_score")] [property: JsonPropertyName("unposted_final_score")]
string? UnpostedFinalScore = null string? UnpostedFinalScore = null
); );

View File

@@ -15,7 +15,7 @@ public record CanvasModule(
[property: JsonPropertyName("items")] [property: JsonPropertyName("items")]
IEnumerable<CanvasModuleItem>? Items, IEnumerable<CanvasModuleItem>? Items,
[property: JsonPropertyName("state")] string? State, // todo make sure this, [property: JsonPropertyName("state")] string? State, // todo make sure this,
// [OptIn] // [OptIn]
[property: JsonPropertyName("completed_at")] [property: JsonPropertyName("completed_at")]
DateTime? CompletedAt, DateTime? CompletedAt,
[property: JsonPropertyName("publish_final_grade")] bool? PublishFinalGrade, [property: JsonPropertyName("publish_final_grade")] bool? PublishFinalGrade,

View File

@@ -25,4 +25,4 @@ public record CanvasModuleItemContentDetails(
[property: JsonPropertyName("lock_at")] DateTime? LockAt, [property: JsonPropertyName("lock_at")] DateTime? LockAt,
[property: JsonPropertyName("points_possible")] double PointsPossible, [property: JsonPropertyName("points_possible")] double PointsPossible,
[property: JsonPropertyName("locked_for_user")] bool LockedForUser [property: JsonPropertyName("locked_for_user")] bool LockedForUser
); );

View File

@@ -1,6 +1,6 @@
namespace CanvasModel.Pages; namespace CanvasModel.Pages;
public record CanvasPage ( public record CanvasPage(
[property: JsonPropertyName("page_id")] ulong PageId, [property: JsonPropertyName("page_id")] ulong PageId,
[property: JsonPropertyName("url")] string Url, [property: JsonPropertyName("url")] string Url,
[property: JsonPropertyName("title")] string Title, [property: JsonPropertyName("title")] string Title,

View File

@@ -16,4 +16,4 @@ public record MediaCommentModel
[property: JsonPropertyName("url")] [property: JsonPropertyName("url")]
string Url string Url
); );

View File

@@ -26,4 +26,4 @@ public record SubmissionCommentModel
[property: JsonPropertyName("media_comment")] [property: JsonPropertyName("media_comment")]
MediaCommentModel? MediaComment = null MediaCommentModel? MediaComment = null
); );

View File

@@ -88,4 +88,4 @@ public record SubmissionModel
[property: JsonPropertyName("anonymous_id")] [property: JsonPropertyName("anonymous_id")]
string? AnonymousId = null string? AnonymousId = null
); );

View File

@@ -160,4 +160,4 @@ public record ActivityStreamObjectModel
[property: JsonPropertyName("assignment_request_id")] [property: JsonPropertyName("assignment_request_id")]
ulong? AssignmentRequestId = null ulong? AssignmentRequestId = null
); );

View File

@@ -12,4 +12,4 @@ public record ActivityStreamSummaryEntryModel
[property: JsonPropertyName("count")] [property: JsonPropertyName("count")]
uint Count uint Count
); );

View File

@@ -7,4 +7,4 @@ public record AnonymousUserDisplayModel
[property: JsonPropertyName("avatar_image_url")] [property: JsonPropertyName("avatar_image_url")]
string AvatarImageUrl string AvatarImageUrl
); );

View File

@@ -25,4 +25,4 @@ public record AvatarModel
[property: JsonPropertyName("size")] [property: JsonPropertyName("size")]
ulong Size ulong Size
); );

View File

@@ -10,4 +10,4 @@ public record CourseNicknameModel
[property: JsonPropertyName("nickname")] [property: JsonPropertyName("nickname")]
string Nickname string Nickname
); );

View File

@@ -16,4 +16,4 @@ public record PageViewLinksModel
[property: JsonPropertyName("account")] [property: JsonPropertyName("account")]
ulong? Account = null ulong? Account = null
); );

View File

@@ -50,4 +50,4 @@ public record PageViewModel
[property: JsonPropertyName("participated")] [property: JsonPropertyName("participated")]
bool? Participated = null bool? Participated = null
); );

View File

@@ -43,4 +43,4 @@ public record ProfileModel
[property: JsonPropertyName("locale")] [property: JsonPropertyName("locale")]
string Locale string Locale
); );

View File

@@ -22,4 +22,4 @@ public record UserDisplayModel
[property: JsonPropertyName("pronouns")] [property: JsonPropertyName("pronouns")]
string? Pronouns = null string? Pronouns = null
); );

View File

@@ -53,4 +53,4 @@ public record UserModel
[property: JsonPropertyName("last_login")] [property: JsonPropertyName("last_login")]
DateTime? LastLogin = null DateTime? LastLogin = null
); );

View File

@@ -7,4 +7,4 @@ public class RubricMarkdownParseException : Exception
public class AssignmentMarkdownParseException : Exception public class AssignmentMarkdownParseException : Exception
{ {
public AssignmentMarkdownParseException(string message) : base(message) { } public AssignmentMarkdownParseException(string message) : base(message) { }
} }

View File

@@ -26,5 +26,5 @@ public record AssignmentTemplate
// } // }
// return html; // return html;
// } // }
} }

View File

@@ -6,7 +6,7 @@ using YamlDotNet.Serialization;
namespace LocalModels; namespace LocalModels;
public record LocalAssignment: IModuleItem public record LocalAssignment : IModuleItem
{ {
private string _name = ""; private string _name = "";
public string Name public string Name

View File

@@ -6,4 +6,4 @@ public record LocalAssignmentGroup
public string Id { get; init; } = string.Empty; public string Id { get; init; } = string.Empty;
public required string Name { get; init; } public required string Name { get; init; }
public double Weight { get; init; } public double Weight { get; init; }
} }

View File

@@ -1,6 +1,6 @@
namespace LocalModels; namespace LocalModels;
public record LocalCoursePage: IModuleItem public record LocalCoursePage : IModuleItem
{ {
public required string Name { get; init; } public required string Name { get; init; }
public required string Text { get; set; } public required string Text { get; set; }

View File

@@ -3,7 +3,7 @@ using YamlDotNet.Serialization;
namespace LocalModels; namespace LocalModels;
public record LocalQuiz: IModuleItem public record LocalQuiz : IModuleItem
{ {
public required string Name { get; init; } public required string Name { get; init; }
@@ -171,7 +171,7 @@ Description: {Description}
public class QuizMarkdownParseException : Exception public class QuizMarkdownParseException : Exception
{ {
public QuizMarkdownParseException(string message): base(message) public QuizMarkdownParseException(string message) : base(message)
{ {
} }

View File

@@ -35,7 +35,7 @@ public record LocalQuizQuestion
return $"{questionTypeIndicator}{multilineMarkdownCompatibleText}"; return $"{questionTypeIndicator}{multilineMarkdownCompatibleText}";
} }
else if(QuestionType == "matching") else if (QuestionType == "matching")
{ {
return $"^ {answer.Text} - {answer.MatchedText}"; return $"^ {answer.Text} - {answer.MatchedText}";
} }

View File

@@ -63,10 +63,12 @@ public class CanvasModuleService
if (items == null) if (items == null)
throw new Exception($"Error getting canvas module items for {url}"); throw new Exception($"Error getting canvas module items for {url}");
return items.Select(i => return items.Select(i =>
i with { i with
{
ContentDetails = i.ContentDetails == null ContentDetails = i.ContentDetails == null
? null ? null
: i.ContentDetails with { : i.ContentDetails with
{
DueAt = i.ContentDetails.DueAt?.ToLocalTime(), DueAt = i.ContentDetails.DueAt?.ToLocalTime(),
LockAt = i.ContentDetails.LockAt?.ToLocalTime(), LockAt = i.ContentDetails.LockAt?.ToLocalTime(),
} }

View File

@@ -117,14 +117,16 @@ public class CanvasQuizService(
await Task.WhenAll(tasks); await Task.WhenAll(tasks);
} }
private async Task hackFixQuestionOrdering(ulong canvasCourseId, ulong canvasQuizId, IEnumerable<(CanvasQuizQuestion question, int position)> questionAndPositions ) private async Task hackFixQuestionOrdering(ulong canvasCourseId, ulong canvasQuizId, IEnumerable<(CanvasQuizQuestion question, int position)> questionAndPositions)
{ {
using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing question ordering with reorder"); using var activity = DiagnosticsConfig.Source.StartActivity("hack fixing question ordering with reorder");
activity?.SetCustomProperty("canvasQuizId", canvasQuizId); activity?.SetCustomProperty("canvasQuizId", canvasQuizId);
activity?.SetTag("canvas syncronization", true); activity?.SetTag("canvas syncronization", true);
var order = questionAndPositions.OrderBy(t => t.position).Select(tuple => { var order = questionAndPositions.OrderBy(t => t.position).Select(tuple =>
return new { {
return new
{
type = "question", type = "question",
id = tuple.question.Id.ToString(), id = tuple.question.Id.ToString(),
}; };
@@ -182,9 +184,10 @@ public class CanvasQuizService(
private static object[] getAnswers(LocalQuizQuestion q) private static object[] getAnswers(LocalQuizQuestion q)
{ {
if(q.QuestionType == QuestionType.MATCHING) if (q.QuestionType == QuestionType.MATCHING)
return q.Answers return q.Answers
.Select(a => new { .Select(a => new
{
answer_match_left = a.Text, answer_match_left = a.Text,
answer_match_right = a.MatchedText answer_match_right = a.MatchedText
}) })

View File

@@ -1,11 +1,11 @@
using Microsoft.Extensions.Logging;
using CanvasModel; using CanvasModel;
using CanvasModel.Assignments; using CanvasModel.Assignments;
using CanvasModel.Courses; using CanvasModel.Courses;
using CanvasModel.EnrollmentTerms; using CanvasModel.EnrollmentTerms;
using CanvasModel.Modules; using CanvasModel.Modules;
using RestSharp;
using CanvasModel.Pages; using CanvasModel.Pages;
using Microsoft.Extensions.Logging;
using RestSharp;
namespace Management.Services.Canvas; namespace Management.Services.Canvas;

View File

@@ -14,4 +14,4 @@ public class FileConfiguration(IConfiguration config)
return basePath; return basePath;
} }
} }

View File

@@ -47,7 +47,7 @@ public class FileStorageManager
public IEnumerable<string> GetEmptyDirectories() public IEnumerable<string> GetEmptyDirectories()
{ {
if(!Directory.Exists(_basePath)) if (!Directory.Exists(_basePath))
throw new DirectoryNotFoundException($"Cannot get empty directories, {_basePath} does not exist"); throw new DirectoryNotFoundException($"Cannot get empty directories, {_basePath} does not exist");
return Directory return Directory

View File

@@ -25,7 +25,7 @@ public class CourseMarkdownLoader
}) })
.Select(async d => await LoadCourseByPath(d)) .Select(async d => await LoadCourseByPath(d))
); );
return courses.OrderBy(c=>c.Settings.Name); return courses.OrderBy(c => c.Settings.Name);
} }
public async Task<LocalCourse> LoadCourseByPath(string courseDirectory) public async Task<LocalCourse> LoadCourseByPath(string courseDirectory)

View File

@@ -45,12 +45,15 @@ public class WebRequestor : IWebRequestor
var response = await client.ExecutePostAsync(request); var response = await client.ExecutePostAsync(request);
if (isRateLimited(response)) { if (isRateLimited(response))
if(retryCount < rateLimitRetryCount){ {
if (retryCount < rateLimitRetryCount)
{
logger.LogInformation($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying"); logger.LogInformation($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
Console.WriteLine($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying"); Console.WriteLine($"hit rate limit on post, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
Thread.Sleep(rateLimitSleepInterval); Thread.Sleep(rateLimitSleepInterval);
return await rateLimitAwarePostAsync(request, retryCount + 1);} return await rateLimitAwarePostAsync(request, retryCount + 1);
}
} }
if (!response.IsSuccessful) if (!response.IsSuccessful)
@@ -63,7 +66,7 @@ public class WebRequestor : IWebRequestor
private static bool isRateLimited(RestResponse response) private static bool isRateLimited(RestResponse response)
{ {
if(response.Content == null) if (response.Content == null)
return false; return false;
return response.StatusCode == HttpStatusCode.Forbidden return response.StatusCode == HttpStatusCode.Forbidden
&& response.Content.Contains("403 Forbidden (Rate Limit Exceeded)"); && response.Content.Contains("403 Forbidden (Rate Limit Exceeded)");
@@ -110,7 +113,7 @@ public class WebRequestor : IWebRequestor
{ {
if (e.StatusCode == HttpStatusCode.Forbidden) // && response.Content == "403 Forbidden (Rate Limit Exceeded)" if (e.StatusCode == HttpStatusCode.Forbidden) // && response.Content == "403 Forbidden (Rate Limit Exceeded)"
{ {
if(retryCount < rateLimitRetryCount) if (retryCount < rateLimitRetryCount)
{ {
logger.LogInformation($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying"); logger.LogInformation($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying");
Console.WriteLine($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying"); Console.WriteLine($"hit rate limit in delete, retry count is {retryCount} / {rateLimitRetryCount}, retrying");