got basic question and answer creation from canvas

This commit is contained in:
2023-08-17 18:50:59 -06:00
parent 4fb257e000
commit 28ad344018
18 changed files with 604 additions and 204 deletions

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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; }
}

View 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; }
}

View 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; }
}

View File

@@ -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>();
}

View File

@@ -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;

View File

@@ -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; }

View File

@@ -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);
}
}

View 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;
}
}

View File

@@ -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)

View File

@@ -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);
}