mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 23:28:33 -06:00
i liked syncing more before i cared about order
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,4 +3,5 @@ bin/
|
|||||||
.env
|
.env
|
||||||
*.env
|
*.env
|
||||||
storage/
|
storage/
|
||||||
tmp.json
|
tmp.json
|
||||||
|
tmp*.json
|
||||||
@@ -95,6 +95,13 @@
|
|||||||
>
|
>
|
||||||
Sync With Canvas
|
Sync With Canvas
|
||||||
</button>
|
</button>
|
||||||
|
<a
|
||||||
|
class="btn btn-outline-secondary"
|
||||||
|
target="_blank"
|
||||||
|
href="@($"{Environment.GetEnvironmentVariable("CANVAS_URL")}/courses/{planner.LocalCourse.CanvasId}")"
|
||||||
|
>
|
||||||
|
View In Canvas
|
||||||
|
</a>
|
||||||
|
|
||||||
@if(planner.LoadingCanvasData)
|
@if(planner.LoadingCanvasData)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ global using CanvasModel.EnrollmentTerms;
|
|||||||
global using CanvasModel.Courses;
|
global using CanvasModel.Courses;
|
||||||
global using CanvasModel;
|
global using CanvasModel;
|
||||||
global using LocalModels;
|
global using LocalModels;
|
||||||
|
global using Management.Planner;
|
||||||
global using Management.Web.Shared.Components;
|
global using Management.Web.Shared.Components;
|
||||||
global using Management.Web.Shared.Course;
|
global using Management.Web.Shared.Course;
|
||||||
|
|
||||||
@@ -17,6 +18,16 @@ DotEnv.Load();
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
var canvas_token = Environment.GetEnvironmentVariable("CANVAS_TOKEN");
|
||||||
|
if (canvas_token == null)
|
||||||
|
throw new Exception("CANVAS_TOKEN is null");
|
||||||
|
var canvas_url = Environment.GetEnvironmentVariable("CANVAS_URL");
|
||||||
|
if (canvas_url == null)
|
||||||
|
{
|
||||||
|
Console.WriteLine("CANVAS_URL is null, defaulting to https://snow.instructure.com");
|
||||||
|
Environment.SetEnvironmentVariable("CANVAS_URL", "https://snow.instructure.com");
|
||||||
|
}
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|
||||||
@if (localCourses != null)
|
@if (localCourses != null)
|
||||||
{
|
{
|
||||||
<h3 >Stored Courses</h3>
|
<h3 >Stored Courses</h3>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
private string name { get; set; } = String.Empty;
|
private string name { get; set; } = String.Empty;
|
||||||
private bool lockAtDueDate { get; set; }
|
private bool lockAtDueDate { get; set; }
|
||||||
private IEnumerable<RubricItem> rubric { get; set; } = Enumerable.Empty<RubricItem>();
|
private IEnumerable<RubricItem> rubric { get; set; } = Enumerable.Empty<RubricItem>();
|
||||||
private IEnumerable<SubmissionType> submissionTypes { get; set; } = Enumerable.Empty<SubmissionType>();
|
private IEnumerable<string> submissionTypes { get; set; } = Enumerable.Empty<string>();
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
@using System.Reflection
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[Parameter, EditorRequired]
|
[Parameter, EditorRequired]
|
||||||
public IEnumerable<SubmissionType> Types { get; set; } = Enumerable.Empty<SubmissionType>();
|
public IEnumerable<string> Types { get; set; } = Enumerable.Empty<string>();
|
||||||
|
|
||||||
[Parameter, EditorRequired]
|
[Parameter, EditorRequired]
|
||||||
public Action<IEnumerable<SubmissionType>> SetTypes { get; set; } = (_) => {};
|
public Action<IEnumerable<string>> SetTypes { get; set; } = (_) => {};
|
||||||
private string getLabel(SubmissionType type)
|
private string getLabel(string type)
|
||||||
{
|
{
|
||||||
return type.ToString().Replace("_", "") + "switch";
|
return type.ToString().Replace("_", "") + "switch";
|
||||||
}
|
}
|
||||||
@@ -14,7 +16,7 @@
|
|||||||
<h5>Submission Types</h5>
|
<h5>Submission Types</h5>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
@foreach (var submissionType in (SubmissionType[])Enum.GetValues(typeof(SubmissionType)))
|
@foreach (var submissionType in SubmissionType.AllTypes)
|
||||||
{
|
{
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
lock_at = null,
|
lock_at = null,
|
||||||
due_at = DateTime.Now,
|
due_at = DateTime.Now,
|
||||||
points_possible = 10,
|
points_possible = 10,
|
||||||
submission_types = new SubmissionType[] { SubmissionType.online_text_entry }
|
submission_types = new string[] { SubmissionType.online_text_entry }
|
||||||
};
|
};
|
||||||
|
|
||||||
if(planner.LocalCourse != null)
|
if(planner.LocalCourse != null)
|
||||||
|
|||||||
@@ -6,6 +6,18 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
[EditorRequired]
|
[EditorRequired]
|
||||||
public LocalAssignment Assignment { get; set; } = new();
|
public LocalAssignment Assignment { get; set; } = new();
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
planner.StateHasChanged += reload;
|
||||||
|
}
|
||||||
|
private void reload()
|
||||||
|
{
|
||||||
|
this.InvokeAsync(this.StateHasChanged);
|
||||||
|
}
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
planner.StateHasChanged -= reload;
|
||||||
|
}
|
||||||
|
|
||||||
private void dropOnDate(DateTime dropDate)
|
private void dropOnDate(DateTime dropDate)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ using CanvasModel.Modules;
|
|||||||
using Management.Services.Canvas;
|
using Management.Services.Canvas;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Management.Planner;
|
||||||
|
|
||||||
public class CoursePlanner
|
public class CoursePlanner
|
||||||
{
|
{
|
||||||
private readonly YamlManager yamlManager;
|
private readonly YamlManager yamlManager;
|
||||||
@@ -32,7 +34,8 @@ public class CoursePlanner
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var verifiedCourse = cleanupCourse(value);
|
var verifiedCourse = value.GeneralCourseCleanup();
|
||||||
|
|
||||||
// ignore initial load of course
|
// ignore initial load of course
|
||||||
if (_localCourse != null)
|
if (_localCourse != null)
|
||||||
{
|
{
|
||||||
@@ -44,53 +47,34 @@ public class CoursePlanner
|
|||||||
}
|
}
|
||||||
public event Action? StateHasChanged;
|
public event Action? StateHasChanged;
|
||||||
|
|
||||||
private LocalCourse cleanupCourse(LocalCourse incomingCourse)
|
public IEnumerable<CanvasAssignment>? CanvasAssignments { get; internal set; }
|
||||||
{
|
public IEnumerable<CanvasModule>? CanvasModules { get; internal set; }
|
||||||
var modulesWithUniqueAssignments = incomingCourse.Modules.Select(
|
public Dictionary<ulong, IEnumerable<CanvasModuleItem>>? CanvasModulesItems { get; internal set; }
|
||||||
module => module with { Assignments = module.Assignments.DistinctBy(a => a.id) }
|
|
||||||
);
|
|
||||||
|
|
||||||
return incomingCourse with
|
public async Task<(
|
||||||
{
|
IEnumerable<CanvasAssignment> CanvasAssignments,
|
||||||
Modules = modulesWithUniqueAssignments
|
IEnumerable<CanvasModule> CanvasModules,
|
||||||
};
|
Dictionary<ulong, IEnumerable<CanvasModuleItem>> CanvasModulesItems
|
||||||
}
|
)> LoadCanvasData()
|
||||||
|
|
||||||
private IEnumerable<CanvasAssignment>? canvasAssignments = null;
|
|
||||||
|
|
||||||
public IEnumerable<CanvasAssignment>? CanvasAssignments
|
|
||||||
{
|
|
||||||
get => canvasAssignments;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
canvasAssignments = value;
|
|
||||||
StateHasChanged?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private IEnumerable<CanvasModule>? canvasModules = null;
|
|
||||||
public IEnumerable<CanvasModule>? CanvasModules
|
|
||||||
{
|
|
||||||
get => canvasModules;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
canvasModules = value;
|
|
||||||
StateHasChanged?.Invoke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LoadCanvasData()
|
|
||||||
{
|
{
|
||||||
LoadingCanvasData = true;
|
LoadingCanvasData = true;
|
||||||
StateHasChanged?.Invoke();
|
StateHasChanged?.Invoke();
|
||||||
|
|
||||||
Thread.Sleep(1000);
|
|
||||||
var canvasId =
|
var canvasId =
|
||||||
LocalCourse?.CanvasId ?? throw new Exception("no canvas id found for selected course");
|
LocalCourse?.CanvasId ?? throw new Exception("no canvas id found for selected course");
|
||||||
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
|
|
||||||
CanvasModules = await canvas.GetModules(canvasId);
|
var assignmentsTask = canvas.Assignments.GetAll(canvasId);
|
||||||
|
var modulesTask = canvas.GetModules(canvasId);
|
||||||
|
|
||||||
|
CanvasAssignments = await assignmentsTask;
|
||||||
|
CanvasModules = await modulesTask;
|
||||||
|
|
||||||
|
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
||||||
|
// Console.WriteLine(JsonSerializer.Serialize(CanvasModulesItems));
|
||||||
|
|
||||||
LoadingCanvasData = false;
|
LoadingCanvasData = false;
|
||||||
StateHasChanged?.Invoke();
|
StateHasChanged?.Invoke();
|
||||||
|
return (CanvasAssignments, CanvasModules, CanvasModulesItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SyncWithCanvas()
|
public async Task SyncWithCanvas()
|
||||||
@@ -107,22 +91,31 @@ public class CoursePlanner
|
|||||||
LoadingCanvasData = true;
|
LoadingCanvasData = true;
|
||||||
StateHasChanged?.Invoke();
|
StateHasChanged?.Invoke();
|
||||||
|
|
||||||
|
var (canvasAssignments, canvasModules, canvasModuleItems) = await LoadCanvasData();
|
||||||
|
LocalCourse = LocalCourse.deleteCanvasIdsThatNoLongerExist(canvasModules, canvasAssignments);
|
||||||
|
|
||||||
var canvasId =
|
var canvasId =
|
||||||
LocalCourse.CanvasId ?? throw new Exception("no course canvas id to sync with canvas");
|
LocalCourse.CanvasId ?? throw new Exception("no course canvas id to sync with canvas");
|
||||||
await ensureAllModulesCreated(canvasId);
|
|
||||||
await reloadModules_UpdateLocalModulesWithNewId(canvasId);
|
await ensureAllModulesExistInCanvas(canvasId);
|
||||||
|
CanvasModules = await canvas.GetModules(canvasId);
|
||||||
|
await sortCanvasModules(canvasId);
|
||||||
|
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
||||||
|
|
||||||
|
await syncModulesWithCanvasData(canvasId);
|
||||||
|
|
||||||
await syncAssignmentsWithCanvas(canvasId);
|
await syncAssignmentsWithCanvas(canvasId);
|
||||||
|
|
||||||
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
|
CanvasAssignments = await canvas.Assignments.GetAll(canvasId);
|
||||||
CanvasModules = await canvas.GetModules(canvasId);
|
|
||||||
|
await syncModuleItemsWithCanvas(canvasId);
|
||||||
|
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
|
||||||
|
|
||||||
LoadingCanvasData = false;
|
LoadingCanvasData = false;
|
||||||
StateHasChanged?.Invoke();
|
StateHasChanged?.Invoke();
|
||||||
Console.WriteLine("done syncing with canvas\n");
|
Console.WriteLine("done syncing with canvas\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task reloadModules_UpdateLocalModulesWithNewId(ulong canvasId)
|
private async Task syncModulesWithCanvasData(ulong canvasId)
|
||||||
{
|
{
|
||||||
if (LocalCourse == null)
|
if (LocalCourse == null)
|
||||||
return;
|
return;
|
||||||
@@ -138,7 +131,7 @@ public class CoursePlanner
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ensureAllModulesCreated(ulong canvasId)
|
private async Task ensureAllModulesExistInCanvas(ulong canvasId)
|
||||||
{
|
{
|
||||||
if (LocalCourse == null || CanvasModules == null)
|
if (LocalCourse == null || CanvasModules == null)
|
||||||
return;
|
return;
|
||||||
@@ -153,6 +146,27 @@ public class CoursePlanner
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task sortCanvasModules(ulong canvasId)
|
||||||
|
{
|
||||||
|
if (LocalCourse == null)
|
||||||
|
throw new Exception("cannot sort modules, no course selected");
|
||||||
|
if (CanvasModules == null)
|
||||||
|
throw new Exception("cannot sort modules, no canvas modules loaded");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task syncAssignmentsWithCanvas(ulong canvasId)
|
private async Task syncAssignmentsWithCanvas(ulong canvasId)
|
||||||
{
|
{
|
||||||
if (
|
if (
|
||||||
@@ -205,11 +219,7 @@ public class CoursePlanner
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return await canvas.Assignments.Create(
|
return await canvas.Assignments.Create(canvasId, localAssignment, localHtmlDescription);
|
||||||
canvasId,
|
|
||||||
localAssignment,
|
|
||||||
localHtmlDescription
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +294,94 @@ public class CoursePlanner
|
|||||||
|| !submissionTypesSame;
|
|| !submissionTypesSame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.due_at)
|
||||||
|
.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;
|
||||||
|
|||||||
83
Management/Features/Configuration/CoursePlannerExtensions.cs
Normal file
83
Management/Features/Configuration/CoursePlannerExtensions.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using CanvasModel.Assignments;
|
||||||
|
using CanvasModel.Modules;
|
||||||
|
using LocalModels;
|
||||||
|
|
||||||
|
namespace Management.Planner;
|
||||||
|
|
||||||
|
public static class CoursePlannerExtensions
|
||||||
|
{
|
||||||
|
public static LocalCourse GeneralCourseCleanup(this LocalCourse incomingCourse)
|
||||||
|
{
|
||||||
|
var modulesWithUniqueAssignments = incomingCourse.Modules.Select(
|
||||||
|
module =>
|
||||||
|
module with
|
||||||
|
{
|
||||||
|
Assignments = module.Assignments.OrderBy(a => a.due_at).DistinctBy(a => a.id)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return incomingCourse with
|
||||||
|
{
|
||||||
|
Modules = modulesWithUniqueAssignments
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LocalCourse deleteCanvasIdsThatNoLongerExist(
|
||||||
|
this LocalCourse localCourse,
|
||||||
|
IEnumerable<CanvasModule> canvasModules,
|
||||||
|
IEnumerable<CanvasAssignment> canvasAssignments
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Console.WriteLine("checking canvas ids still exist");
|
||||||
|
|
||||||
|
var correctedModules = localCourse.Modules
|
||||||
|
.Select((m) => m.validateCanvasIds(canvasModules, canvasAssignments))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
return localCourse with
|
||||||
|
{
|
||||||
|
Modules = correctedModules
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocalModule validateCanvasIds(
|
||||||
|
this LocalModule module,
|
||||||
|
IEnumerable<CanvasModule> canvasModules,
|
||||||
|
IEnumerable<CanvasAssignment> canvasAssignments
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var moduleIdInCanvas = canvasModules.FirstOrDefault(m => m.Id == module.CanvasId) != null;
|
||||||
|
var moduleWithAssignments = module with
|
||||||
|
{
|
||||||
|
Assignments = module.Assignments
|
||||||
|
.Select((a) => a.validateAssignmentForCanvasId(canvasAssignments))
|
||||||
|
.ToArray()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!moduleIdInCanvas)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"no id in canvas for module, removing old canvas id: {moduleWithAssignments.Name}"
|
||||||
|
);
|
||||||
|
return moduleWithAssignments with { CanvasId = null };
|
||||||
|
}
|
||||||
|
return moduleWithAssignments;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocalAssignment validateAssignmentForCanvasId(
|
||||||
|
this LocalAssignment assignment,
|
||||||
|
IEnumerable<CanvasAssignment> canvasAssignments
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var assignmentIdInCanvas =
|
||||||
|
canvasAssignments.FirstOrDefault(ca => ca.Id == assignment.canvasId) != null;
|
||||||
|
if (!assignmentIdInCanvas)
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"no id in canvas for assignment, removing old canvas id: {assignment.name}"
|
||||||
|
);
|
||||||
|
return assignment with { canvasId = null };
|
||||||
|
}
|
||||||
|
return assignment;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ namespace CanvasModel.Modules;
|
|||||||
public record CanvasModuleItem(
|
public record CanvasModuleItem(
|
||||||
[property: JsonPropertyName("id")] ulong Id,
|
[property: JsonPropertyName("id")] ulong Id,
|
||||||
[property: JsonPropertyName("module_id")] ulong ModuleId,
|
[property: JsonPropertyName("module_id")] ulong ModuleId,
|
||||||
[property: JsonPropertyName("position")] uint Position,
|
[property: JsonPropertyName("position")] int Position,
|
||||||
[property: JsonPropertyName("title")] string Title,
|
[property: JsonPropertyName("title")] string Title,
|
||||||
[property: JsonPropertyName("indent")] uint? Indent,
|
[property: JsonPropertyName("indent")] uint? Indent,
|
||||||
[property: JsonPropertyName("type")] string Type,
|
[property: JsonPropertyName("type")] string Type,
|
||||||
@@ -17,4 +17,4 @@ public record CanvasModuleItem(
|
|||||||
[property: JsonPropertyName("completion_requirement")]
|
[property: JsonPropertyName("completion_requirement")]
|
||||||
CanvasCompletionRequirement? CompletionRequirement,
|
CanvasCompletionRequirement? CompletionRequirement,
|
||||||
[property: JsonPropertyName("published")] bool? Published
|
[property: JsonPropertyName("published")] bool? Published
|
||||||
);
|
);
|
||||||
@@ -8,24 +8,37 @@ public record RubricItem
|
|||||||
public int Points { get; set; } = 0;
|
public int Points { get; set; } = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum SubmissionType
|
public static class SubmissionType
|
||||||
{
|
{
|
||||||
online_text_entry,
|
public static readonly string online_text_entry = "online_text_entry";
|
||||||
online_upload,
|
public static readonly string online_upload = "online_upload";
|
||||||
online_quiz,
|
public static readonly string online_quiz = "online_quiz";
|
||||||
on_paper,
|
public static readonly string on_paper = "on_paper";
|
||||||
discussion_topic,
|
public static readonly string discussion_topic = "discussion_topic";
|
||||||
external_tool,
|
public static readonly string external_tool = "external_tool";
|
||||||
online_url,
|
public static readonly string online_url = "online_url";
|
||||||
media_recording,
|
public static readonly string media_recording = "media_recording";
|
||||||
student_annotation,
|
public static readonly string student_annotation = "student_annotation";
|
||||||
none,
|
public static readonly string none = "none";
|
||||||
|
public static readonly IEnumerable<string> AllTypes = new string[]
|
||||||
|
{
|
||||||
|
SubmissionType.online_text_entry,
|
||||||
|
SubmissionType.online_upload,
|
||||||
|
SubmissionType.online_quiz,
|
||||||
|
SubmissionType.on_paper,
|
||||||
|
SubmissionType.discussion_topic,
|
||||||
|
SubmissionType.external_tool,
|
||||||
|
SubmissionType.online_url,
|
||||||
|
SubmissionType.media_recording,
|
||||||
|
SubmissionType.student_annotation,
|
||||||
|
SubmissionType.none,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public record LocalAssignment
|
public record LocalAssignment
|
||||||
{
|
{
|
||||||
public string id { get; init; } = "";
|
public string id { get; init; } = "";
|
||||||
public ulong? canvasId = null;
|
public ulong? canvasId { get; init; } = null;
|
||||||
public string name { get; init; } = "";
|
public string name { get; init; } = "";
|
||||||
public string description { get; init; } = "";
|
public string description { get; init; } = "";
|
||||||
public bool use_template { get; init; } = false;
|
public bool use_template { get; init; } = false;
|
||||||
@@ -37,7 +50,7 @@ public record LocalAssignment
|
|||||||
public DateTime? lock_at { get; init; }
|
public DateTime? lock_at { get; init; }
|
||||||
public DateTime due_at { get; init; }
|
public DateTime due_at { get; init; }
|
||||||
public int points_possible { get; init; }
|
public int points_possible { get; init; }
|
||||||
public IEnumerable<SubmissionType> submission_types { get; init; } = new SubmissionType[] { };
|
public IEnumerable<string> submission_types { get; init; } = new string[] { };
|
||||||
|
|
||||||
public string GetRubricHtml()
|
public string GetRubricHtml()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,12 +52,14 @@ public class CanvasAssignmentService
|
|||||||
if (canvasAssignment == null)
|
if (canvasAssignment == null)
|
||||||
throw new Exception("created canvas assignment was null");
|
throw new Exception("created canvas assignment was null");
|
||||||
|
|
||||||
await CreateRubric(courseId, localAssignment);
|
var updatedLocalAssignment = localAssignment with
|
||||||
|
|
||||||
return localAssignment with
|
|
||||||
{
|
{
|
||||||
canvasId = canvasAssignment.Id
|
canvasId = canvasAssignment.Id
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await CreateRubric(courseId, updatedLocalAssignment);
|
||||||
|
|
||||||
|
return updatedLocalAssignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Update(ulong courseId, LocalAssignment localAssignment, string htmlDescription)
|
public async Task Update(ulong courseId, LocalAssignment localAssignment, string htmlDescription)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using CanvasModel.Courses;
|
|||||||
using CanvasModel.EnrollmentTerms;
|
using CanvasModel.EnrollmentTerms;
|
||||||
using CanvasModel.Modules;
|
using CanvasModel.Modules;
|
||||||
using RestSharp;
|
using RestSharp;
|
||||||
|
|
||||||
namespace Management.Services.Canvas;
|
namespace Management.Services.Canvas;
|
||||||
|
|
||||||
public class CanvasService
|
public class CanvasService
|
||||||
@@ -75,6 +76,53 @@ public class CanvasService
|
|||||||
await webRequestor.PostAsync(request);
|
await webRequestor.PostAsync(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateModule(ulong courseId, ulong moduleId, string name, int position)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Updating Module: {name}");
|
||||||
|
var url = $"courses/{courseId}/modules/{moduleId}";
|
||||||
|
var body = new { module = new { name = name, position = position } };
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
request.AddBody(body);
|
||||||
|
|
||||||
|
await webRequestor.PutAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IEnumerable<CanvasModuleItem>> GetModuleItems(ulong courseId, ulong moduleId)
|
||||||
|
{
|
||||||
|
var url = $"courses/{courseId}/modules/{moduleId}/items";
|
||||||
|
var request = new RestRequest(url);
|
||||||
|
var (items, response) = await webRequestor.GetAsync<IEnumerable<CanvasModuleItem>>(request);
|
||||||
|
if (items == null)
|
||||||
|
throw new Exception($"Error getting canvas module items for {url}");
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Dictionary<ulong, IEnumerable<CanvasModuleItem>>> GetAllModulesItems(
|
||||||
|
ulong courseId,
|
||||||
|
IEnumerable<CanvasModule> modules
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var itemsTasks = modules.Select(
|
||||||
|
async (m) =>
|
||||||
|
{
|
||||||
|
var items = await GetModuleItems(courseId, m.Id);
|
||||||
|
return (m, items);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var output = new Dictionary<ulong, IEnumerable<CanvasModuleItem>>();
|
||||||
|
var itemTasksResult = await Task.WhenAll(itemsTasks);
|
||||||
|
foreach (var (module, items) in itemTasksResult)
|
||||||
|
{
|
||||||
|
if (module == null || items == null)
|
||||||
|
throw new Exception(
|
||||||
|
"i'm not sure how we got here, but module and items are null after looking up module items"
|
||||||
|
);
|
||||||
|
output[module.Id] = items;
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<EnrollmentTermModel>> GetCurrentTermsFor(
|
public async Task<IEnumerable<EnrollmentTermModel>> GetCurrentTermsFor(
|
||||||
DateTime? _queryDate = null
|
DateTime? _queryDate = null
|
||||||
)
|
)
|
||||||
@@ -90,4 +138,50 @@ public class CanvasService
|
|||||||
|
|
||||||
return currentTerms;
|
return currentTerms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateModuleItem(
|
||||||
|
ulong canvasCourseId,
|
||||||
|
ulong canvasModuleId,
|
||||||
|
CanvasModuleItem item
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"updating module item {item.Title}");
|
||||||
|
var url = $"courses/{canvasCourseId}/modules/{canvasModuleId}/items/{item.Id}";
|
||||||
|
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)
|
||||||
|
throw new Exception("something went wrong updating module item");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CreateModuleItem(
|
||||||
|
ulong canvasCourseId,
|
||||||
|
ulong canvasModuleId,
|
||||||
|
string title,
|
||||||
|
string type,
|
||||||
|
ulong contentId
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"creating new module item {title}");
|
||||||
|
var url = $"courses/{canvasCourseId}/modules/{canvasModuleId}/items";
|
||||||
|
var body = new
|
||||||
|
{
|
||||||
|
module_item = new
|
||||||
|
{
|
||||||
|
title = title,
|
||||||
|
type = type.ToString(),
|
||||||
|
content_id = contentId,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
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)
|
||||||
|
throw new Exception("something went wrong updating module item");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using RestSharp;
|
|||||||
|
|
||||||
public class WebRequestor : IWebRequestor
|
public class WebRequestor : IWebRequestor
|
||||||
{
|
{
|
||||||
private const string BaseUrl = "https://snow.instructure.com/api/v1/";
|
private string BaseUrl = Environment.GetEnvironmentVariable("CANVAS_URL") + "/api/v1/";
|
||||||
private string token;
|
private string token;
|
||||||
private RestClient client;
|
private RestClient client;
|
||||||
|
|
||||||
@@ -49,13 +49,13 @@ public class WebRequestor : IWebRequestor
|
|||||||
public async Task<RestResponse> PutAsync(RestRequest request)
|
public async Task<RestResponse> PutAsync(RestRequest request)
|
||||||
{
|
{
|
||||||
var response = await client.ExecutePutAsync(request);
|
var response = await client.ExecutePutAsync(request);
|
||||||
if (!response.IsSuccessful)
|
// if (!response.IsSuccessful)
|
||||||
{
|
// {
|
||||||
System.Console.WriteLine(response.Content);
|
// System.Console.WriteLine(response.Content);
|
||||||
System.Console.WriteLine(response.ResponseUri);
|
// System.Console.WriteLine(response.ResponseUri);
|
||||||
System.Console.WriteLine("error with response");
|
// System.Console.WriteLine("error with response");
|
||||||
throw new Exception("error with response");
|
// throw new Exception("error with response");
|
||||||
}
|
// }
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,23 +79,20 @@ public class WebRequestor : IWebRequestor
|
|||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
try
|
var data = JsonSerializer.Deserialize<T>(response.Content!);
|
||||||
{
|
|
||||||
var data = JsonSerializer.Deserialize<T>(response.Content!);
|
|
||||||
|
|
||||||
if (data == null)
|
if (data == null)
|
||||||
{
|
|
||||||
System.Console.WriteLine(response.Content);
|
|
||||||
System.Console.WriteLine(response.ResponseUri);
|
|
||||||
System.Console.WriteLine("could not parse response, got empty object");
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
catch (System.NotSupportedException exception)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine(response.Content);
|
System.Console.WriteLine(response.Content);
|
||||||
throw exception;
|
System.Console.WriteLine(response.ResponseUri);
|
||||||
|
System.Console.WriteLine("could not parse response, got empty object");
|
||||||
}
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
catch (System.NotSupportedException exception)
|
||||||
|
{
|
||||||
|
Console.WriteLine(response.Content);
|
||||||
|
throw exception;
|
||||||
}
|
}
|
||||||
catch (JsonException ex)
|
catch (JsonException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ public class YamlManager
|
|||||||
var serializer = new SerializerBuilder().Build();
|
var serializer = new SerializerBuilder().Build();
|
||||||
var yaml = serializer.Serialize(course);
|
var yaml = serializer.Serialize(course);
|
||||||
|
|
||||||
// System.Console.WriteLine(yaml);
|
|
||||||
return yaml;
|
return yaml;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,8 +16,8 @@ public class YamlManager
|
|||||||
{
|
{
|
||||||
var deserializer = new DeserializerBuilder().Build();
|
var deserializer = new DeserializerBuilder().Build();
|
||||||
|
|
||||||
var person = deserializer.Deserialize<LocalCourse>(rawCourse);
|
var course = deserializer.Deserialize<LocalCourse>(rawCourse);
|
||||||
return person;
|
return course;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SaveCourseAsync(LocalCourse course)
|
public async Task SaveCourseAsync(LocalCourse course)
|
||||||
@@ -44,7 +43,9 @@ public class YamlManager
|
|||||||
var fileNames = Directory.GetFiles(path);
|
var fileNames = Directory.GetFiles(path);
|
||||||
|
|
||||||
var courses = await Task.WhenAll(
|
var courses = await Task.WhenAll(
|
||||||
fileNames.Select(async n => ParseCourse(await File.ReadAllTextAsync($"../storage/{n}")))
|
fileNames
|
||||||
|
.Where(name => name.EndsWith(".yml"))
|
||||||
|
.Select(async n => ParseCourse(await File.ReadAllTextAsync($"../storage/{n}")))
|
||||||
);
|
);
|
||||||
return courses;
|
return courses;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user