Skip to content
This repository has been archived by the owner on Sep 4, 2023. It is now read-only.

Commit

Permalink
Release v1.0.0 (#41)
Browse files Browse the repository at this point in the history
* Change formats type to `string` and split with comma separator

* Use root configuration instead of configuration section

* Fix exceptions not properly displaying in the console

* Add all module item types according to documentation

* Update application demo GIF

* Create code-analysis.yml

* Remove comments and enable additional queries

* Removed push from code-analysis.yml workflow

* Typo code-analysis.yml

* Added support for a Word export option

* Added footer Epsilon Credits

* Created helper class with reformat functions

* Reformat code

* Improved helper functions, Found that they can be added to records :-)

* Reformat files

* string interpolation

* Feature/cleanup (#40)

* Remove obsolete Epsilon.Http.Abstractions project

* Fix invalid serializable implementation

* Disable unused method return value hint

* Change exception to more appropriate one

* Update Grade score description

* Prevent null grades from exporting

* Reduce cognitive complexity to acceptable level

* Move logging call

* Reduce nesting

* Move project name and repository uri to constants

* Fix nullability warnings

* Remove unused class

* Use ?: operator and move constants to top level of class

* Reduce loop complexity

* Use project name constant in output name export option

* Update README.md application demo gif

* Add supported formats to README.md

Co-authored-by: Jelle Maas <typiqally@gmail.com>
  • Loading branch information
NealGeilen and Typiqally authored Nov 8, 2022
1 parent 8bdf63f commit 6eaba08
Show file tree
Hide file tree
Showing 37 changed files with 289 additions and 211 deletions.
2 changes: 1 addition & 1 deletion Epsilon.Abstractions/Export/ICanvasModuleExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Epsilon.Abstractions.Export;

public interface ICanvasModuleExporter : IExporter<IEnumerable<Module>>
public interface ICanvasModuleExporter : IExporter<IAsyncEnumerable<ModuleOutcomeResultCollection>>
{

}
2 changes: 1 addition & 1 deletion Epsilon.Abstractions/Export/IExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ public interface IExporter<in T>
{
public IEnumerable<string> Formats { get; }

void Export(T data, string format);
Task Export(T data, string format);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Epsilon.Http.Abstractions;
namespace Epsilon.Abstractions.Http;

public abstract class HttpService
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Epsilon.Http.Abstractions.Json;
namespace Epsilon.Abstractions.Http.Json;

public static class HttpClientJsonExtensions
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.Text.Json;

namespace Epsilon.Http.Abstractions.Json;
namespace Epsilon.Abstractions.Http.Json;

public static class HttpResponseMessageJsonExtensions
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ namespace Epsilon.Canvas.Abstractions;

public interface ICanvasModuleCollectionFetcher
{
public Task<IEnumerable<Module>> GetAll(int courseId);
public IAsyncEnumerable<ModuleOutcomeResultCollection> GetAll(int courseId);
}
7 changes: 1 addition & 6 deletions Epsilon.Canvas.Abstractions/Model/Module.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Text.Json.Serialization;
using Epsilon.Canvas.Abstractions.Response;

namespace Epsilon.Canvas.Abstractions.Model;

Expand All @@ -8,8 +7,4 @@ public record Module(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("items_count")] int Count,
[property: JsonPropertyName("items")] IEnumerable<ModuleItem>? Items
)
{
[JsonIgnore]
public OutcomeResultCollection Collection { get; set; }
}
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Epsilon.Canvas.Abstractions.Model;

public record ModuleOutcomeResultCollection(Module Module, OutcomeResultCollection Collection);
22 changes: 21 additions & 1 deletion Epsilon.Canvas.Abstractions/Model/Outcome.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,29 @@
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

namespace Epsilon.Canvas.Abstractions.Model;

public record Outcome(
[property: JsonPropertyName("id")] int Id,
[property: JsonPropertyName("title")] string Title,
[property: JsonPropertyName("description")] string Description
);
)
{
public string ShortDescription()
{
string description = RemoveHtml();
//Function gives only the short English description back of the outcome.
var startPos = description.IndexOf(" EN ", StringComparison.Ordinal) + " EN ".Length;
var endPos = description.IndexOf(" NL ", StringComparison.Ordinal);

return description.Substring(startPos, endPos - startPos);
}

private string RemoveHtml()
{
var raw = Regex.Replace(Description, "<.*?>", " ");
var trimmed = Regex.Replace(raw, @"\s\s+", " ");

return trimmed;
}
};
18 changes: 16 additions & 2 deletions Epsilon.Canvas.Abstractions/Model/OutcomeResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,21 @@
namespace Epsilon.Canvas.Abstractions.Model;

public record OutcomeResult(
[property: JsonPropertyName("mastery")] bool? Mastery,
[property: JsonPropertyName("mastery")]
bool? Mastery,
[property: JsonPropertyName("score")] double? Score,
[property: JsonPropertyName("links")] OutcomeResultLink Link
);
)
{
public string? Grade()
{
return Score switch
{
<= 2 => "Unsatisfactory",
3 => "Satisfactory",
4 => "Good",
5 => "Outstanding",
_ => null,
};
}
}
3 changes: 1 addition & 2 deletions Epsilon.Canvas.Abstractions/Model/OutcomeResultCollection.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Text.Json.Serialization;
using Epsilon.Canvas.Abstractions.Model;

namespace Epsilon.Canvas.Abstractions.Response;
namespace Epsilon.Canvas.Abstractions.Model;

public record OutcomeResultCollection(
[property: JsonPropertyName("outcome_results")] IEnumerable<OutcomeResult> OutcomeResults,
Expand Down
10 changes: 4 additions & 6 deletions Epsilon.Canvas.Abstractions/Model/OutcomeResultCollectionLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,13 @@
namespace Epsilon.Canvas.Abstractions.Model;

public record OutcomeResultCollectionLink(
[property: JsonPropertyName("outcomes")] IEnumerable<Outcome>? Outcomes,
[property: JsonPropertyName("alignments")] IEnumerable<Alignment>? Alignments
[property: JsonPropertyName("outcomes")] IEnumerable<Outcome> Outcomes,
[property: JsonPropertyName("alignments")] IEnumerable<Alignment> Alignments
)
{
public IDictionary<string, Outcome> OutcomesDictionary => Outcomes
.DistinctBy(static o => o.Id)
public IDictionary<string, Outcome> OutcomesDictionary => Outcomes.DistinctBy(static o => o.Id)
.ToDictionary(static o => o.Id.ToString(), static o => o);

public IDictionary<string, Alignment> AlignmentsDictionary => Alignments
.DistinctBy(static a => a.Id)
public IDictionary<string, Alignment> AlignmentsDictionary => Alignments.DistinctBy(static a => a.Id)
.ToDictionary(static a => a.Id, static a => a);
}
12 changes: 0 additions & 12 deletions Epsilon.Canvas.Abstractions/Model/User.cs

This file was deleted.

1 change: 0 additions & 1 deletion Epsilon.Canvas.Abstractions/Service/IOutcomeHttpService.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Epsilon.Canvas.Abstractions.Model;
using Epsilon.Canvas.Abstractions.Response;

namespace Epsilon.Canvas.Abstractions.Service;

Expand Down
35 changes: 14 additions & 21 deletions Epsilon.Canvas/CanvasModuleCollectionFetcher.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using Epsilon.Canvas.Abstractions;
using System.Diagnostics;
using Epsilon.Canvas.Abstractions;
using Epsilon.Canvas.Abstractions.Model;
using Epsilon.Canvas.Abstractions.Response;
using Epsilon.Canvas.Abstractions.Service;
using Microsoft.Extensions.Logging;

namespace Epsilon.Canvas;

public class CanvasModuleCollectionFetcher : ICanvasModuleCollectionFetcher
{
private readonly ILogger<CanvasModuleCollectionFetcher> _logger;
private readonly IModuleHttpService _moduleService;
private readonly IOutcomeHttpService _outcomeService;

Expand All @@ -18,36 +17,30 @@ public CanvasModuleCollectionFetcher(
IOutcomeHttpService outcomeService
)
{
_logger = logger;
_moduleService = moduleService;
_outcomeService = outcomeService;
}

public async Task<IEnumerable<Module>> GetAll(int courseId)
public async IAsyncEnumerable<ModuleOutcomeResultCollection> GetAll(int courseId)
{
_logger.LogInformation("Downloading results...");

var response = await _outcomeService.GetResults(courseId, new[] { "outcomes", "alignments" });

var alignments = response.Links.Alignments
.DistinctBy(static a => a.Id)
.ToDictionary(static a => a.Id, static a => a);

var outcomes = response.Links.Outcomes
.DistinctBy(static o => o.Id)
.ToDictionary(static o => o.Id.ToString(), static o => o);

var modules = await _moduleService.GetAll(courseId, new[] { "items" });
foreach (var module in modules)

Debug.Assert(response != null, nameof(response) + " != null");
Debug.Assert(modules != null, nameof(modules) + " != null");

foreach (var module in modules.ToArray())
{
Debug.Assert(module.Items != null, "module.Items != null");

var ids = module.Items.Select(static i => $"assignment_{i.ContentId}");

module.Collection = new OutcomeResultCollection(
Debug.Assert(response.Links?.Alignments != null, "response.Links?.Alignments != null");

yield return new ModuleOutcomeResultCollection(module, new OutcomeResultCollection(
response.OutcomeResults.Where(r => ids.Contains(r.Link.Alignment)),
response.Links with { Alignments = response.Links.Alignments.Where(a => ids.Contains(a.Id)) }
);
));
}

return modules;
}
}
1 change: 1 addition & 0 deletions Epsilon.Canvas/CanvasServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public static class CanvasServiceCollectionExtensions
{
private const string CanvasHttpClient = "CanvasHttpClient";

// ReSharper disable once UnusedMethodReturnValue.Global
public static IServiceCollection AddCanvas(this IServiceCollection services, IConfiguration config)
{
services.Configure<CanvasSettings>(config);
Expand Down
2 changes: 1 addition & 1 deletion Epsilon.Canvas/Converter/LinkHeaderConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public LinkHeader ConvertFrom(HttpResponseMessage response)
{
if (!response.Headers.Contains("Link"))
{
throw new ArgumentNullException(nameof(response.Headers), "Header does not contain link key");
throw new KeyNotFoundException("Header does not contain link key");
}

return ConvertFrom(response.Headers.GetValues("Link").First());
Expand Down
2 changes: 1 addition & 1 deletion Epsilon.Canvas/Epsilon.Canvas.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Epsilon.Abstractions\Epsilon.Abstractions.csproj" />
<ProjectReference Include="..\Epsilon.Canvas.Abstractions\Epsilon.Canvas.Abstractions.csproj" />
<ProjectReference Include="..\Epsilon.Http.Abstractions\Epsilon.Http.Abstractions.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions Epsilon.Canvas/Service/AssignmentHttpService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Epsilon.Canvas.Abstractions.Model;
using Epsilon.Abstractions.Http;
using Epsilon.Abstractions.Http.Json;
using Epsilon.Canvas.Abstractions.Model;
using Epsilon.Canvas.Abstractions.Service;
using Epsilon.Http.Abstractions;
using Epsilon.Http.Abstractions.Json;

namespace Epsilon.Canvas.Service;

Expand Down
4 changes: 2 additions & 2 deletions Epsilon.Canvas/Service/ModuleHttpService.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
using System.Text;
using Epsilon.Abstractions.Http;
using Epsilon.Abstractions.Http.Json;
using Epsilon.Canvas.Abstractions.Model;
using Epsilon.Canvas.Abstractions.Service;
using Epsilon.Http.Abstractions;
using Epsilon.Http.Abstractions.Json;

namespace Epsilon.Canvas.Service;

Expand Down
13 changes: 7 additions & 6 deletions Epsilon.Canvas/Service/OutcomeHttpService.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
using System.Text;
using Epsilon.Abstractions.Http;
using Epsilon.Abstractions.Http.Json;
using Epsilon.Canvas.Abstractions.Model;
using Epsilon.Canvas.Abstractions.Response;
using Epsilon.Canvas.Abstractions.Service;
using Epsilon.Http.Abstractions;
using Epsilon.Http.Abstractions.Json;

namespace Epsilon.Canvas.Service;

Expand All @@ -30,11 +29,13 @@ public OutcomeHttpService(HttpClient client, IPaginatorHttpService paginator) :
var query = $"?include[]={string.Join("&include[]=", include)}";

var responses = await _paginator.GetAllPages<OutcomeResultCollection>(HttpMethod.Get, url + query);
var responsesArray = responses.ToArray();

return new OutcomeResultCollection(
responses.SelectMany(static r => r.OutcomeResults),
responsesArray.SelectMany(static r => r.OutcomeResults),
new OutcomeResultCollectionLink(
responses.SelectMany(static r => r.Links.Outcomes),
responses.SelectMany(static r => r.Links.Alignments)
responsesArray.SelectMany(static r => r.Links?.Outcomes ?? Array.Empty<Outcome>()),
responsesArray.SelectMany(static r => r.Links?.Alignments ?? Array.Empty<Alignment>())
)
);
}
Expand Down
26 changes: 11 additions & 15 deletions Epsilon.Canvas/Service/PaginatorHttpService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using System.Web;
using Epsilon.Abstractions.Http;
using Epsilon.Abstractions.Http.Json;
using Epsilon.Canvas.Abstractions.Converter;
using Epsilon.Canvas.Abstractions.Service;
using Epsilon.Http.Abstractions;
using Epsilon.Http.Abstractions.Json;

namespace Epsilon.Canvas.Service;

public class PaginatorHttpService : HttpService, IPaginatorHttpService
{
private const int Limit = 100;
private readonly ILinkHeaderConverter _headerConverter;

public PaginatorHttpService(HttpClient client, ILinkHeaderConverter headerConverter) : base(client)
Expand All @@ -19,25 +20,20 @@ public async Task<IEnumerable<TResult>> GetAllPages<TResult>(HttpMethod method,
{
var pages = new List<TResult>();
var page = "1";
const int limit = 100;

if (!uri.Contains('?'))
{
uri += "?";
}
else
{
uri += "&";
}
uri += !uri.Contains('?') ? "?" : "&";

do
{
var offset = pages.Count * limit;
var request = new HttpRequestMessage(method, $"{uri}per_page={limit}&offset={offset}&page={page}");
var offset = pages.Count * Limit;
var request = new HttpRequestMessage(method, $"{uri}per_page={Limit}&offset={offset}&page={page}");
var (response, value) = await Client.SendAsync<TResult>(request);
var links = _headerConverter.ConvertFrom(response);

pages.Add(value);
if (value != null)
{
pages.Add(value);
}

if (links.NextLink == null)
{
Expand All @@ -46,7 +42,7 @@ public async Task<IEnumerable<TResult>> GetAllPages<TResult>(HttpMethod method,

var query = HttpUtility.ParseQueryString(new Uri(links.NextLink).Query);
page = query["page"];
} while (pages.Count * limit % limit == 0);
} while (pages.Count * Limit % Limit == 0);

return pages;
}
Expand Down
2 changes: 1 addition & 1 deletion Epsilon.Canvas/Service/SubmissionHttpService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Text;
using Epsilon.Abstractions.Http;
using Epsilon.Canvas.Abstractions.Model;
using Epsilon.Canvas.Abstractions.Service;
using Epsilon.Http.Abstractions;

namespace Epsilon.Canvas.Service;

Expand Down
Loading

0 comments on commit 6eaba08

Please sign in to comment.