mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
put a little more work into quizzes
This commit is contained in:
@@ -82,6 +82,11 @@
|
||||
|
||||
UpdateQuestion(Question with { Answers = answers });
|
||||
}
|
||||
private void handleTextUpdate(ChangeEventArgs e)
|
||||
{
|
||||
var newText = e.Value?.ToString() ?? "";
|
||||
UpdateQuestion(Question with { Text = newText });
|
||||
}
|
||||
}
|
||||
|
||||
<div class="my-1 p-2 border border-3 rounded">
|
||||
@@ -99,6 +104,7 @@
|
||||
id="question_text"
|
||||
name="question_text"
|
||||
@bind="text"
|
||||
@oninput="handleTextUpdate"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
|
||||
@@ -21,7 +21,7 @@ public class CoursePlanner
|
||||
this.canvas = canvas;
|
||||
}
|
||||
|
||||
private Timer _debounceTimer;
|
||||
private Timer? _debounceTimer;
|
||||
private int _debounceInterval = 1000;
|
||||
private LocalCourse? _localCourse { get; set; }
|
||||
public LocalCourse? LocalCourse
|
||||
@@ -54,6 +54,7 @@ public class CoursePlanner
|
||||
private void saveCourseToFile(LocalCourse courseAsOfDebounce)
|
||||
{
|
||||
_debounceTimer?.Dispose();
|
||||
|
||||
// ignore initial load of course
|
||||
if (LocalCourse == null)
|
||||
{
|
||||
@@ -137,6 +138,8 @@ public class CoursePlanner
|
||||
LocalCourse = await LocalCourse.SyncAssignmentsWithCanvas(canvasId, CanvasAssignments, canvas);
|
||||
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
|
||||
|
||||
|
||||
|
||||
await syncModuleItemsWithCanvas(canvasId);
|
||||
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
||||
|
||||
|
||||
@@ -1,75 +1,16 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using CanvasModel.Assignments;
|
||||
using CanvasModel.Modules;
|
||||
using CanvasModel.Quizzes;
|
||||
using LocalModels;
|
||||
using Management.Services.Canvas;
|
||||
|
||||
namespace Management.Planner;
|
||||
|
||||
public static partial class CoursePlannerSyncronizationExtensions
|
||||
public static partial class AssignmentSyncronizationExtensions
|
||||
{
|
||||
internal static async Task<IEnumerable<LocalModule>> EnsureAllModulesExistInCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
IEnumerable<CanvasModule> canvasModules,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
var moduleTasks = localCourse.Modules.Select(async module =>
|
||||
{
|
||||
var canvasModule = canvasModules.FirstOrDefault(cm => cm.Id == module.CanvasId);
|
||||
if (canvasModule == null)
|
||||
{
|
||||
var newModule = await canvas.CreateModule(canvasId, module.Name);
|
||||
return module with { CanvasId = newModule.Id };
|
||||
}
|
||||
else
|
||||
return module;
|
||||
});
|
||||
var newModules = await Task.WhenAll(moduleTasks);
|
||||
return newModules ?? throw new Exception("Error ensuring all modules exist in canvas");
|
||||
}
|
||||
|
||||
internal static async Task SortCanvasModules(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
IEnumerable<CanvasModule> canvasModules,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
var currentCanvasPositions = canvasModules.ToDictionary(m => m.Id, m => m.Position);
|
||||
foreach (var (localModule, i) in localCourse.Modules.Select((m, i) => (m, i)))
|
||||
{
|
||||
var correctPosition = i + 1;
|
||||
var moduleCanvasId =
|
||||
localModule.CanvasId ?? throw new Exception("cannot sort module if no module canvas id");
|
||||
var currentCanvasPosition = currentCanvasPositions[moduleCanvasId];
|
||||
if (currentCanvasPosition != correctPosition)
|
||||
{
|
||||
await canvas.UpdateModule(canvasId, moduleCanvasId, localModule.Name, correctPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<LocalCourse> SyncModulesWithCanvasData(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
IEnumerable<CanvasModule> canvasModules,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
canvasModules = await canvas.GetModules(canvasId);
|
||||
return localCourse with
|
||||
{
|
||||
Modules = localCourse.Modules.Select(m =>
|
||||
{
|
||||
var canvasModule = canvasModules.FirstOrDefault(cm => cm.Name == m.Name);
|
||||
return canvasModule == null ? m : m with { CanvasId = canvasModule.Id };
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
internal static async Task<LocalAssignment> SyncToCanvas(
|
||||
internal static async Task<LocalAssignment> SyncAssignmentToCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
LocalAssignment localAssignment,
|
||||
@@ -114,38 +55,51 @@ public static partial class CoursePlannerSyncronizationExtensions
|
||||
|
||||
var localHtmlDescription = localAssignment
|
||||
.GetDescriptionHtml(courseAssignmentTemplates)
|
||||
.Replace("<hr />", "<hr>")
|
||||
.Replace(">", "")
|
||||
.Replace("<", "")
|
||||
.Replace(">", "")
|
||||
.Replace("<", "");
|
||||
.Replace("<", "")
|
||||
.Replace(""", "")
|
||||
.Replace("\"", "");
|
||||
|
||||
var canvasHtmlDescription = canvasAssignment.Description;
|
||||
canvasHtmlDescription = CanvasScriptTagRegex().Replace(canvasHtmlDescription, "");
|
||||
canvasHtmlDescription = CanvasLinkTagRegex().Replace(canvasHtmlDescription, "");
|
||||
canvasHtmlDescription = canvasHtmlDescription
|
||||
.Replace("<hr />", "<hr>")
|
||||
.Replace(">", "")
|
||||
.Replace("<", "")
|
||||
.Replace(">", "")
|
||||
.Replace("<", "");
|
||||
.Replace("<", "")
|
||||
.Replace(""", "")
|
||||
.Replace("\"", "");
|
||||
|
||||
var dueDatesSame =
|
||||
var canvasComparisonDueDate =
|
||||
canvasAssignment.DueAt != null
|
||||
&& new DateTime(
|
||||
year: canvasAssignment.DueAt.Value.Year,
|
||||
month: canvasAssignment.DueAt.Value.Month,
|
||||
day: canvasAssignment.DueAt.Value.Day,
|
||||
hour: canvasAssignment.DueAt.Value.Hour,
|
||||
minute: canvasAssignment.DueAt.Value.Minute,
|
||||
second: canvasAssignment.DueAt.Value.Second
|
||||
)
|
||||
== new DateTime(
|
||||
? new DateTime(
|
||||
year: canvasAssignment.DueAt.Value.Year,
|
||||
month: canvasAssignment.DueAt.Value.Month,
|
||||
day: canvasAssignment.DueAt.Value.Day,
|
||||
hour: canvasAssignment.DueAt.Value.Hour,
|
||||
minute: canvasAssignment.DueAt.Value.Minute,
|
||||
second: canvasAssignment.DueAt.Value.Second
|
||||
)
|
||||
: new DateTime();
|
||||
var localComparisonDueDate =
|
||||
canvasAssignment.DueAt != null
|
||||
? new DateTime(
|
||||
year: localAssignment.DueAt.Year,
|
||||
month: localAssignment.DueAt.Month,
|
||||
day: localAssignment.DueAt.Day,
|
||||
hour: localAssignment.DueAt.Hour,
|
||||
minute: localAssignment.DueAt.Minute,
|
||||
second: localAssignment.DueAt.Second
|
||||
);
|
||||
)
|
||||
: new DateTime();
|
||||
|
||||
var dueDatesSame =
|
||||
canvasAssignment.DueAt != null && canvasComparisonDueDate == localComparisonDueDate;
|
||||
|
||||
var descriptionSame = canvasHtmlDescription == localHtmlDescription;
|
||||
var nameSame = canvasAssignment.Name == localAssignment.Name;
|
||||
@@ -159,6 +113,9 @@ public static partial class CoursePlannerSyncronizationExtensions
|
||||
{
|
||||
if (!dueDatesSame)
|
||||
{
|
||||
Console.WriteLine(JsonSerializer.Serialize(canvasAssignment));
|
||||
Console.WriteLine(canvasComparisonDueDate);
|
||||
Console.WriteLine(localComparisonDueDate);
|
||||
Console.WriteLine(
|
||||
$"Due dates different for {localAssignment.Name}, local: {localAssignment.DueAt}, in canvas {canvasAssignment.DueAt}"
|
||||
);
|
||||
@@ -219,7 +176,7 @@ public static partial class CoursePlannerSyncronizationExtensions
|
||||
var moduleTasks = localCourse.Modules.Select(async m =>
|
||||
{
|
||||
var assignmentTasks = m.Assignments.Select(
|
||||
(a) => localCourse.SyncToCanvas(canvasId, a, canvasAssignments, canvas)
|
||||
(a) => localCourse.SyncAssignmentToCanvas(canvasId, a, canvasAssignments, canvas)
|
||||
);
|
||||
var assignments = await Task.WhenAll(assignmentTasks);
|
||||
return m with { Assignments = assignments };
|
||||
@@ -0,0 +1,72 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using CanvasModel.Assignments;
|
||||
using CanvasModel.Modules;
|
||||
using CanvasModel.Quizzes;
|
||||
using LocalModels;
|
||||
using Management.Services.Canvas;
|
||||
|
||||
namespace Management.Planner;
|
||||
|
||||
public static partial class ModuleSyncronizationExtensions
|
||||
{
|
||||
internal static async Task<IEnumerable<LocalModule>> EnsureAllModulesExistInCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
IEnumerable<CanvasModule> canvasModules,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
var moduleTasks = localCourse.Modules.Select(async module =>
|
||||
{
|
||||
var canvasModule = canvasModules.FirstOrDefault(cm => cm.Id == module.CanvasId);
|
||||
if (canvasModule == null)
|
||||
{
|
||||
var newModule = await canvas.CreateModule(canvasId, module.Name);
|
||||
return module with { CanvasId = newModule.Id };
|
||||
}
|
||||
else
|
||||
return module;
|
||||
});
|
||||
var newModules = await Task.WhenAll(moduleTasks);
|
||||
return newModules ?? throw new Exception("Error ensuring all modules exist in canvas");
|
||||
}
|
||||
|
||||
internal static async Task SortCanvasModules(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
IEnumerable<CanvasModule> canvasModules,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
var currentCanvasPositions = canvasModules.ToDictionary(m => m.Id, m => m.Position);
|
||||
foreach (var (localModule, i) in localCourse.Modules.Select((m, i) => (m, i)))
|
||||
{
|
||||
var correctPosition = i + 1;
|
||||
var moduleCanvasId =
|
||||
localModule.CanvasId ?? throw new Exception("cannot sort module if no module canvas id");
|
||||
var currentCanvasPosition = currentCanvasPositions[moduleCanvasId];
|
||||
if (currentCanvasPosition != correctPosition)
|
||||
{
|
||||
await canvas.UpdateModule(canvasId, moduleCanvasId, localModule.Name, correctPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task<LocalCourse> SyncModulesWithCanvasData(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
IEnumerable<CanvasModule> canvasModules,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
canvasModules = await canvas.GetModules(canvasId);
|
||||
return localCourse with
|
||||
{
|
||||
Modules = localCourse.Modules.Select(m =>
|
||||
{
|
||||
var canvasModule = canvasModules.FirstOrDefault(cm => cm.Name == m.Name);
|
||||
return canvasModule == null ? m : m with { CanvasId = canvasModule.Id };
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using CanvasModel.Assignments;
|
||||
using CanvasModel.Modules;
|
||||
using CanvasModel.Quizzes;
|
||||
using LocalModels;
|
||||
using Management.Services.Canvas;
|
||||
|
||||
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
|
||||
)
|
||||
{
|
||||
// TODO actually sync
|
||||
return localQuiz;
|
||||
}
|
||||
|
||||
internal static async Task<LocalCourse> SyncQuizzesWithCanvas(
|
||||
this LocalCourse localCourse,
|
||||
ulong canvasId,
|
||||
IEnumerable<CanvasQuiz> canvasQuizzes,
|
||||
CanvasService canvas
|
||||
)
|
||||
{
|
||||
var moduleTasks = localCourse.Modules.Select(async m =>
|
||||
{
|
||||
var quizTasks = m.Quizzes.Select(
|
||||
(q) => localCourse.SyncQuizToCanvas(canvasId, q, canvasQuizzes, canvas)
|
||||
);
|
||||
var quizzes = await Task.WhenAll(quizTasks);
|
||||
return m with { Quizzes = quizzes };
|
||||
});
|
||||
|
||||
var modules = await Task.WhenAll(moduleTasks);
|
||||
return localCourse;
|
||||
}
|
||||
}
|
||||
46
Management/Models/CanvasModels/Quizzes/CanvasQuiz.cs
Normal file
46
Management/Models/CanvasModels/Quizzes/CanvasQuiz.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
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
|
||||
);
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
|
||||
namespace CanvasModel.Quizzes;
|
||||
public class CanvasQuizPermissions
|
||||
{
|
||||
|
||||
[JsonPropertyName("read")]
|
||||
public bool Read { get; set; }
|
||||
|
||||
[JsonPropertyName("submit")]
|
||||
public bool Submit { get; set; }
|
||||
|
||||
[JsonPropertyName("create")]
|
||||
public bool Create { get; set; }
|
||||
|
||||
[JsonPropertyName("manage")]
|
||||
public bool Manage { get; set; }
|
||||
|
||||
[JsonPropertyName("read_statistics")]
|
||||
public bool ReadStatistics { get; set; }
|
||||
|
||||
[JsonPropertyName("review_grades")]
|
||||
public bool ReviewGrades { get; set; }
|
||||
|
||||
[JsonPropertyName("update")]
|
||||
public bool Update { get; set; }
|
||||
}
|
||||
@@ -3,6 +3,7 @@ namespace LocalModels;
|
||||
public record LocalModule
|
||||
{
|
||||
public string Name { get; init; } = string.Empty;
|
||||
public string Id { get; init; } = string.Empty;
|
||||
public IEnumerable<LocalAssignment> Assignments { get; init; } =
|
||||
Enumerable.Empty<LocalAssignment>();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ public class CanvasAssignmentService
|
||||
{
|
||||
var url = $"courses/{courseId}/assignments";
|
||||
var request = new RestRequest(url);
|
||||
request.AddParameter("include[]", "overrides");
|
||||
var assignmentResponse = await utils.PaginatedRequest<IEnumerable<CanvasAssignment>>(request);
|
||||
return assignmentResponse.SelectMany(
|
||||
assignments =>
|
||||
@@ -77,6 +78,8 @@ public class CanvasAssignmentService
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
var bodyObj = new { assignment = body };
|
||||
request.AddBody(bodyObj);
|
||||
Console.WriteLine(url);
|
||||
Console.WriteLine(JsonSerializer.Serialize(bodyObj));
|
||||
await webRequestor.PutAsync(request);
|
||||
|
||||
await CreateRubric(courseId, localAssignment);
|
||||
|
||||
@@ -13,6 +13,7 @@ public class WebRequestor : IWebRequestor
|
||||
?? throw new Exception("CANVAS_TOKEN not in environment");
|
||||
client = new RestClient(BaseUrl);
|
||||
client.AddDefaultHeader("Authorization", $"Bearer {token}");
|
||||
|
||||
}
|
||||
|
||||
public async Task<(T[]?, RestResponse)> GetManyAsync<T>(RestRequest request)
|
||||
@@ -48,6 +49,7 @@ public class WebRequestor : IWebRequestor
|
||||
|
||||
public async Task<RestResponse> PutAsync(RestRequest request)
|
||||
{
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
var response = await client.ExecutePutAsync(request);
|
||||
// if (!response.IsSuccessful)
|
||||
// {
|
||||
@@ -61,6 +63,7 @@ public class WebRequestor : IWebRequestor
|
||||
|
||||
public async Task<(T?, RestResponse)> PutAsync<T>(RestRequest request)
|
||||
{
|
||||
request.AddHeader("Content-Type", "application/json");
|
||||
var response = await client.ExecutePutAsync(request);
|
||||
return (deserialize<T>(response), response);
|
||||
}
|
||||
|
||||
@@ -108,3 +108,20 @@ Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||
"unlock_at":null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
###
|
||||
PUT https://snow.instructure.com/api/v1/courses/872095/assignments/12676639
|
||||
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"assignment": {
|
||||
"due_at": "2021-09-15T23:59:59Z",
|
||||
"lock_at": "2023-08-22T05:59:00Z"
|
||||
}
|
||||
}
|
||||
|
||||
###
|
||||
GET https://snow.instructure.com/api/v1/courses/872095/assignments/12676639?include[]=overrides
|
||||
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||
3
requests/quiz.http
Normal file
3
requests/quiz.http
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
GET https://snow.instructure.com/api/v1/courses/705168
|
||||
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||
Reference in New Issue
Block a user