mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
hooks
This commit is contained in:
@@ -7,8 +7,8 @@ public class AssignmentMarkdownTests
|
||||
{
|
||||
var assignment = new LocalAssignment()
|
||||
{
|
||||
Name="test assignment",
|
||||
Description ="here is the description",
|
||||
Name = "test assignment",
|
||||
Description = "here is the description",
|
||||
DueAt = new DateTime(),
|
||||
LockAt = new DateTime(),
|
||||
SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD],
|
||||
@@ -29,13 +29,13 @@ public class AssignmentMarkdownTests
|
||||
{
|
||||
var assignment = new LocalAssignment()
|
||||
{
|
||||
Name="test assignment",
|
||||
Description ="here is the description",
|
||||
Name = "test assignment",
|
||||
Description = "here is the description",
|
||||
DueAt = new DateTime(),
|
||||
LockAt = new DateTime(),
|
||||
SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD],
|
||||
LocalAssignmentGroupName = "Final Project",
|
||||
Rubric = new List<RubricItem>() {}
|
||||
Rubric = new List<RubricItem>() { }
|
||||
};
|
||||
|
||||
var assignmentMarkdown = assignment.ToMarkdown();
|
||||
@@ -48,8 +48,8 @@ public class AssignmentMarkdownTests
|
||||
{
|
||||
var assignment = new LocalAssignment()
|
||||
{
|
||||
Name="test assignment",
|
||||
Description ="here is the description",
|
||||
Name = "test assignment",
|
||||
Description = "here is the description",
|
||||
DueAt = new DateTime(),
|
||||
LockAt = new DateTime(),
|
||||
SubmissionTypes = [],
|
||||
@@ -71,8 +71,8 @@ public class AssignmentMarkdownTests
|
||||
{
|
||||
var assignment = new LocalAssignment()
|
||||
{
|
||||
Name="test assignment",
|
||||
Description ="here is the description",
|
||||
Name = "test assignment",
|
||||
Description = "here is the description",
|
||||
DueAt = new DateTime(),
|
||||
LockAt = null,
|
||||
SubmissionTypes = [],
|
||||
@@ -94,7 +94,7 @@ public class AssignmentMarkdownTests
|
||||
{
|
||||
var assignment = new LocalAssignment()
|
||||
{
|
||||
Name="test assignment",
|
||||
Name = "test assignment",
|
||||
Description = "",
|
||||
DueAt = new DateTime(),
|
||||
LockAt = new DateTime(),
|
||||
@@ -116,13 +116,14 @@ public class AssignmentMarkdownTests
|
||||
{
|
||||
var assignment = new LocalAssignment()
|
||||
{
|
||||
Name="test assignment",
|
||||
Name = "test assignment",
|
||||
Description = "test assignment\n---\nsomestuff",
|
||||
DueAt = new DateTime(),
|
||||
LockAt = new DateTime(),
|
||||
SubmissionTypes = [],
|
||||
LocalAssignmentGroupName = "Final Project",
|
||||
Rubric = new List<RubricItem>() {
|
||||
Rubric = new List<RubricItem>()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -72,8 +72,10 @@ public class FileStorageTests
|
||||
[Test]
|
||||
public async Task CourseSettings_CanBeSavedAndLoaded()
|
||||
{
|
||||
LocalCourse testCourse = new() {
|
||||
Settings = new() {
|
||||
LocalCourse testCourse = new()
|
||||
{
|
||||
Settings = new()
|
||||
{
|
||||
AssignmentGroups = [],
|
||||
Name = "Test Course with settings",
|
||||
DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday],
|
||||
@@ -96,13 +98,15 @@ public class FileStorageTests
|
||||
[Test]
|
||||
public async Task EmptyCourseModules_CanBeSavedAndLoaded()
|
||||
{
|
||||
LocalCourse testCourse = new() {
|
||||
LocalCourse testCourse = new()
|
||||
{
|
||||
Settings = new() { Name = "Test Course with modules" },
|
||||
Modules = [
|
||||
new() {
|
||||
Name="test module 1",
|
||||
Assignments= [],
|
||||
Quizzes=[]
|
||||
new()
|
||||
{
|
||||
Name = "test module 1",
|
||||
Assignments = [],
|
||||
Quizzes = []
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -118,26 +122,29 @@ public class FileStorageTests
|
||||
[Test]
|
||||
public async Task CourseModules_WithAssignments_CanBeSavedAndLoaded()
|
||||
{
|
||||
LocalCourse testCourse = new() {
|
||||
LocalCourse testCourse = new()
|
||||
{
|
||||
Settings = new() { Name = "Test Course with modules and assignments" },
|
||||
Modules = [
|
||||
new() {
|
||||
Name="test module 1 with assignments",
|
||||
Assignments=[
|
||||
new () {
|
||||
Name="test assignment",
|
||||
Description ="here is the description",
|
||||
new()
|
||||
{
|
||||
Name = "test module 1 with assignments",
|
||||
Assignments = [
|
||||
new()
|
||||
{
|
||||
Name = "test assignment",
|
||||
Description = "here is the description",
|
||||
DueAt = new DateTime(),
|
||||
LockAt = new DateTime(),
|
||||
SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD],
|
||||
LocalAssignmentGroupName = "Final Project",
|
||||
Rubric = [
|
||||
new() {Points = 4, Label="do task 1"},
|
||||
new() {Points = 2, Label="do task 2"},
|
||||
new() { Points = 4, Label = "do task 1" },
|
||||
new() { Points = 2, Label = "do task 2" },
|
||||
]
|
||||
}
|
||||
],
|
||||
Quizzes=[]
|
||||
Quizzes = []
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -156,14 +163,17 @@ public class FileStorageTests
|
||||
[Test]
|
||||
public async Task CourseModules_WithQuizzes_CanBeSavedAndLoaded()
|
||||
{
|
||||
LocalCourse testCourse = new() {
|
||||
LocalCourse testCourse = new()
|
||||
{
|
||||
Settings = new() { Name = "Test Course with modules and quiz" },
|
||||
Modules = [
|
||||
new() {
|
||||
Name="test module 1 with quiz",
|
||||
Assignments=[],
|
||||
Quizzes=[
|
||||
new() {
|
||||
new()
|
||||
{
|
||||
Name = "test module 1 with quiz",
|
||||
Assignments = [],
|
||||
Quizzes = [
|
||||
new()
|
||||
{
|
||||
Name = "Test Quiz",
|
||||
Description = "quiz description",
|
||||
LockAt = new DateTime(2022, 10, 3, 12, 5, 0),
|
||||
@@ -171,8 +181,9 @@ public class FileStorageTests
|
||||
ShuffleAnswers = true,
|
||||
OneQuestionAtATime = true,
|
||||
LocalAssignmentGroupName = "Assignments",
|
||||
Questions=[
|
||||
new () {
|
||||
Questions = [
|
||||
new()
|
||||
{
|
||||
Text = "test essay",
|
||||
QuestionType = QuestionType.ESSAY,
|
||||
Points = 1
|
||||
@@ -196,8 +207,10 @@ public class FileStorageTests
|
||||
[Test]
|
||||
public async Task MarkdownStorage_FullyPopulated_DoesNotLoseData()
|
||||
{
|
||||
LocalCourse testCourse = new() {
|
||||
Settings = new () {
|
||||
LocalCourse testCourse = new()
|
||||
{
|
||||
Settings = new()
|
||||
{
|
||||
AssignmentGroups = [],
|
||||
Name = "Test Course with lots of data",
|
||||
DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday],
|
||||
@@ -206,24 +219,27 @@ public class FileStorageTests
|
||||
DefaultDueTime = new() { Hour = 1, Minute = 59 },
|
||||
},
|
||||
Modules = [
|
||||
new() {
|
||||
Name= "new test module",
|
||||
new()
|
||||
{
|
||||
Name = "new test module",
|
||||
Assignments = [
|
||||
new() {
|
||||
Name="test assignment",
|
||||
Description ="here is the description",
|
||||
new()
|
||||
{
|
||||
Name = "test assignment",
|
||||
Description = "here is the description",
|
||||
DueAt = new DateTime(),
|
||||
LockAt = new DateTime(),
|
||||
SubmissionTypes = [AssignmentSubmissionType.ONLINE_UPLOAD],
|
||||
LocalAssignmentGroupName = "Final Project",
|
||||
Rubric = [
|
||||
new() { Points = 4, Label="do task 1" },
|
||||
new() { Points = 2, Label="do task 2" },
|
||||
new() { Points = 4, Label = "do task 1" },
|
||||
new() { Points = 2, Label = "do task 2" },
|
||||
]
|
||||
}
|
||||
],
|
||||
Quizzes = [
|
||||
new() {
|
||||
new()
|
||||
{
|
||||
Name = "Test Quiz",
|
||||
Description = "quiz description",
|
||||
LockAt = new DateTime(),
|
||||
@@ -233,7 +249,8 @@ public class FileStorageTests
|
||||
LocalAssignmentGroupName = "someId",
|
||||
AllowedAttempts = -1,
|
||||
Questions = [
|
||||
new() {
|
||||
new()
|
||||
{
|
||||
Text = "test short answer",
|
||||
QuestionType = QuestionType.SHORT_ANSWER,
|
||||
Points = 1
|
||||
@@ -257,8 +274,10 @@ public class FileStorageTests
|
||||
[Test]
|
||||
public async Task MarkdownStorage_CanPersistPages()
|
||||
{
|
||||
LocalCourse testCourse = new() {
|
||||
Settings = new () {
|
||||
LocalCourse testCourse = new()
|
||||
{
|
||||
Settings = new()
|
||||
{
|
||||
AssignmentGroups = [],
|
||||
Name = "Test Course with page",
|
||||
DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday],
|
||||
@@ -267,10 +286,12 @@ public class FileStorageTests
|
||||
DefaultDueTime = new() { Hour = 1, Minute = 59 },
|
||||
},
|
||||
Modules = [
|
||||
new(){
|
||||
new()
|
||||
{
|
||||
Name = "page test module",
|
||||
Pages = [
|
||||
new () {
|
||||
new()
|
||||
{
|
||||
Name = "test page persistence",
|
||||
DueAt = new DateTime(),
|
||||
Text = "this is some\n## markdown\n"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
global using NUnit.Framework;
|
||||
global using FluentAssertions;
|
||||
global using System.Text.Json;
|
||||
global using FluentAssertions;
|
||||
global using NUnit.Framework;
|
||||
|
||||
@@ -45,7 +45,8 @@ public class DroppableQuiz : ComponentBase
|
||||
.Select(q =>
|
||||
q.Name + q.Description != Quiz.Name + Quiz.Description
|
||||
? q :
|
||||
q with {
|
||||
q with
|
||||
{
|
||||
DueAt = defaultDueTimeDate,
|
||||
LockAt = q.LockAt > defaultDueTimeDate ? q.LockAt : defaultDueTimeDate
|
||||
}
|
||||
|
||||
@@ -1,24 +1,23 @@
|
||||
global using System.Text.Json.Serialization;
|
||||
global using System.Text.Json;
|
||||
global using System.ComponentModel.DataAnnotations;
|
||||
global using Management.Services.Canvas;
|
||||
global using Management.Services;
|
||||
global using CanvasModel.EnrollmentTerms;
|
||||
global using CanvasModel.Courses;
|
||||
global using System.Text.Json;
|
||||
global using System.Text.Json.Serialization;
|
||||
global using CanvasModel;
|
||||
global using CanvasModel.Courses;
|
||||
global using CanvasModel.EnrollmentTerms;
|
||||
global using LocalModels;
|
||||
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.Components;
|
||||
using dotenv.net;
|
||||
using Microsoft.AspNetCore.Hosting.Server;
|
||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||
using OpenTelemetry;
|
||||
using OpenTelemetry.Logs;
|
||||
using OpenTelemetry.Metrics;
|
||||
using OpenTelemetry.Resources;
|
||||
using OpenTelemetry.Trace;
|
||||
using OpenTelemetry;
|
||||
|
||||
DotEnv.Load();
|
||||
|
||||
|
||||
@@ -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 CanvasModel.Quizzes;
|
||||
using Management.Services;
|
||||
using CanvasModel;
|
||||
using CanvasModel.Assignments;
|
||||
using CanvasModel.Courses;
|
||||
using CanvasModel.EnrollmentTerms;
|
||||
using CanvasModel.Modules;
|
||||
using CanvasModel.Pages;
|
||||
using CanvasModel.Quizzes;
|
||||
using LocalModels;
|
||||
using Management.Services;
|
||||
using Management.Services.Canvas;
|
||||
|
||||
namespace Management.Planner;
|
||||
|
||||
@@ -181,12 +181,16 @@ public class CoursePlanner
|
||||
|
||||
CanvasAssignmentGroups = await canvas.AssignmentGroups.GetAll(canvasCourseId);
|
||||
|
||||
LocalCourse = LocalCourse with {Settings = LocalCourse.Settings with {
|
||||
AssignmentGroups = LocalCourse.Settings.AssignmentGroups.Select(g => {
|
||||
LocalCourse = LocalCourse with
|
||||
{
|
||||
Settings = LocalCourse.Settings with
|
||||
{
|
||||
AssignmentGroups = LocalCourse.Settings.AssignmentGroups.Select(g =>
|
||||
{
|
||||
var canvasGroup = CanvasAssignmentGroups.FirstOrDefault(c => c.Name == g.Name);
|
||||
return canvasGroup == null
|
||||
? g
|
||||
: g with {CanvasId = canvasGroup.Id};
|
||||
: g with { CanvasId = canvasGroup.Id };
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
@@ -112,7 +112,7 @@ public class PageEditorContext(
|
||||
|
||||
var canvasModule = getCurrentCanvasModule(Page, planner.LocalCourse);
|
||||
|
||||
if(canvasPage != null)
|
||||
if (canvasPage != null)
|
||||
{
|
||||
await canvas.CreatePageModuleItem(
|
||||
(ulong)courseCanvasId,
|
||||
|
||||
@@ -39,14 +39,15 @@ public static partial class ModuleSyncronizationExtensions
|
||||
{
|
||||
var canvasModuleItems = await canvas.Modules.GetModuleItems(canvasId, moduleCanvasId);
|
||||
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);
|
||||
Console.WriteLine(JsonSerializer.Serialize(localModule.Pages));
|
||||
|
||||
if(localPage != null)
|
||||
if (localPage != null)
|
||||
return localPage.DueAt.Date;
|
||||
}
|
||||
return canvasItem.ContentDetails?.DueAt?.Date;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
global using System.Text.Json.Serialization;
|
||||
global using System.Text.Json;
|
||||
global using System.Text.Json.Serialization;
|
||||
global using Microsoft.Extensions.Logging;
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
namespace CanvasModel.Pages;
|
||||
public record CanvasPage (
|
||||
public record CanvasPage(
|
||||
[property: JsonPropertyName("page_id")] ulong PageId,
|
||||
[property: JsonPropertyName("url")] string Url,
|
||||
[property: JsonPropertyName("title")] string Title,
|
||||
|
||||
@@ -6,7 +6,7 @@ using YamlDotNet.Serialization;
|
||||
|
||||
namespace LocalModels;
|
||||
|
||||
public record LocalAssignment: IModuleItem
|
||||
public record LocalAssignment : IModuleItem
|
||||
{
|
||||
private string _name = "";
|
||||
public string Name
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace LocalModels;
|
||||
|
||||
public record LocalCoursePage: IModuleItem
|
||||
public record LocalCoursePage : IModuleItem
|
||||
{
|
||||
public required string Name { get; init; }
|
||||
public required string Text { get; set; }
|
||||
|
||||
@@ -3,7 +3,7 @@ using YamlDotNet.Serialization;
|
||||
|
||||
namespace LocalModels;
|
||||
|
||||
public record LocalQuiz: IModuleItem
|
||||
public record LocalQuiz : IModuleItem
|
||||
{
|
||||
|
||||
public required string Name { get; init; }
|
||||
@@ -171,7 +171,7 @@ Description: {Description}
|
||||
|
||||
public class QuizMarkdownParseException : Exception
|
||||
{
|
||||
public QuizMarkdownParseException(string message): base(message)
|
||||
public QuizMarkdownParseException(string message) : base(message)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public record LocalQuizQuestion
|
||||
|
||||
return $"{questionTypeIndicator}{multilineMarkdownCompatibleText}";
|
||||
}
|
||||
else if(QuestionType == "matching")
|
||||
else if (QuestionType == "matching")
|
||||
{
|
||||
return $"^ {answer.Text} - {answer.MatchedText}";
|
||||
}
|
||||
|
||||
@@ -63,10 +63,12 @@ public class CanvasModuleService
|
||||
if (items == null)
|
||||
throw new Exception($"Error getting canvas module items for {url}");
|
||||
return items.Select(i =>
|
||||
i with {
|
||||
i with
|
||||
{
|
||||
ContentDetails = i.ContentDetails == null
|
||||
? null
|
||||
: i.ContentDetails with {
|
||||
: i.ContentDetails with
|
||||
{
|
||||
DueAt = i.ContentDetails.DueAt?.ToLocalTime(),
|
||||
LockAt = i.ContentDetails.LockAt?.ToLocalTime(),
|
||||
}
|
||||
|
||||
@@ -117,14 +117,16 @@ public class CanvasQuizService(
|
||||
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");
|
||||
activity?.SetCustomProperty("canvasQuizId", canvasQuizId);
|
||||
activity?.SetTag("canvas syncronization", true);
|
||||
|
||||
var order = questionAndPositions.OrderBy(t => t.position).Select(tuple => {
|
||||
return new {
|
||||
var order = questionAndPositions.OrderBy(t => t.position).Select(tuple =>
|
||||
{
|
||||
return new
|
||||
{
|
||||
type = "question",
|
||||
id = tuple.question.Id.ToString(),
|
||||
};
|
||||
@@ -182,9 +184,10 @@ public class CanvasQuizService(
|
||||
|
||||
private static object[] getAnswers(LocalQuizQuestion q)
|
||||
{
|
||||
if(q.QuestionType == QuestionType.MATCHING)
|
||||
if (q.QuestionType == QuestionType.MATCHING)
|
||||
return q.Answers
|
||||
.Select(a => new {
|
||||
.Select(a => new
|
||||
{
|
||||
answer_match_left = a.Text,
|
||||
answer_match_right = a.MatchedText
|
||||
})
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using CanvasModel;
|
||||
using CanvasModel.Assignments;
|
||||
using CanvasModel.Courses;
|
||||
using CanvasModel.EnrollmentTerms;
|
||||
using CanvasModel.Modules;
|
||||
using RestSharp;
|
||||
using CanvasModel.Pages;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RestSharp;
|
||||
|
||||
namespace Management.Services.Canvas;
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ public class FileStorageManager
|
||||
|
||||
public IEnumerable<string> GetEmptyDirectories()
|
||||
{
|
||||
if(!Directory.Exists(_basePath))
|
||||
if (!Directory.Exists(_basePath))
|
||||
throw new DirectoryNotFoundException($"Cannot get empty directories, {_basePath} does not exist");
|
||||
|
||||
return Directory
|
||||
|
||||
@@ -25,7 +25,7 @@ public class CourseMarkdownLoader
|
||||
})
|
||||
.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)
|
||||
|
||||
@@ -45,12 +45,15 @@ public class WebRequestor : IWebRequestor
|
||||
|
||||
var response = await client.ExecutePostAsync(request);
|
||||
|
||||
if (isRateLimited(response)) {
|
||||
if(retryCount < rateLimitRetryCount){
|
||||
if (isRateLimited(response))
|
||||
{
|
||||
if (retryCount < rateLimitRetryCount)
|
||||
{
|
||||
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");
|
||||
Thread.Sleep(rateLimitSleepInterval);
|
||||
return await rateLimitAwarePostAsync(request, retryCount + 1);}
|
||||
return await rateLimitAwarePostAsync(request, retryCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.IsSuccessful)
|
||||
@@ -63,7 +66,7 @@ public class WebRequestor : IWebRequestor
|
||||
|
||||
private static bool isRateLimited(RestResponse response)
|
||||
{
|
||||
if(response.Content == null)
|
||||
if (response.Content == null)
|
||||
return false;
|
||||
return response.StatusCode == HttpStatusCode.Forbidden
|
||||
&& 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(retryCount < rateLimitRetryCount)
|
||||
if (retryCount < rateLimitRetryCount)
|
||||
{
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user