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:
@@ -79,29 +79,31 @@
|
|||||||
|
|
||||||
@if(planner.LocalCourse != null)
|
@if(planner.LocalCourse != null)
|
||||||
{
|
{
|
||||||
<div class="mb-3">
|
<div class="mb-3 d-flex justify-content-between" style="height: 4em;">
|
||||||
<button
|
<div class="my-auto">
|
||||||
@onclick="planner.Clear"
|
<button
|
||||||
class="btn btn-primary"
|
@onclick="planner.Clear"
|
||||||
>
|
class="btn btn-primary"
|
||||||
Select New Course
|
>
|
||||||
</button>
|
Select New Course
|
||||||
<CourseSettings />
|
</button>
|
||||||
<AssignmentTemplateManagement />
|
<CourseSettings />
|
||||||
|
<AssignmentTemplateManagement />
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-primary"
|
class="btn btn-outline-primary"
|
||||||
@onclick="planner.SyncWithCanvas"
|
@onclick="planner.SyncWithCanvas"
|
||||||
>
|
>
|
||||||
Sync With Canvas
|
Sync With Canvas
|
||||||
</button>
|
</button>
|
||||||
<a
|
<a
|
||||||
class="btn btn-outline-secondary"
|
class="btn btn-outline-secondary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
href="@($"{Environment.GetEnvironmentVariable("CANVAS_URL")}/courses/{planner.LocalCourse.CanvasId}")"
|
href="@($"{Environment.GetEnvironmentVariable("CANVAS_URL")}/courses/{planner.LocalCourse.CanvasId}")"
|
||||||
>
|
>
|
||||||
View In Canvas
|
View In Canvas
|
||||||
</a>
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if(planner.LoadingCanvasData)
|
@if(planner.LoadingCanvasData)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,10 +32,10 @@ if (canvas_url == null)
|
|||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddScoped<IWebRequestor, WebRequestor>();
|
builder.Services.AddScoped<IWebRequestor, WebRequestor>();
|
||||||
builder.Services.AddScoped<CanvasServiceUtils>();
|
builder.Services.AddScoped<CanvasServiceUtils>();
|
||||||
builder.Services.AddScoped<CanvasAssignmentService>();
|
builder.Services.AddScoped<CanvasAssignmentService>();
|
||||||
|
builder.Services.AddScoped<CanvasQuizService>();
|
||||||
builder.Services.AddScoped<CanvasService, CanvasService>();
|
builder.Services.AddScoped<CanvasService, CanvasService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<YamlManager>();
|
builder.Services.AddScoped<YamlManager>();
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
@using Management.Web.Shared.Components
|
||||||
@using Management.Web.Shared.Components.Quiz
|
@using Management.Web.Shared.Components.Quiz
|
||||||
|
|
||||||
@inject DragContainer dragContainer
|
@inject DragContainer dragContainer
|
||||||
@inject QuizEditorContext quizContext
|
@inject QuizEditorContext quizContext
|
||||||
|
@inject CoursePlanner planner
|
||||||
|
|
||||||
@inherits DroppableQuiz
|
@inherits DroppableQuiz
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
|
||||||
private void HandleDragStart()
|
private void HandleDragStart()
|
||||||
{
|
{
|
||||||
dragContainer.DropCallback = dropCallback;
|
dragContainer.DropCallback = dropCallback;
|
||||||
@@ -16,6 +17,10 @@
|
|||||||
{
|
{
|
||||||
dragContainer.DropCallback = null;
|
dragContainer.DropCallback = null;
|
||||||
}
|
}
|
||||||
|
private bool isSyncedWithCanvas =>
|
||||||
|
planner.CanvasQuizzes != null
|
||||||
|
? Quiz.QuizIsCreated(planner.CanvasQuizzes)
|
||||||
|
: false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -29,9 +34,16 @@
|
|||||||
>
|
>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
<div class="card-title pt-2 px-2 m-0">
|
<div class="card-title pt-2 px-2 m-0 d-flex justify-content-between">
|
||||||
<h4>@Quiz.Name</h4>
|
<h4>@Quiz.Name</h4>
|
||||||
|
@if(isSyncedWithCanvas)
|
||||||
|
{
|
||||||
|
<CheckIcon />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<SyncIcon />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-text overflow-hidden p-2">
|
<div class="card-text overflow-hidden p-2">
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using CanvasModel.Assignments;
|
|||||||
using CanvasModel.Modules;
|
using CanvasModel.Modules;
|
||||||
using Management.Services.Canvas;
|
using Management.Services.Canvas;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using CanvasModel.Quizzes;
|
||||||
|
|
||||||
namespace Management.Planner;
|
namespace Management.Planner;
|
||||||
|
|
||||||
@@ -71,6 +72,7 @@ public class CoursePlanner
|
|||||||
public event Action? StateHasChanged;
|
public event Action? StateHasChanged;
|
||||||
|
|
||||||
public IEnumerable<CanvasAssignment>? CanvasAssignments { get; internal set; }
|
public IEnumerable<CanvasAssignment>? CanvasAssignments { get; internal set; }
|
||||||
|
public IEnumerable<CanvasQuiz>? CanvasQuizzes { get; internal set; }
|
||||||
public IEnumerable<CanvasModule>? CanvasModules { get; internal set; }
|
public IEnumerable<CanvasModule>? CanvasModules { get; internal set; }
|
||||||
public Dictionary<ulong, IEnumerable<CanvasModuleItem>>? CanvasModulesItems { 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");
|
LocalCourse?.CanvasId ?? throw new Exception("no canvas id found for selected course");
|
||||||
|
|
||||||
var assignmentsTask = canvas.Assignments.GetAll(canvasId);
|
var assignmentsTask = canvas.Assignments.GetAll(canvasId);
|
||||||
|
var quizzesTask = canvas.Quizzes.GetAll(canvasId);
|
||||||
var modulesTask = canvas.GetModules(canvasId);
|
var modulesTask = canvas.GetModules(canvasId);
|
||||||
|
|
||||||
CanvasAssignments = await assignmentsTask;
|
CanvasAssignments = await assignmentsTask;
|
||||||
|
CanvasQuizzes = await quizzesTask;
|
||||||
CanvasModules = await modulesTask;
|
CanvasModules = await modulesTask;
|
||||||
|
|
||||||
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
||||||
// Console.WriteLine(JsonSerializer.Serialize(CanvasModulesItems));
|
|
||||||
|
|
||||||
LoadingCanvasData = false;
|
LoadingCanvasData = false;
|
||||||
StateHasChanged?.Invoke();
|
StateHasChanged?.Invoke();
|
||||||
@@ -107,6 +110,7 @@ public class CoursePlanner
|
|||||||
|| LocalCourse.CanvasId == null
|
|| LocalCourse.CanvasId == null
|
||||||
|| CanvasAssignments == null
|
|| CanvasAssignments == null
|
||||||
|| CanvasModules == null
|
|| CanvasModules == null
|
||||||
|
|| CanvasQuizzes == null
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -138,9 +142,9 @@ public class CoursePlanner
|
|||||||
LocalCourse = await LocalCourse.SyncAssignmentsWithCanvas(canvasId, CanvasAssignments, canvas);
|
LocalCourse = await LocalCourse.SyncAssignmentsWithCanvas(canvasId, CanvasAssignments, canvas);
|
||||||
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
|
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
|
||||||
|
|
||||||
|
LocalCourse = await LocalCourse.SyncQuizzesWithCanvas(canvasId, CanvasQuizzes, canvas);
|
||||||
|
|
||||||
|
await LocalCourse.SyncModuleItemsWithCanvas(canvasId, CanvasModulesItems, canvas);
|
||||||
await syncModuleItemsWithCanvas(canvasId);
|
|
||||||
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
||||||
|
|
||||||
LoadingCanvasData = false;
|
LoadingCanvasData = false;
|
||||||
@@ -148,94 +152,6 @@ public class CoursePlanner
|
|||||||
Console.WriteLine("done syncing with canvas\n");
|
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()
|
public void Clear()
|
||||||
{
|
{
|
||||||
LocalCourse = null;
|
LocalCourse = null;
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ namespace Management.Planner;
|
|||||||
|
|
||||||
public static partial class AssignmentSyncronizationExtensions
|
public static partial class AssignmentSyncronizationExtensions
|
||||||
{
|
{
|
||||||
|
|
||||||
internal static async Task<LocalAssignment> SyncAssignmentToCanvas(
|
internal static async Task<LocalAssignment> SyncAssignmentToCanvas(
|
||||||
this LocalCourse localCourse,
|
this LocalCourse localCourse,
|
||||||
ulong canvasId,
|
ulong canvasCourseId,
|
||||||
LocalAssignment localAssignment,
|
LocalAssignment localAssignment,
|
||||||
IEnumerable<CanvasAssignment> canvasAssignments,
|
IEnumerable<CanvasAssignment> canvasAssignments,
|
||||||
CanvasService canvas
|
CanvasService canvas
|
||||||
@@ -25,23 +24,41 @@ public static partial class AssignmentSyncronizationExtensions
|
|||||||
localCourse.AssignmentTemplates
|
localCourse.AssignmentTemplates
|
||||||
);
|
);
|
||||||
|
|
||||||
if (canvasAssignment != null)
|
return canvasAssignment != null
|
||||||
{
|
? await updateAssignmentIfNeeded(
|
||||||
var assignmentNeedsUpdates = localAssignment.NeedsUpdates(
|
localCourse,
|
||||||
|
canvasCourseId,
|
||||||
|
localAssignment,
|
||||||
canvasAssignments,
|
canvasAssignments,
|
||||||
localCourse.AssignmentTemplates,
|
canvas,
|
||||||
quiet: false
|
localHtmlDescription
|
||||||
);
|
)
|
||||||
if (assignmentNeedsUpdates)
|
: await canvas.Assignments.Create(canvasCourseId, localAssignment, localHtmlDescription);
|
||||||
{
|
}
|
||||||
await canvas.Assignments.Update(courseId: canvasId, localAssignment, localHtmlDescription);
|
|
||||||
}
|
private static async Task<LocalAssignment> updateAssignmentIfNeeded(
|
||||||
return localAssignment;
|
LocalCourse localCourse,
|
||||||
}
|
ulong canvasCourseId,
|
||||||
else
|
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(
|
public static bool NeedsUpdates(
|
||||||
@@ -168,7 +185,7 @@ public static partial class AssignmentSyncronizationExtensions
|
|||||||
|
|
||||||
internal static async Task<LocalCourse> SyncAssignmentsWithCanvas(
|
internal static async Task<LocalCourse> SyncAssignmentsWithCanvas(
|
||||||
this LocalCourse localCourse,
|
this LocalCourse localCourse,
|
||||||
ulong canvasId,
|
ulong canvasCourseId,
|
||||||
IEnumerable<CanvasAssignment> canvasAssignments,
|
IEnumerable<CanvasAssignment> canvasAssignments,
|
||||||
CanvasService canvas
|
CanvasService canvas
|
||||||
)
|
)
|
||||||
@@ -176,7 +193,7 @@ public static partial class AssignmentSyncronizationExtensions
|
|||||||
var moduleTasks = localCourse.Modules.Select(async m =>
|
var moduleTasks = localCourse.Modules.Select(async m =>
|
||||||
{
|
{
|
||||||
var assignmentTasks = m.Assignments.Select(
|
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);
|
var assignments = await Task.WhenAll(assignmentTasks);
|
||||||
return m with { Assignments = assignments };
|
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
|
public static partial class QuizSyncronizationExtensions
|
||||||
{
|
{
|
||||||
internal static async Task<LocalQuiz> SyncQuizToCanvas(
|
public static bool QuizIsCreated(this LocalQuiz localQuiz, IEnumerable<CanvasQuiz> canvasQuizzes)
|
||||||
this LocalCourse localCourse,
|
|
||||||
ulong canvasId,
|
|
||||||
LocalQuiz localQuiz,
|
|
||||||
IEnumerable<CanvasQuiz> canvasQuizzes,
|
|
||||||
CanvasService canvas
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
// TODO actually sync
|
return canvasQuizzes.Any(q => q.Id == localQuiz.CanvasId);
|
||||||
return localQuiz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static async Task<LocalCourse> SyncQuizzesWithCanvas(
|
internal static async Task<LocalCourse> SyncQuizzesWithCanvas(
|
||||||
@@ -38,6 +31,28 @@ public static partial class QuizSyncronizationExtensions
|
|||||||
});
|
});
|
||||||
|
|
||||||
var modules = await Task.WhenAll(moduleTasks);
|
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;
|
namespace CanvasModel.Quizzes;
|
||||||
|
|
||||||
public record CanvasQuiz(
|
public record CanvasQuiz
|
||||||
[property: JsonPropertyName("id")] ulong Id,
|
{
|
||||||
[property: JsonPropertyName("title")] string Title,
|
[JsonPropertyName("id")]
|
||||||
[property: JsonPropertyName("html_url")] string HtmlUrl,
|
public ulong Id { get; init; }
|
||||||
[property: JsonPropertyName("mobile_url")] string MobileUrl,
|
|
||||||
[property: JsonPropertyName("preview_url")] string PreviewUrl,
|
[JsonPropertyName("title")]
|
||||||
[property: JsonPropertyName("description")] string Description,
|
public required string Title { get; init; }
|
||||||
[property: JsonPropertyName("quiz_type")] string QuizType,
|
|
||||||
[property: JsonPropertyName("assignment_group_id")] ulong AssignmentGroupId,
|
[JsonPropertyName("html_url")]
|
||||||
[property: JsonPropertyName("time_limit")] decimal? TimeLimit,
|
public required string HtmlUrl { get; init; }
|
||||||
[property: JsonPropertyName("shuffle_answers")] bool? ShuffleAnswers,
|
|
||||||
[property: JsonPropertyName("hide_results")] string? HideResults,
|
[JsonPropertyName("mobile_url")]
|
||||||
[property: JsonPropertyName("show_correct_answers")] bool? ShowCorrectAnswers,
|
public required string MobileUrl { get; init; }
|
||||||
[property: JsonPropertyName("show_correct_answers_last_attempt")]
|
|
||||||
bool? ShowCorrectAnswersLastAttempt,
|
[JsonPropertyName("preview_url")]
|
||||||
[property: JsonPropertyName("show_correct_answers_at")] DateTime? ShowCorrectAnswersAt,
|
public string? PreviewUrl { get; init; }
|
||||||
[property: JsonPropertyName("hide_correct_answers_at")] DateTime? HideCorrectAnswersAt,
|
|
||||||
[property: JsonPropertyName("one_time_results")] bool? OneTimeResults,
|
[JsonPropertyName("description")]
|
||||||
[property: JsonPropertyName("scoring_policy")] string? ScoringPolicy,
|
public required string Description { get; init; }
|
||||||
[property: JsonPropertyName("allowed_attempts")] int AllowedAttempts,
|
|
||||||
[property: JsonPropertyName("one_question_at_a_time")] bool? OneQuestionAtATime,
|
[JsonPropertyName("quiz_type")]
|
||||||
[property: JsonPropertyName("question_count")] uint? QuestionCount,
|
public required string QuizType { get; init; }
|
||||||
[property: JsonPropertyName("points_possible")] decimal? PointsPossible,
|
|
||||||
[property: JsonPropertyName("cant_go_back")] bool? CantGoBack,
|
[JsonPropertyName("assignment_group_id")]
|
||||||
[property: JsonPropertyName("access_code")] string? AccessCode,
|
public ulong? AssignmentGroupId { get; init; }
|
||||||
[property: JsonPropertyName("ip_filter")] string? IpFilter,
|
|
||||||
[property: JsonPropertyName("due_at")] DateTime? DueAt,
|
[JsonPropertyName("time_limit")]
|
||||||
[property: JsonPropertyName("lock_at")] DateTime? LockAt,
|
public decimal? TimeLimit { get; init; }
|
||||||
[property: JsonPropertyName("unlock_at")] DateTime? UnlockAt,
|
|
||||||
[property: JsonPropertyName("published")] bool? Published,
|
[JsonPropertyName("shuffle_answers")]
|
||||||
[property: JsonPropertyName("unpublishable")] bool? Unpublishable,
|
public bool? ShuffleAnswers { get; init; }
|
||||||
[property: JsonPropertyName("locked_for_user")] bool? LockedForUser,
|
|
||||||
[property: JsonPropertyName("lock_info")] CanvasLockInfo? LockInfo,
|
[JsonPropertyName("hide_results")]
|
||||||
[property: JsonPropertyName("lock_explanation")] string? LockExplanation,
|
public string? HideResults { get; init; }
|
||||||
[property: JsonPropertyName("speedgrader_url")] string? SpeedGraderUrl,
|
|
||||||
[property: JsonPropertyName("quiz_extensions_url")] string QuizExtensionsUrl,
|
[JsonPropertyName("show_correct_answers")]
|
||||||
[property: JsonPropertyName("permissions")] CanvasQuizPermissions Permissions,
|
public bool? ShowCorrectAnswers { get; init; }
|
||||||
[property: JsonPropertyName("all_dates")] object AllDates,
|
|
||||||
[property: JsonPropertyName("version_number")] uint? VersionNumber,
|
[JsonPropertyName("show_correct_answers_last_attempt")]
|
||||||
[property: JsonPropertyName("question_types")] IEnumerable<string> QuestionTypes,
|
public bool? ShowCorrectAnswersLastAttempt { get; init; }
|
||||||
[property: JsonPropertyName("anonymous_submissions")] bool? AnonymousSubmissions
|
|
||||||
);
|
[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 DateTime DueAt { get; init; }
|
||||||
public bool ShuffleAnswers { get; init; }
|
public bool ShuffleAnswers { get; init; }
|
||||||
public bool OneQuestionAtATime { 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; } =
|
public IEnumerable<LocalQuizQuestion> Questions { get; init; } =
|
||||||
Enumerable.Empty<LocalQuizQuestion>();
|
Enumerable.Empty<LocalQuizQuestion>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ namespace LocalModels;
|
|||||||
|
|
||||||
public record LocalQuizQuestion
|
public record LocalQuizQuestion
|
||||||
{
|
{
|
||||||
|
public ulong? CanvasId { get; set; }
|
||||||
public string Id { get; set; } = "";
|
public string Id { get; set; } = "";
|
||||||
public string Text { get; init; } = string.Empty;
|
public string Text { get; init; } = string.Empty;
|
||||||
public string QuestionType { get; init; } = string.Empty;
|
public string QuestionType { get; init; } = string.Empty;
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ namespace LocalModels;
|
|||||||
|
|
||||||
public record LocalQuizQuestionAnswer
|
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
|
//correct gets a weight of 100 in canvas
|
||||||
public bool Correct { get; init; }
|
public bool Correct { get; init; }
|
||||||
|
|||||||
@@ -30,13 +30,13 @@ public class CanvasAssignmentService
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<LocalAssignment> Create(
|
public async Task<LocalAssignment> Create(
|
||||||
ulong courseId,
|
ulong canvasCourseId,
|
||||||
LocalAssignment localAssignment,
|
LocalAssignment localAssignment,
|
||||||
string htmlDescription
|
string htmlDescription
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"creating assignment: {localAssignment.Name}");
|
Console.WriteLine($"creating assignment: {localAssignment.Name}");
|
||||||
var url = $"courses/{courseId}/assignments";
|
var url = $"courses/{canvasCourseId}/assignments";
|
||||||
var request = new RestRequest(url);
|
var request = new RestRequest(url);
|
||||||
var body = new CanvasAssignmentCreationRequest()
|
var body = new CanvasAssignmentCreationRequest()
|
||||||
{
|
{
|
||||||
@@ -47,7 +47,6 @@ public class CanvasAssignmentService
|
|||||||
lock_at = localAssignment.LockAt,
|
lock_at = localAssignment.LockAt,
|
||||||
points_possible = localAssignment.PointsPossible
|
points_possible = localAssignment.PointsPossible
|
||||||
};
|
};
|
||||||
request.AddHeader("Content-Type", "application/json");
|
|
||||||
var bodyObj = new { assignment = body };
|
var bodyObj = new { assignment = body };
|
||||||
request.AddBody(bodyObj);
|
request.AddBody(bodyObj);
|
||||||
var (canvasAssignment, response) = await webRequestor.PostAsync<CanvasAssignment>(request);
|
var (canvasAssignment, response) = await webRequestor.PostAsync<CanvasAssignment>(request);
|
||||||
@@ -56,7 +55,7 @@ public class CanvasAssignmentService
|
|||||||
|
|
||||||
var updatedLocalAssignment = localAssignment with { CanvasId = canvasAssignment.Id };
|
var updatedLocalAssignment = localAssignment with { CanvasId = canvasAssignment.Id };
|
||||||
|
|
||||||
await CreateRubric(courseId, updatedLocalAssignment);
|
await CreateRubric(canvasCourseId, updatedLocalAssignment);
|
||||||
|
|
||||||
return updatedLocalAssignment;
|
return updatedLocalAssignment;
|
||||||
}
|
}
|
||||||
@@ -75,7 +74,6 @@ public class CanvasAssignmentService
|
|||||||
lock_at = localAssignment.LockAt,
|
lock_at = localAssignment.LockAt,
|
||||||
points_possible = localAssignment.PointsPossible
|
points_possible = localAssignment.PointsPossible
|
||||||
};
|
};
|
||||||
request.AddHeader("Content-Type", "application/json");
|
|
||||||
var bodyObj = new { assignment = body };
|
var bodyObj = new { assignment = body };
|
||||||
request.AddBody(bodyObj);
|
request.AddBody(bodyObj);
|
||||||
Console.WriteLine(url);
|
Console.WriteLine(url);
|
||||||
@@ -145,7 +143,6 @@ public class CanvasAssignmentService
|
|||||||
var creationUrl = $"courses/{courseId}/rubrics";
|
var creationUrl = $"courses/{courseId}/rubrics";
|
||||||
var rubricCreationRequest = new RestRequest(creationUrl);
|
var rubricCreationRequest = new RestRequest(creationUrl);
|
||||||
rubricCreationRequest.AddBody(body);
|
rubricCreationRequest.AddBody(body);
|
||||||
rubricCreationRequest.AddHeader("Content-Type", "application/json");
|
|
||||||
var (rubricCreationResponse, _) = await webRequestor.PostAsync<CanvasRubricCreationResponse>(
|
var (rubricCreationResponse, _) = await webRequestor.PostAsync<CanvasRubricCreationResponse>(
|
||||||
rubricCreationRequest
|
rubricCreationRequest
|
||||||
);
|
);
|
||||||
@@ -160,7 +157,6 @@ public class CanvasAssignmentService
|
|||||||
var adjustmentUrl = $"courses/{courseId}/assignments/{localAssignment.CanvasId}";
|
var adjustmentUrl = $"courses/{courseId}/assignments/{localAssignment.CanvasId}";
|
||||||
var pointAdjustmentRequest = new RestRequest(adjustmentUrl);
|
var pointAdjustmentRequest = new RestRequest(adjustmentUrl);
|
||||||
pointAdjustmentRequest.AddBody(assignmentPointCorrectionBody);
|
pointAdjustmentRequest.AddBody(assignmentPointCorrectionBody);
|
||||||
pointAdjustmentRequest.AddHeader("Content-Type", "application/json");
|
|
||||||
var (_, _) = await webRequestor.PutAsync<CanvasAssignment>(pointAdjustmentRequest);
|
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;
|
private readonly CanvasServiceUtils utils;
|
||||||
|
|
||||||
public CanvasAssignmentService Assignments { get; }
|
public CanvasAssignmentService Assignments { get; }
|
||||||
|
public CanvasQuizService Quizzes { get; }
|
||||||
|
|
||||||
public CanvasService(
|
public CanvasService(
|
||||||
IWebRequestor webRequestor,
|
IWebRequestor webRequestor,
|
||||||
CanvasServiceUtils utils,
|
CanvasServiceUtils utils,
|
||||||
CanvasAssignmentService Assignments
|
CanvasAssignmentService Assignments,
|
||||||
|
CanvasQuizService Quizzes
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.webRequestor = webRequestor;
|
this.webRequestor = webRequestor;
|
||||||
this.utils = utils;
|
this.utils = utils;
|
||||||
this.Assignments = Assignments;
|
this.Assignments = Assignments;
|
||||||
|
this.Quizzes = Quizzes;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<EnrollmentTermModel>> GetTerms()
|
public async Task<IEnumerable<EnrollmentTermModel>> GetTerms()
|
||||||
@@ -51,8 +54,8 @@ public class CanvasService
|
|||||||
|
|
||||||
if (data == null)
|
if (data == null)
|
||||||
{
|
{
|
||||||
System.Console.WriteLine(response.Content);
|
Console.WriteLine(response.Content);
|
||||||
System.Console.WriteLine(response.ResponseUri);
|
Console.WriteLine(response.ResponseUri);
|
||||||
throw new Exception("error getting course from canvas");
|
throw new Exception("error getting course from canvas");
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
@@ -151,7 +154,6 @@ public class CanvasService
|
|||||||
var body = new { module_item = new { title = item.Title, position = item.Position } };
|
var body = new { module_item = new { title = item.Title, position = item.Position } };
|
||||||
var request = new RestRequest(url);
|
var request = new RestRequest(url);
|
||||||
request.AddBody(body);
|
request.AddBody(body);
|
||||||
request.AddHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
var (newItem, response) = await webRequestor.PutAsync<CanvasModuleItem>(request);
|
var (newItem, response) = await webRequestor.PutAsync<CanvasModuleItem>(request);
|
||||||
if (newItem == null)
|
if (newItem == null)
|
||||||
@@ -179,7 +181,6 @@ public class CanvasService
|
|||||||
};
|
};
|
||||||
var request = new RestRequest(url);
|
var request = new RestRequest(url);
|
||||||
request.AddBody(body);
|
request.AddBody(body);
|
||||||
request.AddHeader("Content-Type", "application/json");
|
|
||||||
|
|
||||||
var (newItem, response) = await webRequestor.PostAsync<CanvasModuleItem>(request);
|
var (newItem, response) = await webRequestor.PostAsync<CanvasModuleItem>(request);
|
||||||
if (newItem == null)
|
if (newItem == null)
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ public class WebRequestor : IWebRequestor
|
|||||||
|
|
||||||
public async Task<RestResponse> PostAsync(RestRequest request)
|
public async Task<RestResponse> PostAsync(RestRequest request)
|
||||||
{
|
{
|
||||||
|
request.AddHeader("Content-Type", "application/json");
|
||||||
var response = await client.ExecutePostAsync(request);
|
var response = await client.ExecutePostAsync(request);
|
||||||
if (!response.IsSuccessful)
|
if (!response.IsSuccessful)
|
||||||
{
|
{
|
||||||
@@ -43,6 +44,7 @@ public class WebRequestor : IWebRequestor
|
|||||||
|
|
||||||
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
public async Task<(T?, RestResponse)> PostAsync<T>(RestRequest request)
|
||||||
{
|
{
|
||||||
|
request.AddHeader("Content-Type", "application/json");
|
||||||
var response = await client.ExecutePostAsync(request);
|
var response = await client.ExecutePostAsync(request);
|
||||||
return (deserialize<T>(response), response);
|
return (deserialize<T>(response), response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,46 @@
|
|||||||
|
|
||||||
GET https://snow.instructure.com/api/v1/courses/705168
|
GET https://snow.instructure.com/api/v1/courses/871954/quizzes
|
||||||
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||||
|
|
||||||
|
###
|
||||||
|
GET https://snow.instructure.com/api/v1/courses/871954/assignments
|
||||||
|
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||||
|
|
||||||
|
###
|
||||||
|
POST https://snow.instructure.com/api/v1/courses/871954/quizzes/3236013/questions
|
||||||
|
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"question":{
|
||||||
|
"question_text": "Other clues to how things work come from their visible structure. Specifically from _____, _____, and _____",
|
||||||
|
"question_type": "multiple_answers_question",
|
||||||
|
"points_possible": 3,
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"answer_text": "color",
|
||||||
|
"answer_weight": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer_text": "affordances",
|
||||||
|
"answer_weight": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer_text": "structure",
|
||||||
|
"answer_weight": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer_text": "constraints",
|
||||||
|
"answer_weight": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer_text": "mappings",
|
||||||
|
"answer_weight": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"answer_text": "placement",
|
||||||
|
"answer_weight": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user