mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
got basic question and answer creation from canvas
This commit is contained in:
@@ -6,6 +6,7 @@ using CanvasModel.Assignments;
|
||||
using CanvasModel.Modules;
|
||||
using Management.Services.Canvas;
|
||||
using System.Text.RegularExpressions;
|
||||
using CanvasModel.Quizzes;
|
||||
|
||||
namespace Management.Planner;
|
||||
|
||||
@@ -54,7 +55,7 @@ public class CoursePlanner
|
||||
private void saveCourseToFile(LocalCourse courseAsOfDebounce)
|
||||
{
|
||||
_debounceTimer?.Dispose();
|
||||
|
||||
|
||||
// ignore initial load of course
|
||||
if (LocalCourse == null)
|
||||
{
|
||||
@@ -71,6 +72,7 @@ public class CoursePlanner
|
||||
public event Action? StateHasChanged;
|
||||
|
||||
public IEnumerable<CanvasAssignment>? CanvasAssignments { get; internal set; }
|
||||
public IEnumerable<CanvasQuiz>? CanvasQuizzes { get; internal set; }
|
||||
public IEnumerable<CanvasModule>? CanvasModules { get; internal set; }
|
||||
public Dictionary<ulong, IEnumerable<CanvasModuleItem>>? CanvasModulesItems { get; internal set; }
|
||||
|
||||
@@ -87,13 +89,14 @@ public class CoursePlanner
|
||||
LocalCourse?.CanvasId ?? throw new Exception("no canvas id found for selected course");
|
||||
|
||||
var assignmentsTask = canvas.Assignments.GetAll(canvasId);
|
||||
var quizzesTask = canvas.Quizzes.GetAll(canvasId);
|
||||
var modulesTask = canvas.GetModules(canvasId);
|
||||
|
||||
CanvasAssignments = await assignmentsTask;
|
||||
CanvasQuizzes = await quizzesTask;
|
||||
CanvasModules = await modulesTask;
|
||||
|
||||
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
||||
// Console.WriteLine(JsonSerializer.Serialize(CanvasModulesItems));
|
||||
|
||||
LoadingCanvasData = false;
|
||||
StateHasChanged?.Invoke();
|
||||
@@ -107,6 +110,7 @@ public class CoursePlanner
|
||||
|| LocalCourse.CanvasId == null
|
||||
|| CanvasAssignments == null
|
||||
|| CanvasModules == null
|
||||
|| CanvasQuizzes == null
|
||||
)
|
||||
return;
|
||||
|
||||
@@ -138,9 +142,9 @@ public class CoursePlanner
|
||||
LocalCourse = await LocalCourse.SyncAssignmentsWithCanvas(canvasId, CanvasAssignments, canvas);
|
||||
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
|
||||
|
||||
|
||||
LocalCourse = await LocalCourse.SyncQuizzesWithCanvas(canvasId, CanvasQuizzes, canvas);
|
||||
|
||||
await syncModuleItemsWithCanvas(canvasId);
|
||||
await LocalCourse.SyncModuleItemsWithCanvas(canvasId, CanvasModulesItems, canvas);
|
||||
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
||||
|
||||
LoadingCanvasData = false;
|
||||
@@ -148,94 +152,6 @@ public class CoursePlanner
|
||||
Console.WriteLine("done syncing with canvas\n");
|
||||
}
|
||||
|
||||
private async Task syncModuleItemsWithCanvas(ulong canvasId)
|
||||
{
|
||||
if (LocalCourse == null)
|
||||
throw new Exception("cannot sync modules without localcourse selected");
|
||||
if (CanvasModulesItems == null)
|
||||
throw new Exception("cannot sync modules with canvas if they are not loaded in the variable");
|
||||
|
||||
foreach (var localModule in LocalCourse.Modules)
|
||||
{
|
||||
var moduleCanvasId =
|
||||
localModule.CanvasId
|
||||
?? throw new Exception("cannot sync canvas modules items if module not synced with canvas");
|
||||
|
||||
bool anyUpdated = await ensureAllItemsCreated(canvasId, localModule, moduleCanvasId);
|
||||
|
||||
var canvasModuleItems = anyUpdated
|
||||
? await canvas.GetModuleItems(canvasId, moduleCanvasId)
|
||||
: CanvasModulesItems[moduleCanvasId];
|
||||
|
||||
await sortModuleItems(canvasId, localModule, moduleCanvasId, canvasModuleItems);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task sortModuleItems(
|
||||
ulong canvasId,
|
||||
LocalModule localModule,
|
||||
ulong moduleCanvasId,
|
||||
IEnumerable<CanvasModuleItem> canvasModuleItems
|
||||
)
|
||||
{
|
||||
var localItemsWithCorrectOrder = localModule.Assignments
|
||||
.OrderBy(a => a.DueAt)
|
||||
.Select((a, i) => (Assignment: a, Position: i + 1));
|
||||
|
||||
var canvasContentIdsByCurrentPosition =
|
||||
canvasModuleItems.ToDictionary(item => item.Position, item => item.ContentId)
|
||||
?? new Dictionary<int, ulong?>();
|
||||
|
||||
foreach (var (localAssignment, position) in localItemsWithCorrectOrder)
|
||||
{
|
||||
var itemIsInCorrectOrder =
|
||||
canvasContentIdsByCurrentPosition.ContainsKey(position)
|
||||
&& canvasContentIdsByCurrentPosition[position] == localAssignment.CanvasId;
|
||||
|
||||
var currentCanvasItem = canvasModuleItems.First(i => i.ContentId == localAssignment.CanvasId);
|
||||
if (!itemIsInCorrectOrder)
|
||||
{
|
||||
await canvas.UpdateModuleItem(
|
||||
canvasId,
|
||||
moduleCanvasId,
|
||||
currentCanvasItem with
|
||||
{
|
||||
Position = position
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> ensureAllItemsCreated(
|
||||
ulong canvasId,
|
||||
LocalModule localModule,
|
||||
ulong moduleCanvasId
|
||||
)
|
||||
{
|
||||
var anyUpdated = false;
|
||||
foreach (var localAssignment in localModule.Assignments)
|
||||
{
|
||||
var canvasModuleItemContentIds = CanvasModulesItems[moduleCanvasId].Select(i => i.ContentId);
|
||||
if (!canvasModuleItemContentIds.Contains(localAssignment.CanvasId))
|
||||
{
|
||||
var canvasAssignmentId =
|
||||
localAssignment.CanvasId
|
||||
?? throw new Exception("cannot create module item if assignment does not have canvas id");
|
||||
await canvas.CreateModuleItem(
|
||||
canvasId,
|
||||
moduleCanvasId,
|
||||
localAssignment.Name,
|
||||
"Assignment",
|
||||
canvasAssignmentId
|
||||
);
|
||||
anyUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return anyUpdated;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
LocalCourse = null;
|
||||
|
||||
@@ -9,10 +9,9 @@ namespace Management.Planner;
|
||||
|
||||
public static partial class AssignmentSyncronizationExtensions
|
||||
{
|
||||
|
||||
internal static async Task<LocalAssignment> SyncAssignmentToCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
ulong canvasCourseId,
|
||||
LocalAssignment localAssignment,
|
||||
IEnumerable<CanvasAssignment> canvasAssignments,
|
||||
CanvasService canvas
|
||||
@@ -25,23 +24,41 @@ public static partial class AssignmentSyncronizationExtensions
|
||||
localCourse.AssignmentTemplates
|
||||
);
|
||||
|
||||
if (canvasAssignment != null)
|
||||
{
|
||||
var assignmentNeedsUpdates = localAssignment.NeedsUpdates(
|
||||
return canvasAssignment != null
|
||||
? await updateAssignmentIfNeeded(
|
||||
localCourse,
|
||||
canvasCourseId,
|
||||
localAssignment,
|
||||
canvasAssignments,
|
||||
localCourse.AssignmentTemplates,
|
||||
quiet: false
|
||||
);
|
||||
if (assignmentNeedsUpdates)
|
||||
{
|
||||
await canvas.Assignments.Update(courseId: canvasId, localAssignment, localHtmlDescription);
|
||||
}
|
||||
return localAssignment;
|
||||
}
|
||||
else
|
||||
canvas,
|
||||
localHtmlDescription
|
||||
)
|
||||
: await canvas.Assignments.Create(canvasCourseId, localAssignment, localHtmlDescription);
|
||||
}
|
||||
|
||||
private static async Task<LocalAssignment> updateAssignmentIfNeeded(
|
||||
LocalCourse localCourse,
|
||||
ulong canvasCourseId,
|
||||
LocalAssignment localAssignment,
|
||||
IEnumerable<CanvasAssignment> canvasAssignments,
|
||||
CanvasService canvas,
|
||||
string localHtmlDescription
|
||||
)
|
||||
{
|
||||
var assignmentNeedsUpdates = localAssignment.NeedsUpdates(
|
||||
canvasAssignments,
|
||||
localCourse.AssignmentTemplates,
|
||||
quiet: false
|
||||
);
|
||||
if (assignmentNeedsUpdates)
|
||||
{
|
||||
return await canvas.Assignments.Create(canvasId, localAssignment, localHtmlDescription);
|
||||
await canvas.Assignments.Update(
|
||||
courseId: canvasCourseId,
|
||||
localAssignment,
|
||||
localHtmlDescription
|
||||
);
|
||||
}
|
||||
return localAssignment;
|
||||
}
|
||||
|
||||
public static bool NeedsUpdates(
|
||||
@@ -168,7 +185,7 @@ public static partial class AssignmentSyncronizationExtensions
|
||||
|
||||
internal static async Task<LocalCourse> SyncAssignmentsWithCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
ulong canvasCourseId,
|
||||
IEnumerable<CanvasAssignment> canvasAssignments,
|
||||
CanvasService canvas
|
||||
)
|
||||
@@ -176,7 +193,7 @@ public static partial class AssignmentSyncronizationExtensions
|
||||
var moduleTasks = localCourse.Modules.Select(async m =>
|
||||
{
|
||||
var assignmentTasks = m.Assignments.Select(
|
||||
(a) => localCourse.SyncAssignmentToCanvas(canvasId, a, canvasAssignments, canvas)
|
||||
(a) => localCourse.SyncAssignmentToCanvas(canvasCourseId, a, canvasAssignments, canvas)
|
||||
);
|
||||
var assignments = await Task.WhenAll(assignmentTasks);
|
||||
return m with { Assignments = assignments };
|
||||
|
||||
@@ -69,4 +69,99 @@ public static partial class ModuleSyncronizationExtensions
|
||||
})
|
||||
};
|
||||
}
|
||||
internal static async Task SortModuleItems(
|
||||
this LocalModule localModule,
|
||||
ulong canvasId,
|
||||
ulong moduleCanvasId,
|
||||
IEnumerable<CanvasModuleItem> canvasModuleItems,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
var localItemsWithCorrectOrder = localModule.Assignments
|
||||
.OrderBy(a => a.DueAt)
|
||||
.Select((a, i) => (Assignment: a, Position: i + 1));
|
||||
|
||||
var canvasContentIdsByCurrentPosition =
|
||||
canvasModuleItems.ToDictionary(item => item.Position, item => item.ContentId)
|
||||
?? new Dictionary<int, ulong?>();
|
||||
|
||||
foreach (var (localAssignment, position) in localItemsWithCorrectOrder)
|
||||
{
|
||||
var itemIsInCorrectOrder =
|
||||
canvasContentIdsByCurrentPosition.ContainsKey(position)
|
||||
&& canvasContentIdsByCurrentPosition[position] == localAssignment.CanvasId;
|
||||
|
||||
var currentCanvasItem = canvasModuleItems.First(i => i.ContentId == localAssignment.CanvasId);
|
||||
if (!itemIsInCorrectOrder)
|
||||
{
|
||||
await canvas.UpdateModuleItem(
|
||||
canvasId,
|
||||
moduleCanvasId,
|
||||
currentCanvasItem with
|
||||
{
|
||||
Position = position
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<bool> EnsureAllModulesItemsCreated(
|
||||
this LocalModule localModule,
|
||||
ulong canvasId,
|
||||
ulong moduleCanvasId,
|
||||
Dictionary<ulong, IEnumerable<CanvasModuleItem>> canvasModulesItems,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
var anyUpdated = false;
|
||||
foreach (var localAssignment in localModule.Assignments)
|
||||
{
|
||||
var canvasModuleItemContentIds = canvasModulesItems[moduleCanvasId].Select(i => i.ContentId);
|
||||
if (!canvasModuleItemContentIds.Contains(localAssignment.CanvasId))
|
||||
{
|
||||
var canvasAssignmentId =
|
||||
localAssignment.CanvasId
|
||||
?? throw new Exception("cannot create module item if assignment does not have canvas id");
|
||||
await canvas.CreateModuleItem(
|
||||
canvasId,
|
||||
moduleCanvasId,
|
||||
localAssignment.Name,
|
||||
"Assignment",
|
||||
canvasAssignmentId
|
||||
);
|
||||
anyUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
return anyUpdated;
|
||||
}
|
||||
|
||||
internal static async Task SyncModuleItemsWithCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
Dictionary<ulong, IEnumerable<CanvasModuleItem>> canvasModulesItems,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
foreach (var localModule in localCourse.Modules)
|
||||
{
|
||||
var moduleCanvasId =
|
||||
localModule.CanvasId
|
||||
?? throw new Exception("cannot sync canvas modules items if module not synced with canvas");
|
||||
|
||||
bool anyUpdated = await localModule.EnsureAllModulesItemsCreated(
|
||||
canvasId,
|
||||
moduleCanvasId,
|
||||
canvasModulesItems,
|
||||
canvas
|
||||
);
|
||||
|
||||
var canvasModuleItems = anyUpdated
|
||||
? await canvas.GetModuleItems(canvasId, moduleCanvasId)
|
||||
: canvasModulesItems[moduleCanvasId];
|
||||
|
||||
await localModule.SortModuleItems(canvasId, moduleCanvasId, canvasModuleItems, canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,9 @@ namespace Management.Planner;
|
||||
|
||||
public static partial class QuizSyncronizationExtensions
|
||||
{
|
||||
internal static async Task<LocalQuiz> SyncQuizToCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
LocalQuiz localQuiz,
|
||||
IEnumerable<CanvasQuiz> canvasQuizzes,
|
||||
CanvasService canvas
|
||||
)
|
||||
public static bool QuizIsCreated(this LocalQuiz localQuiz, IEnumerable<CanvasQuiz> canvasQuizzes)
|
||||
{
|
||||
// TODO actually sync
|
||||
return localQuiz;
|
||||
return canvasQuizzes.Any(q => q.Id == localQuiz.CanvasId);
|
||||
}
|
||||
|
||||
internal static async Task<LocalCourse> SyncQuizzesWithCanvas(
|
||||
@@ -38,6 +31,28 @@ public static partial class QuizSyncronizationExtensions
|
||||
});
|
||||
|
||||
var modules = await Task.WhenAll(moduleTasks);
|
||||
return localCourse;
|
||||
return localCourse with { Modules = modules };
|
||||
}
|
||||
|
||||
internal static async Task<LocalQuiz> SyncQuizToCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasCourseId,
|
||||
LocalQuiz localQuiz,
|
||||
IEnumerable<CanvasQuiz> canvasQuizzes,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
var isCreated = localQuiz.QuizIsCreated(canvasQuizzes);
|
||||
|
||||
if (isCreated)
|
||||
{
|
||||
// TODO write update
|
||||
}
|
||||
else
|
||||
{
|
||||
return await canvas.Quizzes.Create(canvasCourseId, localQuiz);
|
||||
}
|
||||
|
||||
return localQuiz;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,45 +2,122 @@ using CanvasModel.Assignments;
|
||||
|
||||
namespace CanvasModel.Quizzes;
|
||||
|
||||
public record CanvasQuiz(
|
||||
[property: JsonPropertyName("id")] ulong Id,
|
||||
[property: JsonPropertyName("title")] string Title,
|
||||
[property: JsonPropertyName("html_url")] string HtmlUrl,
|
||||
[property: JsonPropertyName("mobile_url")] string MobileUrl,
|
||||
[property: JsonPropertyName("preview_url")] string PreviewUrl,
|
||||
[property: JsonPropertyName("description")] string Description,
|
||||
[property: JsonPropertyName("quiz_type")] string QuizType,
|
||||
[property: JsonPropertyName("assignment_group_id")] ulong AssignmentGroupId,
|
||||
[property: JsonPropertyName("time_limit")] decimal? TimeLimit,
|
||||
[property: JsonPropertyName("shuffle_answers")] bool? ShuffleAnswers,
|
||||
[property: JsonPropertyName("hide_results")] string? HideResults,
|
||||
[property: JsonPropertyName("show_correct_answers")] bool? ShowCorrectAnswers,
|
||||
[property: JsonPropertyName("show_correct_answers_last_attempt")]
|
||||
bool? ShowCorrectAnswersLastAttempt,
|
||||
[property: JsonPropertyName("show_correct_answers_at")] DateTime? ShowCorrectAnswersAt,
|
||||
[property: JsonPropertyName("hide_correct_answers_at")] DateTime? HideCorrectAnswersAt,
|
||||
[property: JsonPropertyName("one_time_results")] bool? OneTimeResults,
|
||||
[property: JsonPropertyName("scoring_policy")] string? ScoringPolicy,
|
||||
[property: JsonPropertyName("allowed_attempts")] int AllowedAttempts,
|
||||
[property: JsonPropertyName("one_question_at_a_time")] bool? OneQuestionAtATime,
|
||||
[property: JsonPropertyName("question_count")] uint? QuestionCount,
|
||||
[property: JsonPropertyName("points_possible")] decimal? PointsPossible,
|
||||
[property: JsonPropertyName("cant_go_back")] bool? CantGoBack,
|
||||
[property: JsonPropertyName("access_code")] string? AccessCode,
|
||||
[property: JsonPropertyName("ip_filter")] string? IpFilter,
|
||||
[property: JsonPropertyName("due_at")] DateTime? DueAt,
|
||||
[property: JsonPropertyName("lock_at")] DateTime? LockAt,
|
||||
[property: JsonPropertyName("unlock_at")] DateTime? UnlockAt,
|
||||
[property: JsonPropertyName("published")] bool? Published,
|
||||
[property: JsonPropertyName("unpublishable")] bool? Unpublishable,
|
||||
[property: JsonPropertyName("locked_for_user")] bool? LockedForUser,
|
||||
[property: JsonPropertyName("lock_info")] CanvasLockInfo? LockInfo,
|
||||
[property: JsonPropertyName("lock_explanation")] string? LockExplanation,
|
||||
[property: JsonPropertyName("speedgrader_url")] string? SpeedGraderUrl,
|
||||
[property: JsonPropertyName("quiz_extensions_url")] string QuizExtensionsUrl,
|
||||
[property: JsonPropertyName("permissions")] CanvasQuizPermissions Permissions,
|
||||
[property: JsonPropertyName("all_dates")] object AllDates,
|
||||
[property: JsonPropertyName("version_number")] uint? VersionNumber,
|
||||
[property: JsonPropertyName("question_types")] IEnumerable<string> QuestionTypes,
|
||||
[property: JsonPropertyName("anonymous_submissions")] bool? AnonymousSubmissions
|
||||
);
|
||||
public record CanvasQuiz
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public ulong Id { get; init; }
|
||||
|
||||
[JsonPropertyName("title")]
|
||||
public required string Title { get; init; }
|
||||
|
||||
[JsonPropertyName("html_url")]
|
||||
public required string HtmlUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("mobile_url")]
|
||||
public required string MobileUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("preview_url")]
|
||||
public string? PreviewUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public required string Description { get; init; }
|
||||
|
||||
[JsonPropertyName("quiz_type")]
|
||||
public required string QuizType { get; init; }
|
||||
|
||||
[JsonPropertyName("assignment_group_id")]
|
||||
public ulong? AssignmentGroupId { get; init; }
|
||||
|
||||
[JsonPropertyName("time_limit")]
|
||||
public decimal? TimeLimit { get; init; }
|
||||
|
||||
[JsonPropertyName("shuffle_answers")]
|
||||
public bool? ShuffleAnswers { get; init; }
|
||||
|
||||
[JsonPropertyName("hide_results")]
|
||||
public string? HideResults { get; init; }
|
||||
|
||||
[JsonPropertyName("show_correct_answers")]
|
||||
public bool? ShowCorrectAnswers { get; init; }
|
||||
|
||||
[JsonPropertyName("show_correct_answers_last_attempt")]
|
||||
public bool? ShowCorrectAnswersLastAttempt { get; init; }
|
||||
|
||||
[JsonPropertyName("show_correct_answers_at")]
|
||||
public DateTime? ShowCorrectAnswersAt { get; init; }
|
||||
|
||||
[JsonPropertyName("hide_correct_answers_at")]
|
||||
public DateTime? HideCorrectAnswersAt { get; init; }
|
||||
|
||||
[JsonPropertyName("one_time_results")]
|
||||
public bool? OneTimeResults { get; init; }
|
||||
|
||||
[JsonPropertyName("scoring_policy")]
|
||||
public string? ScoringPolicy { get; init; }
|
||||
|
||||
[JsonPropertyName("allowed_attempts")]
|
||||
public int AllowedAttempts { get; init; }
|
||||
|
||||
[JsonPropertyName("one_question_at_a_time")]
|
||||
public bool? OneQuestionAtATime { get; init; }
|
||||
|
||||
[JsonPropertyName("question_count")]
|
||||
public uint? QuestionCount { get; init; }
|
||||
|
||||
[JsonPropertyName("points_possible")]
|
||||
public decimal? PointsPossible { get; init; }
|
||||
|
||||
[JsonPropertyName("cant_go_back")]
|
||||
public bool? CantGoBack { get; init; }
|
||||
|
||||
[JsonPropertyName("access_code")]
|
||||
public string? AccessCode { get; init; }
|
||||
|
||||
[JsonPropertyName("ip_filter")]
|
||||
public string? IpFilter { get; init; }
|
||||
|
||||
[JsonPropertyName("due_at")]
|
||||
public DateTime? DueAt { get; init; }
|
||||
|
||||
[JsonPropertyName("lock_at")]
|
||||
public DateTime? LockAt { get; init; }
|
||||
|
||||
[JsonPropertyName("unlock_at")]
|
||||
public DateTime? UnlockAt { get; init; }
|
||||
|
||||
[JsonPropertyName("published")]
|
||||
public bool? Published { get; init; }
|
||||
|
||||
[JsonPropertyName("unpublishable")]
|
||||
public bool? Unpublishable { get; init; }
|
||||
|
||||
[JsonPropertyName("locked_for_user")]
|
||||
public bool? LockedForUser { get; init; }
|
||||
|
||||
[JsonPropertyName("lock_info")]
|
||||
public CanvasLockInfo? LockInfo { get; init; }
|
||||
|
||||
[JsonPropertyName("lock_explanation")]
|
||||
public string? LockExplanation { get; init; }
|
||||
|
||||
[JsonPropertyName("speedgrader_url")]
|
||||
public string? SpeedGraderUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("quiz_extensions_url")]
|
||||
public string? QuizExtensionsUrl { get; init; }
|
||||
|
||||
[JsonPropertyName("permissions")]
|
||||
public required CanvasQuizPermissions Permissions { get; init; }
|
||||
|
||||
[JsonPropertyName("all_dates")]
|
||||
public object? AllDates { get; init; }
|
||||
|
||||
[JsonPropertyName("version_number")]
|
||||
public uint? VersionNumber { get; init; }
|
||||
|
||||
[JsonPropertyName("question_types")]
|
||||
public IEnumerable<string>? QuestionTypes { get; init; }
|
||||
|
||||
[JsonPropertyName("anonymous_submissions")]
|
||||
public bool? AnonymousSubmissions { get; init; }
|
||||
}
|
||||
|
||||
55
Management/Models/CanvasModels/Quizzes/CanvasQuizAnswer.cs
Normal file
55
Management/Models/CanvasModels/Quizzes/CanvasQuizAnswer.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
namespace CanvasModel.Quizzes;
|
||||
|
||||
public record CanvasQuizAnswer
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public ulong Id { get; init; }
|
||||
|
||||
[JsonPropertyName("text")]
|
||||
public required string Text { get; init; }
|
||||
|
||||
[JsonPropertyName("html")]
|
||||
public required string Html { get; init; }
|
||||
|
||||
[JsonPropertyName("weight")]
|
||||
public double Weight { get; init; }
|
||||
|
||||
// [JsonPropertyName("comments")]
|
||||
// public string? Comments { get; init; }
|
||||
|
||||
// [JsonPropertyName("text_after_answers")]
|
||||
// public string? TextAfterAnswers { get; init; }
|
||||
|
||||
// [JsonPropertyName("answer_match_left")]
|
||||
// public string? AnswerMatchLeft { get; init; }
|
||||
|
||||
// [JsonPropertyName("answer_match_right")]
|
||||
// public string? AnswerMatchRight { get; init; }
|
||||
|
||||
// [JsonPropertyName("matching_answer_incorrect_matches")]
|
||||
// public string? MatchingAnswerIncorrectMatches { get; init; }
|
||||
|
||||
// [JsonPropertyName("numerical_answer_type")]
|
||||
// public string? NumericalAnswerType { get; init; }
|
||||
|
||||
// [JsonPropertyName("exact")]
|
||||
// public int? Exact { get; init; }
|
||||
|
||||
// [JsonPropertyName("margin")]
|
||||
// public int? Margin { get; init; }
|
||||
|
||||
// [JsonPropertyName("approximate")]
|
||||
// public double? Approximate { get; init; }
|
||||
|
||||
// [JsonPropertyName("precision")]
|
||||
// public int? Precision { get; init; }
|
||||
|
||||
// [JsonPropertyName("start")]
|
||||
// public int? Start { get; init; }
|
||||
|
||||
// [JsonPropertyName("end")]
|
||||
// public int? End { get; init; }
|
||||
|
||||
// [JsonPropertyName("blank_id")]
|
||||
// public int? BlankId { get; init; }
|
||||
}
|
||||
34
Management/Models/CanvasModels/Quizzes/CanvasQuizQuestion.cs
Normal file
34
Management/Models/CanvasModels/Quizzes/CanvasQuizQuestion.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace CanvasModel.Quizzes;
|
||||
|
||||
public record CanvasQuizQuestion
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public ulong Id { get; init; }
|
||||
|
||||
[JsonPropertyName("quiz_id")]
|
||||
public int QuizId { get; init; }
|
||||
|
||||
[JsonPropertyName("position")]
|
||||
public int? Position { get; init; }
|
||||
|
||||
[JsonPropertyName("question_name")]
|
||||
public required string QuestionName { get; init; }
|
||||
|
||||
[JsonPropertyName("question_type")]
|
||||
public required string QuestionType { get; init; }
|
||||
|
||||
[JsonPropertyName("question_text")]
|
||||
public required string QuestionText { get; init; }
|
||||
|
||||
[JsonPropertyName("correct_comments")]
|
||||
public required string CorrectComments { get; init; }
|
||||
|
||||
[JsonPropertyName("incorrect_comments")]
|
||||
public required string IncorrectComments { get; init; }
|
||||
|
||||
[JsonPropertyName("neutral_comments")]
|
||||
public required string NeutralComments { get; init; }
|
||||
|
||||
[JsonPropertyName("answers")]
|
||||
public IEnumerable<CanvasQuizAnswer>? Answers { get; init; }
|
||||
}
|
||||
@@ -11,7 +11,14 @@ public record LocalQuiz
|
||||
public DateTime DueAt { get; init; }
|
||||
public bool ShuffleAnswers { get; init; }
|
||||
public bool OneQuestionAtATime { get; init; }
|
||||
public int AllowedAttempts { get; init; }
|
||||
public int AllowedAttempts { get; init; } = -1; // -1 is infinite
|
||||
public bool ShowCorrectAnswers { get; init; }
|
||||
public int? TimeLimit { get; init; } = null;
|
||||
public string? HideResults { get; init; } = null;
|
||||
|
||||
// If null, students can see their results after any attempt.
|
||||
// If “always”, students can never see their results.
|
||||
// If “until_after_last_attempt”, students can only see results after their last attempt. (Only valid if allowed_attempts > 1). Defaults to null.
|
||||
public IEnumerable<LocalQuizQuestion> Questions { get; init; } =
|
||||
Enumerable.Empty<LocalQuizQuestion>();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace LocalModels;
|
||||
|
||||
public record LocalQuizQuestion
|
||||
{
|
||||
public ulong? CanvasId { get; set; }
|
||||
public string Id { get; set; } = "";
|
||||
public string Text { get; init; } = string.Empty;
|
||||
public string QuestionType { get; init; } = string.Empty;
|
||||
|
||||
@@ -2,7 +2,8 @@ namespace LocalModels;
|
||||
|
||||
public record LocalQuizQuestionAnswer
|
||||
{
|
||||
public string Id { get; set; } = string.Empty;
|
||||
public ulong? CanvasId { get; init; }
|
||||
public string Id { get; init; } = string.Empty;
|
||||
|
||||
//correct gets a weight of 100 in canvas
|
||||
public bool Correct { get; init; }
|
||||
|
||||
@@ -30,13 +30,13 @@ public class CanvasAssignmentService
|
||||
}
|
||||
|
||||
public async Task<LocalAssignment> Create(
|
||||
ulong courseId,
|
||||
ulong canvasCourseId,
|
||||
LocalAssignment localAssignment,
|
||||
string htmlDescription
|
||||
)
|
||||
{
|
||||
Console.WriteLine($"creating assignment: {localAssignment.Name}");
|
||||
var url = $"courses/{courseId}/assignments";
|
||||
var url = $"courses/{canvasCourseId}/assignments";
|
||||
var request = new RestRequest(url);
|
||||
var body = new CanvasAssignmentCreationRequest()
|
||||
{
|
||||
@@ -47,7 +47,6 @@ public class CanvasAssignmentService
|
||||
lock_at = localAssignment.LockAt,
|
||||
points_possible = localAssignment.PointsPossible
|
||||
};
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
var bodyObj = new { assignment = body };
|
||||
request.AddBody(bodyObj);
|
||||
var (canvasAssignment, response) = await webRequestor.PostAsync<CanvasAssignment>(request);
|
||||
@@ -56,7 +55,7 @@ public class CanvasAssignmentService
|
||||
|
||||
var updatedLocalAssignment = localAssignment with { CanvasId = canvasAssignment.Id };
|
||||
|
||||
await CreateRubric(courseId, updatedLocalAssignment);
|
||||
await CreateRubric(canvasCourseId, updatedLocalAssignment);
|
||||
|
||||
return updatedLocalAssignment;
|
||||
}
|
||||
@@ -75,7 +74,6 @@ public class CanvasAssignmentService
|
||||
lock_at = localAssignment.LockAt,
|
||||
points_possible = localAssignment.PointsPossible
|
||||
};
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
var bodyObj = new { assignment = body };
|
||||
request.AddBody(bodyObj);
|
||||
Console.WriteLine(url);
|
||||
@@ -145,7 +143,6 @@ public class CanvasAssignmentService
|
||||
var creationUrl = $"courses/{courseId}/rubrics";
|
||||
var rubricCreationRequest = new RestRequest(creationUrl);
|
||||
rubricCreationRequest.AddBody(body);
|
||||
rubricCreationRequest.AddHeader("Content-Type", "application/json");
|
||||
var (rubricCreationResponse, _) = await webRequestor.PostAsync<CanvasRubricCreationResponse>(
|
||||
rubricCreationRequest
|
||||
);
|
||||
@@ -160,7 +157,6 @@ public class CanvasAssignmentService
|
||||
var adjustmentUrl = $"courses/{courseId}/assignments/{localAssignment.CanvasId}";
|
||||
var pointAdjustmentRequest = new RestRequest(adjustmentUrl);
|
||||
pointAdjustmentRequest.AddBody(assignmentPointCorrectionBody);
|
||||
pointAdjustmentRequest.AddHeader("Content-Type", "application/json");
|
||||
var (_, _) = await webRequestor.PutAsync<CanvasAssignment>(pointAdjustmentRequest);
|
||||
}
|
||||
}
|
||||
|
||||
126
Management/Services/Canvas/CanvasQuizService.cs
Normal file
126
Management/Services/Canvas/CanvasQuizService.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using CanvasModel.Quizzes;
|
||||
using LocalModels;
|
||||
using RestSharp;
|
||||
|
||||
namespace Management.Services.Canvas;
|
||||
|
||||
public class CanvasQuizService
|
||||
{
|
||||
private readonly IWebRequestor webRequestor;
|
||||
private readonly CanvasServiceUtils utils;
|
||||
|
||||
public CanvasQuizService(IWebRequestor webRequestor, CanvasServiceUtils utils)
|
||||
{
|
||||
this.webRequestor = webRequestor;
|
||||
this.utils = utils;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<CanvasQuiz>> GetAll(ulong courseId)
|
||||
{
|
||||
var url = $"courses/{courseId}/quizzes";
|
||||
var request = new RestRequest(url);
|
||||
var quizResponse = await utils.PaginatedRequest<IEnumerable<CanvasQuiz>>(request);
|
||||
return quizResponse.SelectMany(
|
||||
quizzes =>
|
||||
quizzes.Select(
|
||||
a => a with { DueAt = a.DueAt?.ToLocalTime(), LockAt = a.LockAt?.ToLocalTime() }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<LocalQuiz> Create(ulong canvasCourseId, LocalQuiz localQuiz)
|
||||
{
|
||||
Console.WriteLine($"Creating Quiz ${localQuiz.Name}");
|
||||
|
||||
var url = $"courses/{canvasCourseId}/quizzes";
|
||||
var body = new
|
||||
{
|
||||
quiz = new
|
||||
{
|
||||
title = localQuiz.Name,
|
||||
description = localQuiz.Description,
|
||||
// assignment_group_id = "quiz", TODO: support specific assignment groups
|
||||
time_limit = localQuiz.TimeLimit,
|
||||
shuffle_answers = localQuiz.ShuffleAnswers,
|
||||
hide_results = localQuiz.HideResults,
|
||||
allowed_attempts = localQuiz.AllowedAttempts,
|
||||
one_question_at_a_time = true,
|
||||
cant_go_back = false,
|
||||
due_at = localQuiz.DueAt,
|
||||
lock_at = localQuiz.LockAt,
|
||||
}
|
||||
};
|
||||
var request = new RestRequest(url);
|
||||
request.AddBody(body);
|
||||
var (canvasQuiz, response) = await webRequestor.PostAsync<CanvasQuiz>(request);
|
||||
if (canvasQuiz == null)
|
||||
throw new Exception("Created canvas quiz was null");
|
||||
|
||||
|
||||
var updatedQuiz = localQuiz with { CanvasId = canvasQuiz.Id };
|
||||
var quizWithQuestions = await CreateQuizQuestions(canvasCourseId, updatedQuiz);
|
||||
|
||||
|
||||
return quizWithQuestions;
|
||||
}
|
||||
|
||||
public async Task<LocalQuiz> CreateQuizQuestions(ulong canvasCourseId, LocalQuiz localQuiz)
|
||||
{
|
||||
var tasks = localQuiz.Questions
|
||||
.Select(
|
||||
async (question) =>
|
||||
{
|
||||
var newQuestion = await createQuestionOnly(canvasCourseId, localQuiz, question);
|
||||
|
||||
var answersWithIds = question.Answers
|
||||
.Select(answer =>
|
||||
{
|
||||
var canvasAnswer = newQuestion.Answers?.FirstOrDefault(ca => ca.Html == answer.Text);
|
||||
if (canvasAnswer == null)
|
||||
{
|
||||
Console.WriteLine(JsonSerializer.Serialize(newQuestion));
|
||||
Console.WriteLine(JsonSerializer.Serialize(question));
|
||||
throw new NullReferenceException(
|
||||
"Could not find canvas answer to update local answer id"
|
||||
);
|
||||
}
|
||||
return answer with { CanvasId = canvasAnswer.Id };
|
||||
})
|
||||
.ToArray();
|
||||
return question with { CanvasId = newQuestion.Id, Answers = answersWithIds };
|
||||
}
|
||||
)
|
||||
.ToArray();
|
||||
var updatedQuestions = await Task.WhenAll(tasks);
|
||||
return localQuiz with { Questions = updatedQuestions };
|
||||
}
|
||||
|
||||
private async Task<CanvasQuizQuestion> createQuestionOnly(
|
||||
ulong canvasCourseId,
|
||||
LocalQuiz localQuiz,
|
||||
LocalQuizQuestion q
|
||||
)
|
||||
{
|
||||
var url = $"courses/{canvasCourseId}/quizzes/{localQuiz.CanvasId}/questions";
|
||||
var answers = q.Answers
|
||||
.Select(a => new { answer_html = a.Text, answer_weight = a.Correct ? 100 : 0 })
|
||||
.ToArray();
|
||||
var body = new
|
||||
{
|
||||
question = new
|
||||
{
|
||||
question_text = q.Text,
|
||||
question_type = q.QuestionType+"_question",
|
||||
possible_points = q.Points,
|
||||
// position
|
||||
answers
|
||||
}
|
||||
};
|
||||
var request = new RestRequest(url);
|
||||
request.AddBody(body);
|
||||
var (newQuestion, response) = await webRequestor.PostAsync<CanvasQuizQuestion>(request);
|
||||
if (newQuestion == null)
|
||||
throw new NullReferenceException("error creating new question, created question is null");
|
||||
return newQuestion;
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,19 @@ public class CanvasService
|
||||
private readonly CanvasServiceUtils utils;
|
||||
|
||||
public CanvasAssignmentService Assignments { get; }
|
||||
public CanvasQuizService Quizzes { get; }
|
||||
|
||||
public CanvasService(
|
||||
IWebRequestor webRequestor,
|
||||
CanvasServiceUtils utils,
|
||||
CanvasAssignmentService Assignments
|
||||
CanvasAssignmentService Assignments,
|
||||
CanvasQuizService Quizzes
|
||||
)
|
||||
{
|
||||
this.webRequestor = webRequestor;
|
||||
this.utils = utils;
|
||||
this.Assignments = Assignments;
|
||||
this.Quizzes = Quizzes;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EnrollmentTermModel>> GetTerms()
|
||||
@@ -51,8 +54,8 @@ public class CanvasService
|
||||
|
||||
if (data == null)
|
||||
{
|
||||
System.Console.WriteLine(response.Content);
|
||||
System.Console.WriteLine(response.ResponseUri);
|
||||
Console.WriteLine(response.Content);
|
||||
Console.WriteLine(response.ResponseUri);
|
||||
throw new Exception("error getting course from canvas");
|
||||
}
|
||||
return data;
|
||||
@@ -151,7 +154,6 @@ public class CanvasService
|
||||
var body = new { module_item = new { title = item.Title, position = item.Position } };
|
||||
var request = new RestRequest(url);
|
||||
request.AddBody(body);
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
|
||||
var (newItem, response) = await webRequestor.PutAsync<CanvasModuleItem>(request);
|
||||
if (newItem == null)
|
||||
@@ -179,7 +181,6 @@ public class CanvasService
|
||||
};
|
||||
var request = new RestRequest(url);
|
||||
request.AddBody(body);
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
|
||||
var (newItem, response) = await webRequestor.PostAsync<CanvasModuleItem>(request);
|
||||
if (newItem == null)
|
||||
|
||||
@@ -30,6 +30,7 @@ public class WebRequestor : IWebRequestor
|
||||
|
||||
public async Task<RestResponse> PostAsync(RestRequest request)
|
||||
{
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
var response = await client.ExecutePostAsync(request);
|
||||
if (!response.IsSuccessful)
|
||||
{
|
||||
@@ -43,6 +44,7 @@ public class WebRequestor : IWebRequestor
|
||||
|
||||
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
||||
{
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
var response = await client.ExecutePostAsync(request);
|
||||
return (deserialize<T>(response), response);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user