From d50ef411a3be62de10124f74d370be3dce81b84d Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Mon, 10 Mar 2025 13:49:49 -0700 Subject: [PATCH 1/2] Add ComponentCompletionComponent to show percentage of workgroups in the period that has completed the component --- src/app/services/workgroup.service.ts | 7 +- .../component-completion.component.spec.ts | 73 +++++++++++++++++++ .../component-completion.component.ts | 52 +++++++++++++ .../component-grading-view.component.html | 2 + .../component-grading-view.component.ts | 2 + src/messages.xlf | 7 ++ 6 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.spec.ts create mode 100644 src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.ts diff --git a/src/app/services/workgroup.service.ts b/src/app/services/workgroup.service.ts index d8181f23c51..3edccefe95d 100644 --- a/src/app/services/workgroup.service.ts +++ b/src/app/services/workgroup.service.ts @@ -5,11 +5,14 @@ import { ConfigService } from '../../assets/wise5/services/configService'; @Injectable() export class WorkgroupService { - constructor(private ConfigService: ConfigService, private http: HttpClient) {} + constructor( + private ConfigService: ConfigService, + private http: HttpClient + ) {} getWorkgroupsInPeriod(periodId: number): Map { const workgroups = new Map(); for (const workgroup of this.getWorkgroupsSortedById()) { - if (workgroup.periodId === periodId && workgroup.workgroupId != null) { + if (periodId === -1 || (workgroup.periodId === periodId && workgroup.workgroupId != null)) { workgroup.displayNames = this.ConfigService.getDisplayUsernamesByWorkgroupId( workgroup.workgroupId ); diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.spec.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.spec.ts new file mode 100644 index 00000000000..f9514d07767 --- /dev/null +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.spec.ts @@ -0,0 +1,73 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Node } from '../../../common/Node'; +import { MockProviders } from 'ng-mocks'; +import { WorkgroupService } from '../../../../../app/services/workgroup.service'; +import { ComponentServiceLookupService } from '../../../services/componentServiceLookupService'; +import { TeacherDataService } from '../../../services/teacherDataService'; +import { MultipleChoiceService } from '../../../components/multipleChoice/multipleChoiceService'; +import { ComponentCompletionComponent } from './component-completion.component'; + +let component: ComponentCompletionComponent; +let fixture: ComponentFixture; +describe('ComponentProgressComponent', () => { + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ComponentCompletionComponent], + providers: [ + MockProviders( + ComponentServiceLookupService, + MultipleChoiceService, + TeacherDataService, + WorkgroupService + ) + ] + }).compileComponents(); + + fixture = TestBed.createComponent(ComponentCompletionComponent); + component = fixture.componentInstance; + component.component = { id: 'component1', maxScore: 10 }; + component.node = { id: 'node1' } as Node; + component.periodId = 1; + }); + ngOnChanges(); +}); + +function ngOnChanges() { + describe('ngOnChanges()', () => { + beforeEach(() => { + const workgroups = new Map(); + workgroups.set(1, {}); + workgroups.set(2, {}); + spyOn(TestBed.inject(WorkgroupService), 'getWorkgroupsInPeriod').and.returnValue(workgroups); + spyOn(TestBed.inject(ComponentServiceLookupService), 'getService').and.returnValue( + new MultipleChoiceService() + ); + }); + describe('no student completed this work', () => { + beforeEach(() => + spyOn( + TestBed.inject(TeacherDataService), + 'getComponentStatesByWorkgroupIdAndComponentId' + ).and.returnValue([]) + ); + it('shows "0%"', () => { + component.ngOnChanges(); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent.trim()).toEqual('0%'); + }); + }); + describe('half of the students completed this work', () => { + beforeEach(() => + spyOn( + TestBed.inject(TeacherDataService), + 'getComponentStatesByWorkgroupIdAndComponentId' + ).and.returnValues([], [{ studentData: { studentChoices: [{ id: 'choice1' }] } }]) + ); + it('shows "50%"', () => { + component.ngOnChanges(); + fixture.detectChanges(); + expect(fixture.nativeElement.textContent.trim()).toEqual('50%'); + }); + }); + }); +} diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.ts new file mode 100644 index 00000000000..f690a3945a4 --- /dev/null +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.ts @@ -0,0 +1,52 @@ +import { Component, Input } from '@angular/core'; +import { Node } from '../../../common/Node'; +import { WorkgroupService } from '../../../../../app/services/workgroup.service'; +import { TeacherDataService } from '../../../services/teacherDataService'; +import { ComponentServiceLookupService } from '../../../services/componentServiceLookupService'; +import { DecimalPipe } from '@angular/common'; + +@Component({ + imports: [DecimalPipe], + selector: 'component-completion', + template: `{{ completion | number: '1.0-1' }}%` +}) +export class ComponentCompletionComponent { + protected completion: number; + @Input() component: any; + @Input() node: Node; + @Input() periodId: number; + + constructor( + private componentServiceLookupService: ComponentServiceLookupService, + private dataService: TeacherDataService, + private workgroupService: WorkgroupService + ) {} + + ngOnChanges(): void { + if (this.component && this.node) { + const workgroups = this.workgroupService.getWorkgroupsInPeriod(this.periodId); + const numWorkgroupsCompleted = Array.from(workgroups.keys()).filter((workgroupId) => + this.isCompleted(workgroupId) + ).length; + this.completion = (numWorkgroupsCompleted / workgroups.size) * 100; + } + } + + private isCompleted(workgroupId: number): boolean { + const service = this.componentServiceLookupService.getService(this.component.type); + const componentStates = this.dataService.getComponentStatesByWorkgroupIdAndComponentId( + workgroupId, + this.component.id + ); + return ['OpenResponse', 'Discussion'].includes(this.component.type) + ? service.isCompletedV2(this.node, this.component, { + componentStates: componentStates + }) + : service.isCompleted( + this.component, + componentStates, + this.dataService.getEventsByNodeId(this.node.id), + this.node + ); + } +} diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-grading-view/component-grading-view.component.html b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-grading-view/component-grading-view.component.html index 90cbf6a7573..08fd40faa76 100644 --- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-grading-view/component-grading-view.component.html +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-grading-view/component-grading-view.component.html @@ -1,5 +1,7 @@ +Component Completion @if (component?.type === 'MultipleChoice' && hasStudentWork) { 97,100 + + Component Completion + + src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-grading-view/component-grading-view.component.html + 4,6 + + Show/hide team's work for this step From 2c7f67968f17a2959ad359cb7676e6d2b8c6627d Mon Sep 17 00:00:00 2001 From: Jonathan Lim-Breitbart Date: Fri, 14 Mar 2025 10:28:08 -0700 Subject: [PATCH 2/2] Round completion percent to whole number --- .../component-completion/component-completion.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.ts b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.ts index f690a3945a4..f8b55522c06 100644 --- a/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.ts +++ b/src/assets/wise5/classroomMonitor/classroomMonitorComponents/component-completion/component-completion.component.ts @@ -8,7 +8,7 @@ import { DecimalPipe } from '@angular/common'; @Component({ imports: [DecimalPipe], selector: 'component-completion', - template: `{{ completion | number: '1.0-1' }}%` + template: `{{ completion | number: '1.0-0' }}%` }) export class ComponentCompletionComponent { protected completion: number;