am syncing assignment groups

This commit is contained in:
2023-08-23 14:06:40 -06:00
parent 8c3ab09f38
commit bbae0c054d
18 changed files with 327 additions and 11 deletions

View File

@@ -103,6 +103,9 @@
>
View In Canvas
</a>
<div class="my-auto ms-2 d-inline">
@planner.LocalCourse.Name
</div>
</div>
@if(planner.LoadingCanvasData)

View File

@@ -35,6 +35,7 @@ builder.Services.AddServerSideBlazor();
builder.Services.AddScoped<IWebRequestor, WebRequestor>();
builder.Services.AddScoped<CanvasServiceUtils>();
builder.Services.AddScoped<CanvasAssignmentService>();
builder.Services.AddScoped<CanvasAssignmentGroupService>();
builder.Services.AddScoped<CanvasQuizService>();
builder.Services.AddScoped<CanvasService, CanvasService>();

View File

@@ -25,9 +25,12 @@
assignmentContext.StateHasChanged -= reload;
}
[Parameter]
public Action OnHide { get; set; } = () => { };
private void OnHide()
{
assignmentContext.Assignment = null;
name = "";
lockAtDueDate = false;
}
public Modal? AssignmentModal { get; set; } = null;
private string name { get; set; } = String.Empty;
private bool lockAtDueDate { get; set; }
@@ -111,7 +114,7 @@
}
}
<Modal @ref="AssignmentModal" OnHide="@(() => OnHide())">
<Modal @ref="AssignmentModal" OnHide="OnHide" Size="xl">
<Title>
@assignmentContext.Assignment?.Name
</Title>

View File

@@ -14,6 +14,9 @@
[Parameter]
public Action OnHide { get; set; } = () => { };
[Parameter]
public string Size { get; set; } = "xl"; //sm, lg, xl, xxl
private string modalClass = "hide-modal";
private bool showBackdrop = false;
public void Show()
@@ -33,7 +36,7 @@
}
<div class="modal @modalClass" @onmousedown="Hide">
<div class="modal-dialog modal-xl" role="document" @onmousedown:stopPropagation="true">
<div class="@($"modal-dialog modal-{Size}")" role="document" @onmousedown:stopPropagation="true">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title text-center w-100">@Title</h4>

View File

@@ -22,7 +22,7 @@
}
private string text { get; set; } = string.Empty;
private string questionType { get; set; } = string.Empty;
private int points { get; set; }
private int points { get; set; } = 1;
private IEnumerable<LocalQuizQuestionAnswer> answers { get; set; } = Enumerable.Empty<LocalQuizQuestionAnswer>();
private void handleTypeUpdate(string type)

View File

@@ -0,0 +1,113 @@
@using Management.Web.Shared.Components
@inject CanvasService canvas
@inject CoursePlanner planner
@code {
protected override void OnInitialized()
{
planner.StateHasChanged += reload;
}
private void reload()
{
this.InvokeAsync(this.StateHasChanged);
}
public void Dispose()
{
planner.StateHasChanged -= reload;
}
private void AddAssignmentGroup()
{
if(planner.LocalCourse != null)
{
var newGroup = new LocalAssignmentGroup
{
Name = "",
Weight = 0,
Id = Guid.NewGuid().ToString()
};
var updatedGroups = planner.LocalCourse.AssignmentGroups.Append(newGroup);
planner.LocalCourse = planner.LocalCourse with
{
AssignmentGroups = updatedGroups
};
}
}
private Action<ChangeEventArgs> saveGroupName(string groupId)
{
return (e) =>
{
if(planner.LocalCourse != null)
{
var newName = e.Value?.ToString() ?? "";
var newGroups = planner.LocalCourse.AssignmentGroups.Select(
g => g.Id == groupId
? g with { Name = newName }
: g
);
planner.LocalCourse = planner.LocalCourse with
{
AssignmentGroups = newGroups
};
}
};
}
private Action<ChangeEventArgs> saveGroupWeight(string groupId)
{
return (e) =>
{
if(planner.LocalCourse != null)
{
var newWeight = double.Parse(e.Value?.ToString() ?? "0");
var newGroups = planner.LocalCourse.AssignmentGroups.Select(
g => g.Id == groupId
? g with { Weight = newWeight }
: g
);
planner.LocalCourse = planner.LocalCourse with
{
AssignmentGroups = newGroups
};
}
};
}
}
@if(planner.LocalCourse != null)
{
<h4 class="text-center">Assignment Groups</h4>
@foreach (var group in planner.LocalCourse.AssignmentGroups)
{
var groupName = group.Name;
var nameInputCallback = saveGroupName(group.Id);
var weight = group.Weight;
var weightInputCallback = saveGroupWeight(group.Id);
<div class="row">
<div class="col-auto">
<label class="form-label">Group Name</label>
<input
class="form-control"
@bind="groupName" @oninput="nameInputCallback">
</div>
<div class="col-auto">
<label class="form-label">Weight</label>
<input
class="form-control"
@bind="weight"
@oninput="weightInputCallback"
>
</div>
</div>
}
<div class="d-flex justify-content-end">
<button
class="btn btn-outline-primary"
@onclick="AddAssignmentGroup"
>
+ Assignment Group
</button>
</div>
}

View File

@@ -67,6 +67,7 @@
<h1>Course Settings</h1>
</Title>
<Body>
<h5 class="text-center">Select Days Of Week</h5>
<div class="row m-3">
@foreach (DayOfWeek day in (DayOfWeek[])Enum.GetValues(typeof(DayOfWeek)))
@@ -144,6 +145,7 @@
</div>
</div>
}
<AssignmentGroups />
</Body>
<Footer>
<button

View File

@@ -17,7 +17,6 @@ public class AssignmentEditorContext
get => _assignment;
set
{
Console.WriteLine("saving");
_assignment = value;
StateHasChanged?.Invoke();
}

View File

@@ -72,6 +72,7 @@ public class CoursePlanner
public event Action? StateHasChanged;
public IEnumerable<CanvasAssignment>? CanvasAssignments { get; internal set; }
public IEnumerable<CanvasAssignmentGroup>? CanvasAssignmentGroups { get; internal set; }
public IEnumerable<CanvasQuiz>? CanvasQuizzes { get; internal set; }
public IEnumerable<CanvasModule>? CanvasModules { get; internal set; }
public Dictionary<ulong, IEnumerable<CanvasModuleItem>>? CanvasModulesItems { get; internal set; }
@@ -80,7 +81,8 @@ public class CoursePlanner
IEnumerable<CanvasAssignment> CanvasAssignments,
IEnumerable<CanvasModule> CanvasModules,
Dictionary<ulong, IEnumerable<CanvasModuleItem>> CanvasModulesItems,
IEnumerable<CanvasQuiz> canvasQuizzes
IEnumerable<CanvasQuiz> canvasQuizzes,
IEnumerable<CanvasAssignmentGroup> canvasAssignmentGroups
)> LoadCanvasData()
{
LoadingCanvasData = true;
@@ -92,16 +94,18 @@ public class CoursePlanner
var assignmentsTask = canvas.Assignments.GetAll(canvasId);
var quizzesTask = canvas.Quizzes.GetAll(canvasId);
var modulesTask = canvas.GetModules(canvasId);
var assignmentGroupsTask = canvas.AssignmentGroups.GetAll(canvasId);
CanvasAssignments = await assignmentsTask;
CanvasQuizzes = await quizzesTask;
CanvasModules = await modulesTask;
CanvasAssignmentGroups = await assignmentGroupsTask;
CanvasModulesItems = await canvas.GetAllModulesItems(canvasId, CanvasModules);
LoadingCanvasData = false;
StateHasChanged?.Invoke();
return (CanvasAssignments, CanvasModules, CanvasModulesItems, CanvasQuizzes);
return (CanvasAssignments, CanvasModules, CanvasModulesItems, CanvasQuizzes, CanvasAssignmentGroups);
}
public async Task SyncWithCanvas()
@@ -119,18 +123,31 @@ public class CoursePlanner
LoadingCanvasData = true;
StateHasChanged?.Invoke();
var (canvasAssignments, canvasModules, canvasModuleItems, canvasQuizzes) = await LoadCanvasData();
var (
canvasAssignments,
canvasModules,
canvasModuleItems,
canvasQuizzes,
canvasAssignmentGroups
) = await LoadCanvasData();
LoadingCanvasData = true;
StateHasChanged?.Invoke();
LocalCourse = LocalCourse.deleteCanvasIdsThatNoLongerExist(
canvasModules,
canvasAssignments,
canvasAssignmentGroups,
canvasQuizzes
);
var canvasId =
LocalCourse.CanvasId ?? throw new Exception("no course canvas id to sync with canvas");
var newAssignmentGroups = await LocalCourse.EnsureAllAssignmentGroupsExistInCanvas(
canvasId, canvasAssignmentGroups, canvas);
LocalCourse = LocalCourse with { AssignmentGroups = newAssignmentGroups };
var newModules = await LocalCourse.EnsureAllModulesExistInCanvas(
canvasId,
CanvasModules,

View File

@@ -47,6 +47,7 @@ public static class CoursePlannerExtensions
this LocalCourse localCourse,
IEnumerable<CanvasModule> canvasModules,
IEnumerable<CanvasAssignment> canvasAssignments,
IEnumerable<CanvasAssignmentGroup> canvasAssignmentGroups,
IEnumerable<CanvasQuiz> canvasQuizzes
)
{
@@ -56,9 +57,21 @@ public static class CoursePlannerExtensions
.Select((m) => m.validateCanvasIds(canvasModules, canvasAssignments, canvasQuizzes))
.ToArray();
var canvasAssignmentGroupIds = canvasAssignmentGroups.Select(g => g.Id).ToArray();
var correctAssignmentGroups = localCourse.AssignmentGroups.Select(
g =>
{
var groupCanvasId = g.CanvasId ?? 0;
return canvasAssignmentGroupIds.Contains(groupCanvasId)
? g
: g with { CanvasId = null };
}
).ToArray();
return localCourse with
{
Modules = correctedModules
Modules = correctedModules,
AssignmentGroups = correctAssignmentGroups,
};
}

View File

@@ -0,0 +1,40 @@
using CanvasModel.Assignments;
using CanvasModel.Modules;
using LocalModels;
using Management.Services.Canvas;
namespace Management.Planner;
public static partial class AssignmentGroupSyncronizationExtensions
{
internal static async Task<IEnumerable<LocalAssignmentGroup>> EnsureAllAssignmentGroupsExistInCanvas(
this LocalCourse localCourse,
ulong courseCanvasId,
IEnumerable<CanvasAssignmentGroup> canvasAssignmentGroups,
CanvasService canvas
)
{
var canvasAssignmentGroupIds = canvasAssignmentGroups.Select(g => g.Id).ToArray();
var assignmentGroups = await Task.WhenAll((Task<LocalAssignmentGroup>[])localCourse.AssignmentGroups.Select(
async assignmentGroup =>
{
var canvasGroupWithSameName = canvasAssignmentGroups.FirstOrDefault(
cg => cg.Name.Equals(assignmentGroup.Name)
);
if (canvasGroupWithSameName == null)
return await canvas.AssignmentGroups.Create(courseCanvasId, assignmentGroup);
var correctGroup = assignmentGroup with { CanvasId = canvasGroupWithSameName.Id };
var needsUpdate = canvasGroupWithSameName.GroupWeight != correctGroup.Weight;
if (needsUpdate)
await canvas.AssignmentGroups.Update(courseCanvasId, assignmentGroup);
return correctGroup;
}
).ToArray());
return assignmentGroups;
}
}

View File

@@ -0,0 +1,29 @@
namespace CanvasModel.Assignments;
public record CanvasAssignmentGroup
{
[JsonPropertyName("id")]
public ulong Id { get; init; }
[JsonPropertyName("name")]
public required string Name { get; init; }
[JsonPropertyName("position")]
public int Position { get; init; }
[JsonPropertyName("group_weight")]
public double GroupWeight { get; init; }
// [JsonPropertyName("sis_source_id")]
// public string? SisSourceId { get; init; } = null;
// [JsonPropertyName("integration_data")]
// public Dictionary<string, string> IntegrationData { get; init; } = new Dictionary<string, string>();
// [JsonPropertyName("assignments")]
// public List<CanvasAssignment> Assignments { get; init; }
// [JsonPropertyName("rules")]
// public object Rules { get; init; } // The specific type for 'Rules' is not detailed in the spec, so using object for now.
}

View File

@@ -0,0 +1,9 @@
namespace LocalModels;
public record LocalAssignmentGroup
{
public ulong? CanvasId { get; init; }
public string Id { get; init; } = string.Empty;
public required string Name { get; init; }
public double Weight { get; init; }
}

View File

@@ -11,6 +11,8 @@ public record LocalCourse
public SimpleTimeOnly DefaultDueTime { get; init; } = new SimpleTimeOnly();
public IEnumerable<AssignmentTemplate> AssignmentTemplates { get; init; } =
Enumerable.Empty<AssignmentTemplate>();
public IEnumerable<LocalAssignmentGroup> AssignmentGroups { get; init; } =
Enumerable.Empty<LocalAssignmentGroup>();
}
public record SimpleTimeOnly

View File

@@ -0,0 +1,70 @@
using CanvasModel.Assignments;
using LocalModels;
using RestSharp;
namespace Management.Services.Canvas;
public class CanvasAssignmentGroupService
{
private readonly IWebRequestor webRequestor;
private readonly CanvasServiceUtils utils;
public CanvasAssignmentGroupService(IWebRequestor webRequestor, CanvasServiceUtils utils)
{
this.webRequestor = webRequestor;
this.utils = utils;
}
public async Task<IEnumerable<CanvasAssignmentGroup>> GetAll(ulong courseId)
{
var url = $"courses/{courseId}/assignment_groups";
var request = new RestRequest(url);
var assignmentResponse = await utils.PaginatedRequest<IEnumerable<CanvasAssignmentGroup>>(request);
return assignmentResponse.SelectMany(
assignments => assignments
);
}
public async Task<LocalAssignmentGroup> Create(
ulong canvasCourseId,
LocalAssignmentGroup localAssignmentGroup
)
{
Console.WriteLine($"creating assignment group: {localAssignmentGroup.Name}");
var url = $"courses/{canvasCourseId}/assignment_groups";
var request = new RestRequest(url);
var body = new
{
name = localAssignmentGroup.Name,
group_weight = localAssignmentGroup.Weight,
};
request.AddBody(body);
var (canvasAssignmentGroup, response) = await webRequestor.PostAsync<CanvasAssignmentGroup>(request);
if (canvasAssignmentGroup == null)
throw new Exception("created canvas assignment group was null");
return localAssignmentGroup with
{
CanvasId = canvasAssignmentGroup.Id
};
}
public async Task Update(
ulong canvasCourseId,
LocalAssignmentGroup localAssignmentGroup
)
{
Console.WriteLine($"updating assignment group: {localAssignmentGroup.Name}");
if (localAssignmentGroup.CanvasId == null)
throw new Exception("cannot update assignment group if canvas id is null");
var url = $"courses/{canvasCourseId}/assignment_groups/{localAssignmentGroup.CanvasId}";
var request = new RestRequest(url);
var body = new
{
name = localAssignmentGroup.Name,
group_weight = localAssignmentGroup.Weight,
};
request.AddBody(body);
await webRequestor.PutAsync<CanvasAssignmentGroup>(request);
}
}

View File

@@ -13,18 +13,21 @@ public class CanvasService
private readonly CanvasServiceUtils utils;
public CanvasAssignmentService Assignments { get; }
public CanvasAssignmentGroupService AssignmentGroups { get; }
public CanvasQuizService Quizzes { get; }
public CanvasService(
IWebRequestor webRequestor,
CanvasServiceUtils utils,
CanvasAssignmentService Assignments,
CanvasAssignmentGroupService AssignmentGroups,
CanvasQuizService Quizzes
)
{
this.webRequestor = webRequestor;
this.utils = utils;
this.Assignments = Assignments;
this.AssignmentGroups = AssignmentGroups;
this.Quizzes = Quizzes;
}

View File

@@ -17,3 +17,5 @@ Apparently the VSCode razor extension was compiled with a preview of dotnet 6...
The issue can be tracked [here](https://github.com/dotnet/razor/issues/6241)
I am looking for a TA and grader for my CS 1410/1415 class. The labs are tuesdays at 7:30 - 9:30 am. Anyone who is interested, please message me on teams or send an email to alex.mickelson@snow.edu.

View File

@@ -128,4 +128,11 @@ Content-Type: application/json
###
GET https://snow.instructure.com/api/v1/courses/872095/assignments/12676639?include[]=overrides
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
###
GET https://snow.instructure.com/api/v1/courses/871954/assignment_groups
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}
###
GET https://snow.instructure.com/api/v1/courses/871954
Authorization: Bearer {{$dotenv CANVAS_TOKEN}}