Skip to content

Commit

Permalink
Adaptive learning: Allow editors to edit the competency link weight (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
JohannesStoehr authored Oct 30, 2024
1 parent d16872d commit 5562d95
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 3 deletions.
4 changes: 4 additions & 0 deletions src/main/webapp/app/entities/competency.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,11 @@ export enum CourseCompetencyType {

export const DEFAULT_MASTERY_THRESHOLD = 100;

export const HIGH_COMPETENCY_LINK_WEIGHT = 1;
export const MEDIUM_COMPETENCY_LINK_WEIGHT = 0.5;
export const LOW_COMPETENCY_LINK_WEIGHT = 0.25;
export const LOW_COMPETENCY_LINK_WEIGHT_CUT_OFF = 0.375; // halfway between low and medium
export const MEDIUM_COMPETENCY_LINK_WEIGHT_CUT_OFF = 0.75; // halfway between medium and high

export abstract class BaseCompetency implements BaseEntity {
id?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,23 @@
<fa-icon [icon]="getIcon(competencyLink.competency.taxonomy)" [fixedWidth]="true" />
{{ competencyLink.competency.title }}
</label>
@if (checkboxStates[competencyLink.competency.id]) {
<select
[ngModel]="
competencyLink.weight < LOW_COMPETENCY_LINK_WEIGHT_CUT_OFF
? LOW_COMPETENCY_LINK_WEIGHT
: competencyLink.weight < MEDIUM_COMPETENCY_LINK_WEIGHT_CUT_OFF
? MEDIUM_COMPETENCY_LINK_WEIGHT
: HIGH_COMPETENCY_LINK_WEIGHT
"
(ngModelChange)="updateLinkWeight(competencyLink, $event)"
ngbTooltip="{{ 'artemisApp.competency.link.weightTooltip' | artemisTranslate }}"
>
<option [ngValue]="LOW_COMPETENCY_LINK_WEIGHT" [jhiTranslate]="'artemisApp.competency.link.weightLabels.low'"></option>
<option [ngValue]="MEDIUM_COMPETENCY_LINK_WEIGHT" [jhiTranslate]="'artemisApp.competency.link.weightLabels.medium'"></option>
<option [ngValue]="HIGH_COMPETENCY_LINK_WEIGHT" [jhiTranslate]="'artemisApp.competency.link.weightLabels.high'"></option>
</select>
}
</div>
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { faQuestionCircle } from '@fortawesome/free-solid-svg-icons';
import { CompetencyLearningObjectLink, CourseCompetency, MEDIUM_COMPETENCY_LINK_WEIGHT, getIcon } from 'app/entities/competency.model';
import {
CompetencyLearningObjectLink,
CourseCompetency,
HIGH_COMPETENCY_LINK_WEIGHT,
LOW_COMPETENCY_LINK_WEIGHT,
LOW_COMPETENCY_LINK_WEIGHT_CUT_OFF,
MEDIUM_COMPETENCY_LINK_WEIGHT,
MEDIUM_COMPETENCY_LINK_WEIGHT_CUT_OFF,
getIcon,
} from 'app/entities/competency.model';
import { ActivatedRoute } from '@angular/router';
import { CourseStorageService } from 'app/course/manage/course-storage.service';
import { finalize } from 'rxjs';
Expand Down Expand Up @@ -40,6 +49,12 @@ export class CompetencySelectionComponent implements OnInit, ControlValueAccesso
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_onChange = (value: any) => {};

protected readonly HIGH_COMPETENCY_LINK_WEIGHT = HIGH_COMPETENCY_LINK_WEIGHT;
protected readonly MEDIUM_COMPETENCY_LINK_WEIGHT = MEDIUM_COMPETENCY_LINK_WEIGHT;
protected readonly LOW_COMPETENCY_LINK_WEIGHT = LOW_COMPETENCY_LINK_WEIGHT;
protected readonly LOW_COMPETENCY_LINK_WEIGHT_CUT_OFF = LOW_COMPETENCY_LINK_WEIGHT_CUT_OFF; // halfway between low and medium
protected readonly MEDIUM_COMPETENCY_LINK_WEIGHT_CUT_OFF = MEDIUM_COMPETENCY_LINK_WEIGHT_CUT_OFF; // halfway between medium and high

constructor(
private route: ActivatedRoute,
private courseStorageService: CourseStorageService,
Expand Down Expand Up @@ -122,7 +137,19 @@ export class CompetencySelectionComponent implements OnInit, ControlValueAccesso
}
}

updateLinkWeight(link: CompetencyLearningObjectLink, value: number) {
link.weight = value;

this._onChange(this.selectedCompetencyLinks);
this.valueChange.emit(this.selectedCompetencyLinks);
}

writeValue(value?: CompetencyLearningObjectLink[]): void {
this.competencyLinks?.forEach((link) => {
const selectedLink = value?.find((value) => value.competency?.id === link.competency?.id);
link.weight = selectedLink?.weight ?? MEDIUM_COMPETENCY_LINK_WEIGHT;
});

if (value && this.competencyLinks) {
// Compare the ids of the competencies instead of the whole objects
const ids = value.map((el) => el.competency?.id);
Expand Down
8 changes: 7 additions & 1 deletion src/main/webapp/i18n/de/competency.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,13 @@
"link": {
"title": "Verbundene Kompetenzen",
"lectureUnit": "Verbinde diese Vorlesungseinheit wahlweise mit einer oder mehreren Kompetenzen.",
"exercise": "Verbinde diese Übung wahlweise mit einer oder mehreren Kompetenzen."
"exercise": "Verbinde diese Übung wahlweise mit einer oder mehreren Kompetenzen.",
"weightTooltip": "Die Relevanz dieses Lernobjektes für die Kompetenz.",
"weightLabels": {
"low": "Niedrig",
"medium": "Mittel",
"high": "Hoch"
}
},
"competencyCard": {
"connectedLectureUnits": "Verbundene Vorlesungseinheiten",
Expand Down
8 changes: 7 additions & 1 deletion src/main/webapp/i18n/en/competency.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,13 @@
"link": {
"title": "Linked Competencies",
"lectureUnit": "Optionally link this lecture unit to one or more competencies.",
"exercise": "Optionally link this exercise to one or more competencies."
"exercise": "Optionally link this exercise to one or more competencies.",
"weightTooltip": "The relevance of this learning object for the competency.",
"weightLabels": {
"low": "Low",
"medium": "Medium",
"high": "High"
}
},
"competencyCard": {
"connectedLectureUnits": "Linked Lecture Units",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { By } from '@angular/platform-browser';
import { CourseStorageService } from 'app/course/manage/course-storage.service';
import { ChangeDetectorRef } from '@angular/core';
import { CourseCompetencyService } from 'app/course/competencies/course-competency.service';
import { Prerequisite } from 'app/entities/prerequisite.model';

describe('CompetencySelection', () => {
let fixture: ComponentFixture<CompetencySelectionComponent>;
Expand Down Expand Up @@ -122,6 +123,22 @@ describe('CompetencySelection', () => {
expect(component.selectedCompetencyLinks?.first()?.competency?.title).toBe('test');
});

it('should update link weight when value is written', () => {
jest.spyOn(courseStorageService, 'getCourse').mockReturnValue({
competencies: [{ id: 1, title: 'test' } as Competency, { id: 2, title: 'testAgain' } as Prerequisite, { id: 3, title: 'testMore' } as Competency],
});

fixture.detectChanges();

component.writeValue([
new CompetencyLearningObjectLink({ id: 1, title: 'other' } as Competency, 0.5),
new CompetencyLearningObjectLink({ id: 3, title: 'otherMore' } as Competency, 1),
]);
expect(component.selectedCompetencyLinks).toBeArrayOfSize(2);
expect(component.selectedCompetencyLinks?.first()?.weight).toBe(0.5);
expect(component.selectedCompetencyLinks?.last()?.weight).toBe(1);
});

it('should trigger change detection after loading competencies', () => {
jest.spyOn(courseStorageService, 'getCourse').mockReturnValue({ competencies: undefined });
const changeDetector = fixture.debugElement.injector.get(ChangeDetectorRef);
Expand Down

0 comments on commit 5562d95

Please sign in to comment.