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

Add KPI table component #108

Open
wants to merge 23 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
93e456b
Remove unused imports
Jun 16, 2023
1fd27fc
Add KPI table data model & component fetcher
Jun 16, 2023
27e8512
Add legenda data and frontend component
Jun 20, 2023
18d8ce3
Add support for kpi table background color based on kpi level
HansenSven Jun 20, 2023
9015ee5
Add kpi level indicator to kpi table & cleanup code
HansenSven Jun 21, 2023
91858c3
Cleanup kpi table component
HansenSven Jun 21, 2023
eb9ed93
Add hyperlink to assignmentName in KpiTable Word Export & Cleanup Kpi…
Jun 23, 2023
fc40e1b
Add support for personal development kpi's to KPI Table
Jun 23, 2023
d39e859
Move assessmentRating points to grade logic to model
HansenSven Jun 26, 2023
c02e970
Remove commented code
HansenSven Jun 26, 2023
22268f9
Use existing records to determine order, color and name & word export…
HansenSven Jun 26, 2023
81832ae
Merge branch 'develop' into feat/kpi-table
HansenSven Jun 26, 2023
8c01c63
Fix lint issues
HansenSven Jun 27, 2023
bb6f823
Merge branch 'develop' into feat/kpi-table
HansenSven Jun 27, 2023
96c5206
Fix lint issues
HansenSven Jun 27, 2023
9003e4e
Implemented Select
NealGeilen Jun 29, 2023
72ed003
Set equal order as KPI Matrix
NealGeilen Jun 29, 2023
ca0a5ce
Implementation of a dictionary
NealGeilen Jun 29, 2023
6ef160f
Changes in naming grades
NealGeilen Jun 29, 2023
410748d
Merge branch 'develop' into feat/kpi-table
NealGeilen Jun 29, 2023
338ec5b
Bugfix
NealGeilen Jun 29, 2023
ff0d3e5
Bug
NealGeilen Jun 29, 2023
b96a277
Removed unused code
NealGeilen Jun 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 131 additions & 0 deletions Epsilon.Abstractions/Component/KpiTable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

namespace Epsilon.Abstractions.Component;

public record KpiTable(
IDictionary<int, KpiTableEntry> KpiTableEntries
) : IWordCompetenceComponent
{
public void AddToWordDocument(MainDocumentPart mainDocumentPart)
{
var body = new Body();

// Create a table to display outcomes, assignments, and grades
var table = new Table();

// Define column header texts
var columnsHeaders = new Dictionary<string, string> { { "KPI", "3000" }, { "Assignments", "5000" }, { "Grades", "1000" }, };

// Create the table header row
var headerRow = new TableRow();

// Create the header cells
foreach (var columnHeader in columnsHeaders)
{
headerRow.AppendChild(CreateTableCellWithBorders(columnHeader.Value, new Paragraph(new Run(new Text(columnHeader.Key)))));
}

// Add the header row to the table
table.AppendChild(headerRow);

// Create the table body rows and cells
foreach (var entry in KpiTableEntries.ToList().OrderByDescending(static e => e.Value.Kpi))
{
var tableRow = new TableRow();

// Outcome (KPI) column
tableRow.AppendChild(CreateTableCellWithBorders("3000", new Paragraph(new Run(new Text(entry.Value.Kpi)))));

// Assignments column
var assignmentsParagraph = new Paragraph();
var assignmentsRun = assignmentsParagraph.AppendChild(new Run());

foreach (var assignment in entry.Value.Assignments)
{
var rel = mainDocumentPart.AddHyperlinkRelationship(assignment.Link, true);
var relationshipId = rel.Id;

var runProperties = new RunProperties(
new Underline { Val = UnderlineValues.Single, });

assignmentsRun.AppendChild(new Hyperlink(new Run(runProperties, new Text(assignment.Name)))
{
History = OnOffValue.FromBoolean(true),
Id = relationshipId,
});

assignmentsRun.AppendChild(new Break());
}

tableRow.AppendChild(CreateTableCellWithBorders("5000", assignmentsParagraph));

// Grades column
var grades = entry.Value.Assignments.Select(static a => a.Grade);
var gradesParagraph = new Paragraph();
var gradesRun = gradesParagraph.AppendChild(new Run());

foreach (var grade in grades)
{
gradesRun.AppendChild(new Text(grade));
gradesRun.AppendChild(new Break());
}

tableRow.AppendChild(CreateTableCellWithBorders("1000", gradesParagraph));

// Add the row to the table
table.AppendChild(tableRow);
}

// Newline to separate the table from the rest of the document
body.Append(new Paragraph(new Run(new Text(""))));

// Add the table to the document
body.AppendChild(table);

mainDocumentPart.Document.AppendChild(body);
}

private static TableCell CreateTableCellWithBorders(string? width, params OpenXmlElement[] elements)
{
var cell = new TableCell();
var cellProperties = new TableCellProperties();
var borders = new TableCellBorders(
new LeftBorder
{
Val = BorderValues.Single,
},
new RightBorder
{
Val = BorderValues.Single,
},
new TopBorder
{
Val = BorderValues.Single,
},
new BottomBorder
{
Val = BorderValues.Single,
});

foreach (var element in elements)
{
cell.Append(element);
}

if (width != null)
{
cellProperties.Append(new TableCellWidth
{
Type = TableWidthUnitValues.Dxa,
Width = width,
});
}

cellProperties.Append(borders);
cell.PrependChild(cellProperties);

return cell;
}
}
9 changes: 9 additions & 0 deletions Epsilon.Abstractions/Component/KpiTableEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Epsilon.Abstractions.Model;

namespace Epsilon.Abstractions.Component;

public record KpiTableEntry(
string Kpi,
MasteryLevel MasteryLevel,
IEnumerable<KpiTableEntryAssignment> Assignments
);
7 changes: 7 additions & 0 deletions Epsilon.Abstractions/Component/KpiTableEntryAssignment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Epsilon.Abstractions.Component;

public record KpiTableEntryAssignment(
string Name,
string Grade,
Uri Link
);
9 changes: 9 additions & 0 deletions Epsilon.Canvas.Abstractions/Model/GraphQl/AssessmentRating.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,13 @@ public record AssessmentRating(
)
{
public bool IsMastery => Points >= Criterion?.MasteryPoints;

public string? Grade => Points switch
{
>= 5.0 => "Outstanding",
>= 4.0 => "Good",
>= 3.0 => "Sufficient",
>= 0.0 => "Insufficient",
_ => null,
};
}
1 change: 1 addition & 0 deletions Epsilon.Canvas.Abstractions/Model/GraphQl/Assignment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace Epsilon.Canvas.Abstractions.Model.GraphQl;

public record Assignment(
[property: JsonPropertyName("name")] string? Name,
[property: JsonPropertyName("htmlUrl")] Uri? HtmlUrl,
[property: JsonPropertyName("modules")] IEnumerable<Module>? Modules ,
[property: JsonPropertyName("rubric")] Rubric? Rubric
);
68 changes: 68 additions & 0 deletions Epsilon.Host.Frontend/src/components/KpiTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<script lang="ts" setup>
import { Api } from "../logic/Api"
import { Ref, ref } from "vue"

const api = new Api()
const today = new Date()
const otherMonth = new Date()
otherMonth.setMonth(otherMonth.getMonth() - 3)

const data: Ref<unknown> = ref<unknown>()
const Kpis: Ref<unknown> = ref<unknown>([])

api.component
.componentDetail("kpi_table", {
startDate: formatDate(otherMonth),
endDate: formatDate(today),
})
.then((result) => {
data.value = result.data
Kpis.value = result.data.entries
})

function formatDate(date: Date): string {
const year = date.getFullYear()
const month = `${date.getMonth() + 1}`.padStart(2, "0")
const day = date.getDate()
return `${year}-${month}-${day}`
}
</script>

<template>
<table v-if="data">
<tr>
<th>KPI</th>
<th>Assignments</th>
<th>Grades</th>
</tr>
<tr v-for="KPI of Kpis" :key="KPI">
<td
:style="{
border: '3px solid ' + KPI.masteryLevel.color,
}">
{{ KPI.kpi }}
</td>
<td>
<div
v-for="assignment of KPI.assignments"
:key="assignment"
:style="{ textAlign: 'start' }">
<a :href="assignment.link">{{ assignment.name }}</a>
</div>
</td>
<td>
<div v-for="assignment of KPI.assignments" :key="assignment">
{{ assignment.grade }}
</div>
</td>
</tr>
</table>
</template>

<style scoped>
td,
th {
border: 2px solid;
padding: 1rem;
}
</style>
5 changes: 5 additions & 0 deletions Epsilon.Host.Frontend/src/views/PerformanceDashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ onMounted(() => {
height: 64px;
}

.loading-icon {
width: 64px;
height: 64px;
}

@media screen and (min-width: 580px) {
.performance-dashboard {
display: grid;
Expand Down
4 changes: 3 additions & 1 deletion Epsilon.Host.WebApi/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
});

builder.Services.AddControllers();

builder.Services.AddRouting(static options => options.LowercaseUrls = true);

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
Expand All @@ -37,12 +38,14 @@
{ "persona_page", services.GetRequiredService<ICompetenceComponentFetcher<PersonaPage>>() },
{ "competence_profile", services.GetRequiredService<ICompetenceComponentFetcher<CompetenceProfile>>() },
{ "kpi_matrix", services.GetRequiredService<ICompetenceComponentFetcher<KpiMatrixCollection>>() },
{ "kpi_table", services.GetRequiredService<ICompetenceComponentFetcher<KpiTable>>() },
}
));

builder.Services.AddScoped<ICompetenceComponentFetcher<PersonaPage>, PersonaPageComponentFetcher>();
builder.Services.AddScoped<ICompetenceComponentFetcher<CompetenceProfile>, CompetenceProfileComponentFetcher>();
builder.Services.AddScoped<ICompetenceComponentFetcher<KpiMatrixCollection>, KpiMatrixComponentFetcher>();
builder.Services.AddScoped<ICompetenceComponentFetcher<KpiTable>, KpiTableComponentFetcher>();

var app = builder.Build();

Expand All @@ -66,7 +69,6 @@
});
});


app.UseHttpsRedirection();

app.UseCors();
Expand Down
14 changes: 6 additions & 8 deletions Epsilon/Component/CompetenceProfileComponentFetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,17 @@ IHboIDomain domain

if (queryResponse.Data != null)
{
foreach (var course in queryResponse.Data.Courses)
foreach (var course in queryResponse.Data.Courses!)
{
foreach (var submissionsConnection in course.SubmissionsConnection.Nodes)
foreach (var submission in course.SubmissionsConnection!.Nodes.Select(static sm => sm.SubmissionsHistories.Nodes
.Where(static h => h.RubricAssessments.Nodes.Any())
.MaxBy(static h => h.Attempt)))
{
var submission = submissionsConnection.SubmissionsHistories.Nodes
.Where(static h => h.RubricAssessments.Nodes.Any())
.MaxBy(static h => h.Attempt);

if (submission != null)
{
var rubricAssessments = submission.RubricAssessments.Nodes;
var rubricAssessments = submission.RubricAssessments?.Nodes;

foreach (var assessmentRating in rubricAssessments.SelectMany(static rubricAssessment => rubricAssessment.AssessmentRatings.Where(static ar =>
foreach (var assessmentRating in rubricAssessments?.SelectMany(static rubricAssessment => rubricAssessment.AssessmentRatings.Where(static ar =>
ar is { Points: not null, Criterion.MasteryPoints: not null, Criterion.Outcome: not null, } && ar.Points >= ar.Criterion.MasteryPoints)))
{
if (FhictConstants.ProfessionalTasks.TryGetValue(assessmentRating.Criterion.Outcome.Id, out var professionalTask))
Expand Down
10 changes: 4 additions & 6 deletions Epsilon/Component/KpiMatrixComponentFetcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,12 @@ public override async Task<KpiMatrixCollection> Fetch(DateTime startDate, DateTi


var assignments = new List<KpiMatrixAssignment>();
foreach (var course in outcomes.Data!.Courses!)
foreach (var course in outcomes!.Data!.Courses!)
{
foreach (var submissionsConnection in course.SubmissionsConnection!.Nodes)
foreach (var submission in course.SubmissionsConnection!.Nodes.Select(sm => sm.SubmissionsHistories.Nodes
.Where(sub => sub.SubmittedAt > startDate && sub.SubmittedAt < endDate)
.MaxBy(static h => h.Attempt)))
{
var submission = submissionsConnection.SubmissionsHistories.Nodes
.Where(sub => sub.SubmittedAt > startDate && sub.SubmittedAt < endDate)
.MaxBy(static h => h.Attempt);

if (submission is
{
Assignment.Rubric: not null,
Expand Down
Loading