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

Commit

Permalink
Replace exporter dependencies with standardised Open XML SDK (#45)
Browse files Browse the repository at this point in the history
* Release v1.0.0 (#41)

* 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>

* POC Open XML SDK

* Update

* Add KPI format and structure

* Added module names to document

* Testing the build and structure of exel files.

* Testing release deployment script.

* Delete file

* POC for excel export

* Code reformat

* Headers added

* Code cleanup

* Merge

* Removal nullability warnings

* Removal nullability warnings

* Removal tasks/awaits because they dont have nay use atm.

* Removal tasks/awaits because they dont have nay use atm.

* 'Var' is everything

* Bugfix

* Changed foreach loop

* Refactor and cleanup code, set custom column width for Excel export

---------

Co-authored-by: Jelle Maas <typiqally@gmail.com>
  • Loading branch information
NealGeilen and Typiqally committed May 26, 2023
1 parent 3dc1593 commit d423272
Show file tree
Hide file tree
Showing 14 changed files with 190 additions and 80 deletions.
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; }

Task Export(T data, string format);
void Export(T data, string format);
}
7 changes: 4 additions & 3 deletions Epsilon.Abstractions/Model/CourseAssignment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
{
public class CourseAssignment
{
public string Name { get; set; }
public string Score { get; set; }
public string Name { get; set; } = String.Empty;
public string Score { get; set; } = String.Empty;
public string Url { get; set; } = String.Empty;
}
}
}
4 changes: 2 additions & 2 deletions Epsilon.Abstractions/Model/CourseModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
public class CourseModule
{
public string Name { get; set; }
public IEnumerable<CourseOutcome> Kpis { get; set; }
public string Name { get; set; } = String.Empty;
public IEnumerable<CourseOutcome> Kpis { get; set; } = Enumerable.Empty<CourseOutcome>();
}
}
7 changes: 4 additions & 3 deletions Epsilon.Abstractions/Model/CourseOutcome.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
{
public class CourseOutcome
{
public string Name { get; set; }
public IEnumerable<CourseAssignment> Assignments { get; set; }
public string Name { get; set; } = String.Empty;
public IEnumerable<CourseAssignment> Assignments { get; set; } = Enumerable.Empty<CourseAssignment>();
public string Description { get; set; } = String.Empty;
}
}
}
10 changes: 2 additions & 8 deletions Epsilon.Abstractions/Model/ExportData.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Epsilon.Abstractions.Model
namespace Epsilon.Abstractions.Model
{
public class ExportData
{
public IEnumerable<CourseModule> CourseModules { get; set; }
public IEnumerable<CourseModule> CourseModules { get; set; } = Enumerable.Empty<CourseModule>();
}
}
2 changes: 1 addition & 1 deletion Epsilon.Canvas.Abstractions/Model/Outcome.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,4 @@ private string RemoveHtml()

return trimmed;
}
};
}
2 changes: 1 addition & 1 deletion Epsilon.Cli/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ private async Task ExecuteAsync()
{
_logger.LogInformation("Exporting to {Format} using {Exporter}...", format, exporter.GetType().Name);
// ReSharper disable once PossibleMultipleEnumeration
await exporter.Export(formattedItems, format);
exporter.Export(formattedItems, format);
}
}
catch (Exception ex)
Expand Down
4 changes: 2 additions & 2 deletions Epsilon/Epsilon.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="DocX" Version="2.3.0" />
<PackageReference Include="ExcelLibrary" Version="1.2011.7.31" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="6.0.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="DocumentFormat.OpenXml" Version="2.18.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
</ItemGroup>

</Project>
16 changes: 9 additions & 7 deletions Epsilon/Export/ExportDataPackager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace Epsilon.Export;

public class ExportDataPackager : IExportDataPackager
{
{
public async Task<ExportData> GetExportData(IAsyncEnumerable<ModuleOutcomeResultCollection> data)
{
var output = new List<CourseModule>();
Expand All @@ -27,30 +27,32 @@ public async Task<ExportData> GetExportData(IAsyncEnumerable<ModuleOutcomeResult
{
var assignmentIds = item.Collection.OutcomeResults
.Where(o => o.Link.Outcome == outcomeId && o.Grade() != null)
.Select(o => o.Link.Assignment);
.Select(static o => o.Link.Assignment).ToArray();

if (assignmentIds.Any())
{
var assignments = assignmentIds
.Select(assignmentId => new CourseAssignment
{
Name = alignments[assignmentId].Name + " | " + alignments[assignmentId].Url,
Name = alignments[assignmentId!].Name,
Url = alignments[assignmentId!].Url.ToString(),
Score = item.Collection.OutcomeResults
.First(o => o.Link.Outcome == outcomeId && o.Link.Assignment == assignmentId)
.Grade() ?? "N/A"
.Grade() ?? "N/A",
})
.ToList();

moduleKpis.Add(new CourseOutcome
{
Name = outcome.Title + " " + outcome.ShortDescription(),
Assignments = assignments,
Name = outcome.Title,
Description = outcome.ShortDescription(),
Assignments = assignments!,
});
}
}

module.Kpis = moduleKpis;

output.Add(module);
}

Expand Down
2 changes: 1 addition & 1 deletion Epsilon/Export/ExportOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public class ExportOptions

public string Formats { get; set; } = "console";

public string? Modules { get; set; } = null;
public string? Modules { get; set; }

public string FormattedOutputName => OutputName
.Replace("{DateTime}", DateTime.Now.ToString("ddMMyyyyHHmmss"));
Expand Down
2 changes: 1 addition & 1 deletion Epsilon/Export/Exporters/ConsoleModuleExporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public ConsoleModuleExporter(ILogger<ConsoleModuleExporter> logger)

public IEnumerable<string> Formats { get; } = new[] { "console", "logs" };

public async Task Export(ExportData data, string format)
public void Export(ExportData data, string format)
{
foreach (var module in data.CourseModules)
{
Expand Down
4 changes: 2 additions & 2 deletions Epsilon/Export/Exporters/CsvModuleExporter.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Data;
using Epsilon.Abstractions.Export;
using Microsoft.Extensions.Options;
using Epsilon.Abstractions.Model;
using Microsoft.Extensions.Options;

namespace Epsilon.Export.Exporters;

Expand All @@ -16,7 +16,7 @@ public CsvModuleExporter(IOptions<ExportOptions> options)

public IEnumerable<string> Formats { get; } = new[] { "csv" };

public async Task Export(ExportData data, string format)
public void Export(ExportData data, string format)
{
var dt = CreateDataTable(data.CourseModules);

Expand Down
117 changes: 97 additions & 20 deletions Epsilon/Export/Exporters/ExcelModuleExporter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using Epsilon.Abstractions.Export;
using System.Text;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Spreadsheet;
using Epsilon.Abstractions.Export;
using Epsilon.Abstractions.Model;
using ExcelLibrary.SpreadSheet;
using Microsoft.Extensions.Options;

namespace Epsilon.Export.Exporters;
Expand All @@ -16,36 +19,110 @@ public ExcelModuleExporter(IOptions<ExportOptions> options)

public IEnumerable<string> Formats { get; } = new[] { "xls", "xlsx", "excel" };

public async Task Export(ExportData data, string format)
public void Export(ExportData data, string format)
{
var workbook = new Workbook();
using var spreadsheetDocument =
SpreadsheetDocument.Create($"{_options.FormattedOutputName}.xlsx", SpreadsheetDocumentType.Workbook);
var workbookPart = spreadsheetDocument.AddWorkbookPart();
workbookPart.Workbook = new Workbook();

// Add Sheets to the Workbook.
spreadsheetDocument.WorkbookPart!.Workbook.AppendChild(new Sheets());

var cellValueBuilder = new StringBuilder();
var cellValueOutComeResultsBuilder = new StringBuilder();

foreach (var module in data.CourseModules)
{
var worksheet = new Worksheet(module.Name);
var kpis = module.Kpis;
var worksheetPart = CreateWorksheet(module, workbookPart);
worksheetPart.Worksheet.Append(
new Columns(
new Column { Min = 1, Max = 1, Width = 30, CustomWidth = true },
new Column { Min = 2, Max = 2, Width = 60, CustomWidth = true },
new Column { Min = 3, Max = 3, Width = 10, CustomWidth = true }
)
);

worksheet.Cells[0, 0] = new Cell("KPI");
worksheet.Cells[0, 1] = new Cell("Assignment(s)");
worksheet.Cells[0, 2] = new Cell("Score");
InsertCellsInWorksheet(
worksheetPart,
1,
CreateTextCell("KPI", "A", 1),
CreateTextCell("Assignment", "B", 1),
CreateTextCell("Grade", "C", 1)
);

for (var i = 0; i < kpis.Count(); i++)
uint count = 2;
foreach (var kpi in module.Kpis)
{
var kpi = kpis.ElementAt(i);
foreach (var assignment in kpi.Assignments)
{
cellValueBuilder.AppendLine($"{assignment.Name} {assignment.Url}");
cellValueOutComeResultsBuilder.AppendLine(assignment.Score);
}

InsertCellsInWorksheet(
worksheetPart,
count,
CreateTextCell($"{kpi.Name} {kpi.Description}", "A", count),
CreateTextCell(cellValueBuilder.ToString(), "B", count),
CreateTextCell(cellValueOutComeResultsBuilder.ToString(), "C", count)
);

worksheet.Cells[i + 1, 0] = new Cell(kpi.Name);
worksheet.Cells[i + 1, 1] = new Cell(string.Join('\n', kpi.Assignments.Select(a => a.Name)));
worksheet.Cells[i + 1, 2] = new Cell(string.Join('\n', kpi.Assignments.Select(a => a.Score)));
cellValueBuilder.Clear();
cellValueOutComeResultsBuilder.Clear();

count++;
}
}

worksheet.Cells.ColumnWidth[0, 0] = 500;
worksheet.Cells.ColumnWidth[0, 1] = 8000;
worksheet.Cells.ColumnWidth[0, 2] = 8000;
workbookPart.Workbook.Save();
spreadsheetDocument.Close();
}

workbook.Worksheets.Add(worksheet);
// Given a WorkbookPart, inserts a new worksheet.
private static WorksheetPart CreateWorksheet(CourseModule module, WorkbookPart workbookPart)
{
var worksheetPart = workbookPart.AddNewPart<WorksheetPart>();
worksheetPart.Worksheet = new Worksheet(new SheetData());
worksheetPart.Worksheet.Save();

var sheets = workbookPart.Workbook.GetFirstChild<Sheets>()!;
var relationshipId = workbookPart.GetIdOfPart(worksheetPart);

uint sheetId = 1;
if (sheets.Elements<Sheet>().Any())
{
sheetId = sheets.Elements<Sheet>().Select(static s => s.SheetId!.Value).Max() + 1;
}

// We're forced to xls because of the older format
workbook.Save($"{_options.FormattedOutputName}.xls");
var sheet = new Sheet { Id = relationshipId, SheetId = sheetId, Name = module.Name };
sheets.Append(sheet);

workbookPart.Workbook.Save();

return worksheetPart;
}

private static void InsertCellsInWorksheet(WorksheetPart worksheetPart, uint rowIndex, params Cell[] cells)
{
var worksheet = worksheetPart.Worksheet;
var sheetData = worksheet.GetFirstChild<SheetData>()!;

var row = sheetData.Elements<Row>().FirstOrDefault(r => r.RowIndex! == rowIndex);
if (row == null)
{
row = new Row { RowIndex = rowIndex };
sheetData.Append(row);
}

row.Append(cells);
worksheet.Save();
}

private static Cell CreateTextCell(string value, string columnName, uint rowIndex) => new()
{
CellReference = columnName + rowIndex,
CellValue = new CellValue(value),
DataType = CellValues.String,
};
}
Loading

0 comments on commit d423272

Please sign in to comment.