Skip to content

Commit

Permalink
Merge pull request #21 from OfficeDev/uat-fixes
Browse files Browse the repository at this point in the history
bug fixes
  • Loading branch information
trevorbye authored May 28, 2024
2 parents 55e4cd4 + 9c3d08d commit 9b7e6b7
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 95 deletions.
5 changes: 5 additions & 0 deletions grade-sync-api/ClientApp/src/components/Connections.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const Connections = (props) => {
editConnectionId: "",
isGroupEnabled: false,
allowNoneLineItemCategory: false,
autoSetGradingPeriod: false,
defaultLineItemCategory: null,
categories: null
},
Expand Down Expand Up @@ -64,6 +65,7 @@ const Connections = (props) => {
editConnectionId: "",
isGroupEnabled: false,
allowNoneLineItemCategory: false,
autoSetGradingPeriod: false,
defaultLineItemCategory: null,
categories: null
};
Expand All @@ -83,6 +85,7 @@ const Connections = (props) => {
stateCopy.formState.isGroupEnabled = action.dto.isGroupEnabled;
stateCopy.formState.allowNoneLineItemCategory = action.dto.allowNoneLineItemCategory;
stateCopy.formState.defaultLineItemCategory = action.dto.defaultLineItemCategory;
stateCopy.formState.autoSetGradingPeriod = action.dto.autoSetGradingPeriod;
stateCopy.formState.categories = action.categories;

stateCopy.formState.baseUrl = action.detailsDto.oneRosterBaseUrl;
Expand Down Expand Up @@ -111,6 +114,7 @@ const Connections = (props) => {
&& key !== "isGroupEnabled"
&& key !== "allowNoneLineItemCategory"
&& key !== "defaultLineItemCategory"
&& key !== "autoSetGradingPeriod"
&& key !== "categories"
) {
allFieldsHaveValue = false;
Expand Down Expand Up @@ -384,6 +388,7 @@ const Connections = (props) => {
{ getTextInput(dataState.formState.clientSecret, "clientSecret", "Enter OneRoster Client Secret", "OneRoster Client Secret", "password") }
{ getCheckboxInput(dataState.formState.isGroupEnabled, "isGroupEnabled", "Group-enabled") }
{ getCheckboxInput(dataState.formState.allowNoneLineItemCategory, "allowNoneLineItemCategory", "Allow 'None' assignment category") }
{ getCheckboxInput(dataState.formState.autoSetGradingPeriod, "autoSetGradingPeriod", "Auto-set line item grading period") }

{
dataState.formState.allowNoneLineItemCategory && dataState.formState.categories && dataState.formState.editConnectionId ?
Expand Down
11 changes: 9 additions & 2 deletions grade-sync-api/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Identity.Web;
using System.Text.Json;

using GradeSyncApi.Helpers;
using GradeSyncApi.Services.Graph;
Expand All @@ -24,17 +25,20 @@ public class HomeController : ControllerBase
private readonly IGraphService _graphService;
private readonly IMessageQueueService _messageQueueService;
private readonly IOneRosterService _oneRosterService;
private readonly ILogger<HomeController> _logger;

public HomeController(
ITableStorageService storageService,
IGraphService graphService,
IMessageQueueService messageQueueService,
IOneRosterService oneRoster)
IOneRosterService oneRoster,
ILogger<HomeController> logger)
{
_storageService = storageService;
_graphService = graphService;
_messageQueueService = messageQueueService;
_oneRosterService = oneRoster;
_logger = logger;
}

[HttpGet]
Expand Down Expand Up @@ -90,6 +94,8 @@ public async Task<IActionResult> EnqueueGradeSync(string classId, string connect

var teamsClass = await _graphService.GetClass(classId);
jobEntity.ClassExternalId = teamsClass!.ExternalId;
_logger.LogInformation($"Class SIS ID: {jobEntity.ClassExternalId}");

await _storageService.UpsertGradeSyncJobEntityAsync(jobEntity);

await _storageService.BatchUpdateAssignmentsForJobAsync(
Expand Down Expand Up @@ -357,6 +363,7 @@ public async Task<IActionResult> CreateOneRosterConnection(OneRosterConnectionEn
storedConnection.IsGroupEnabled = connectionEntity.IsGroupEnabled;
storedConnection.AllowNoneLineItemCategory = connectionEntity.AllowNoneLineItemCategory;
storedConnection.DefaultLineItemCategory = connectionEntity.DefaultLineItemCategory;
storedConnection.AutoSetGradingPeriod = connectionEntity.AutoSetGradingPeriod;
await _storageService.UpsertOneRosterConnectionEntityAsync(storedConnection);
}
else
Expand Down
80 changes: 70 additions & 10 deletions grade-sync-api/Services/Graph/GraphService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using Microsoft.Identity.Client;
Expand Down Expand Up @@ -283,6 +286,26 @@ private static HashSet<string> _getTeamsClassIdSet(TeamsClassWrapper wrapper)
return idSet;
}

private static async Task<List<EducationUser>> GetPaginatedEduUsers(HttpClient client, string url)
{
var combinedUsers = new List<EducationUser>();
var res = await client!.GetAsync(url);
res.EnsureSuccessStatusCode();
var content = await res.Content.ReadAsStringAsync();
var wrapper = JsonConvert.DeserializeObject<EducationUserWrapper>(content);
combinedUsers.AddRange(wrapper!.Users);

while (wrapper!.NextLink is not null)
{
var page = await client!.GetAsync(wrapper!.NextLink);
var pageContent = await page.Content.ReadAsStringAsync();
wrapper = JsonConvert.DeserializeObject<EducationUserWrapper>(pageContent);
combinedUsers.AddRange(wrapper!.Users);
}

return combinedUsers;
}

public async Task<List<EducationUser>?> GetStudentsByClass(string classId)
{
// we need to get both teachers and members for the class, and figure out who the students are based on the diff
Expand All @@ -292,15 +315,15 @@ private static HashSet<string> _getTeamsClassIdSet(TeamsClassWrapper wrapper)

try
{
var studentWrapper = await GraphGetRequest<EducationUserWrapper>(_httpClient!, $"{_graphEduBaseUrl}/classes/{classId}/members");
var teacherWrapper = await GraphGetRequest<EducationUserWrapper>(_httpClient!, $"{_graphEduBaseUrl}/classes/{classId}/teachers");
var students = await GetPaginatedEduUsers(_httpClient!, $"{_graphEduBaseUrl}/classes/{classId}/members");
var teachers = await GetPaginatedEduUsers(_httpClient!, $"{_graphEduBaseUrl}/classes/{classId}/teachers");
var teacherIds = new HashSet<string>();
foreach (var teacher in teacherWrapper!.Users)
foreach (var teacher in teachers)
{
teacherIds.Add(teacher.UserId);
}

return studentWrapper!.Users.Where(user => !teacherIds.Contains(user.UserId)).ToList();
return students.Where(user => !teacherIds.Contains(user.UserId)).ToList();
}
catch (Exception e)
{
Expand Down Expand Up @@ -330,30 +353,69 @@ public async Task<Dictionary<string, string>> GetStudentSisIdDict(string classId
return sisIdDict;
}

private static async Task<List<AssignmentEntity>> GetPaginatedAssignments(HttpClient client, string url)
{
var combinedAssignments = new List<AssignmentEntity>();
var res = await client!.GetAsync(url);
res.EnsureSuccessStatusCode();
var content = await res.Content.ReadAsStringAsync();
var wrapper = JsonConvert.DeserializeObject<AssignmentsWrapper>(content);
combinedAssignments.AddRange(wrapper!.Assignments);

while (wrapper!.NextLink is not null)
{
var page = await client!.GetAsync(wrapper!.NextLink);
var pageContent = await page.Content.ReadAsStringAsync();
wrapper = JsonConvert.DeserializeObject<AssignmentsWrapper>(pageContent);
combinedAssignments.AddRange(wrapper!.Assignments);
}

return combinedAssignments;
}

public async Task<List<AssignmentEntity>?> GetAssignmentsByClass(string classId)
{
try
{
var wrapper = await GraphGetRequest<AssignmentsWrapper>(_httpClient!, $"{_graphEduBetaUrl}/classes/{classId}/assignments/?$expand=*");
return wrapper!.Assignments;
return await GetPaginatedAssignments(_httpClient!, $"{_graphEduBetaUrl}/classes/{classId}/assignments/?$expand=*");
} catch (Exception e)
{
_logger.Log(Microsoft.Extensions.Logging.LogLevel.Error, e, "MS Graph error fetching EDU assignments.");
return null;
}
}

private static async Task<List<SubmissionEntity>> GetPaginatedSubmissions(HttpClient client, string url)
{
var combinedSubmissions = new List<SubmissionEntity>();
var res = await client!.GetAsync(url);
res.EnsureSuccessStatusCode();
var content = await res.Content.ReadAsStringAsync();
var wrapper = JsonConvert.DeserializeObject<SubmissionsWrapper>(content);
combinedSubmissions.AddRange(wrapper!.Submissions);

while (wrapper!.NextLink is not null)
{
var page = await client!.GetAsync(wrapper!.NextLink);
var pageContent = await page.Content.ReadAsStringAsync();
wrapper = JsonConvert.DeserializeObject<SubmissionsWrapper>(pageContent);
combinedSubmissions.AddRange(wrapper!.Submissions);
}

return combinedSubmissions;
}

private async Task<AssignmentWithSubmissions> GetSubmissionsByAssignmentId(string classId, string assignmentId)
{
var wrapper = await GraphGetRequest<SubmissionsWrapper>(
var submissions = await GetPaginatedSubmissions(
_httpClient!,
$"{_graphEduBaseUrl}/classes/{classId}/assignments/{assignmentId}/submissions?$expand=outcomes"
);

var assignmentWithSubmissions = new AssignmentWithSubmissions
{
AssignmentId = assignmentId,
Submissions = wrapper!.Submissions
Submissions = submissions
};
return assignmentWithSubmissions;
}
Expand All @@ -376,7 +438,5 @@ public async Task<Dictionary<string, List<SubmissionEntity>>> GetAllAssignmentsW
}
return submissionsDict;
}


}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Newtonsoft.Json;
using GradeSyncApi.Services.Storage;

namespace GradeSyncApi.Services.Graph.JsonEntities
{
public class AssignmentsWrapper
{
public AssignmentsWrapper()
{
}
public AssignmentsWrapper() {}

[JsonProperty("@odata.nextLink")]
public string? NextLink { get; set; }

[JsonProperty("value")]
public List<AssignmentEntity> Assignments { get; set; }
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Newtonsoft.Json;

namespace GradeSyncApi.Services.Graph.JsonEntities
Expand All @@ -8,6 +9,9 @@ public class EducationUserWrapper
{
public EducationUserWrapper() {}

[JsonProperty("@odata.nextLink")]
public string? NextLink { get; set; }

[JsonProperty("value")]
public List<EducationUser> Users { get; set; }
}
Expand Down Expand Up @@ -70,4 +74,3 @@ public EducationTeacher(string externalId)
public string ExternalId { get; set; }
}
}

Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using Newtonsoft.Json;
using GradeSyncApi.Services.Storage;

namespace GradeSyncApi.Services.Graph.JsonEntities
{
public class SubmissionsWrapper
{
public SubmissionsWrapper()
{
}
public SubmissionsWrapper() {}

[JsonProperty("@odata.nextLink")]
public string? NextLink { get; set; }

[JsonProperty("value")]
public List<SubmissionEntity> Submissions { get; set; }
}
}

2 changes: 1 addition & 1 deletion grade-sync-api/Services/OneRoster/OneRosterDataEntities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ public LineItem() { }
public IdTypeMapping Class { get; set; }

[JsonProperty("gradingPeriod")]
public IdTypeMapping GradingPeriod { get; set; }
public IdTypeMapping? GradingPeriod { get; set; }

[JsonProperty("category")]
public IdTypeMapping Category { get; set; }
Expand Down
36 changes: 34 additions & 2 deletions grade-sync-api/Services/OneRoster/OneRosterHttpClient.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using GradeSyncApi.Services.Graph.JsonEntities;
using Newtonsoft.Json;

namespace GradeSyncApi.Services.OneRoster
Expand Down Expand Up @@ -43,9 +49,10 @@ public static async Task<List<T>> PaginatedGetRequest<T>(
return pageList;
}

public static async Task<Tuple<string, HttpResponseMessage>> GetRequest(HttpClient client, string url, string token)
public static async Task<Tuple<string, HttpResponseMessage>> GetRequest(HttpClient client, string url, string token, int? limit = 7000)
{
var req = new HttpRequestMessage(HttpMethod.Get, url);
var urlWithLimit = $"{url}?limit={limit}";
var req = new HttpRequestMessage(HttpMethod.Get, urlWithLimit);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var res = await client.SendAsync(req);
var content = await res.Content.ReadAsStringAsync();
Expand All @@ -60,6 +67,31 @@ public static async Task<Tuple<string, HttpResponseMessage>> GetRequest(HttpClie
}
}

public static async Task<Tuple<string, HttpResponseMessage>> GetRequestFilter(HttpClient client, string url, string token, Tuple<string, string>? additionalFilter = null)
{
var reqUrl = $"{url}?limit=5000";
if (additionalFilter is not null)
{
var encodedFilter = Uri.EscapeDataString($"{additionalFilter.Item1}='{additionalFilter.Item2}'");
reqUrl = $"{reqUrl}&filter={encodedFilter}";
}

var req = new HttpRequestMessage(HttpMethod.Get, reqUrl);
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
var res = await client.SendAsync(req);
var content = await res.Content.ReadAsStringAsync();

try
{
res.EnsureSuccessStatusCode();
return new Tuple<string, HttpResponseMessage>(content, res);
}
catch (Exception)
{
throw new ApplicationException(content);
}
}

public static async Task<T> PutRequest<T>(HttpClient client, string url, string token, object reqContent)
{
var serialized = JsonConvert
Expand Down
Loading

0 comments on commit 9b7e6b7

Please sign in to comment.