diff --git a/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html b/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html index e16ea18d72a..7a4eaa4bf04 100644 --- a/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html +++ b/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html @@ -1,61 +1,51 @@ - -
+ +

- - @if (first) { - (Source Bucket) - } + crop_16_9 

-
    - @for (choice of bucket.choices; track $index) { - @if ($index < 3 || getBucketShowMore(bucket.value)) { + @if (choice.choiceDataPoints.length > 0) { + +
+ } @else { +
+ do_not_disturb +
Not moved by any students
+
+ }
+
-

Choice Frequency

-
- @if (bucketData.length > 0) { -

- Number of teams that moved each item (choice) into the different buckets (categories). +

Bucket Frequency

+
+ @if (choiceData.length > 0) { +

+ Number of times each item crop_16_9 was moved + into the different buckets inventory_2.

-
-
- -
- @for (bucket of bucketData; track $index) { - @if ($index > 0) { -
- -
- } +
+ @for (choice of choiceData; track $index) { +
+ +
}
} @else { diff --git a/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.scss b/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.scss index 63b2f99764e..9fd95b7f60d 100644 --- a/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.scss +++ b/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.scss @@ -1,4 +1,4 @@ -@use "tailwindcss"; +@reference "tailwindcss"; h3, .mat-subtitle-1 { @@ -6,16 +6,12 @@ h3, margin-top: 0; } -.bucket { - @apply p-2 mb-2 rounded-md; -} - .choice { - @apply flex gap-1 px-2 py-1 mt-1 rounded-md bg-white border border-neutral-200 text-sm; + @apply p-2 mb-2 rounded-md; } -.mat-icon { - vertical-align: middle; +.bucket { + @apply flex gap-1 px-2 py-1 mt-1 rounded-md bg-white border border-neutral-200 text-sm items-start justify-between; } ul { diff --git a/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.spec.ts b/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.spec.ts index bad43b748b0..69a031f0963 100644 --- a/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.spec.ts +++ b/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.spec.ts @@ -51,45 +51,40 @@ describe('MatchSummaryDisplayComponent', () => { expect(component).toBeTruthy(); }); - it('should show the correct number of buckets and choices', () => { - expect(fixtureQueryAll(fixture, '.bucket').length).toEqual(3); + it('should display one card per unique choice', () => { expect(fixtureQueryAll(fixture, '.choice').length).toEqual(5); - fixture.nativeElement.querySelector('a').click(); - fixture.detectChanges(); - expect(fixtureQueryAll(fixture, '.choice').length).toEqual(6); }); - it('should only show Show more button if more than 3 choices in bucket', () => { - expect(fixtureQueryAll(fixture, 'a').length).toEqual(1); + it('should order choices by total count descending then alphabetically', () => { + const cards = fixtureQueryAll(fixture, '.choice'); + const labels = Array.from(cards).map((el) => el.querySelector('h3')?.textContent?.trim()); + expect(labels[0]).toContain('Choice B'); + expect(labels[1]).toContain('Choice D'); + expect(labels[2]).toContain('Choice C'); + expect(labels[3]).toContain('Choice E'); + expect(labels[4]).toContain('Choice A'); }); - it('should display choices within bucket sorted by count', () => { - const choices = fixtureQueryAll(fixture, '.choice'); - expect(choices[0].textContent.includes('Choice B')); - expect(choices[1].textContent.includes('Choice A')); - expect(choices[2].textContent.includes('Choice C')); - expect(choices[3].textContent.includes('Choice D')); + it('should show bucket rows sorted by count within each choice', () => { + const cards = fixtureQueryAll(fixture, '.choice'); + const choiceDCard = cards[1]; + const bucketRows = choiceDCard.querySelectorAll('.bucket'); + expect(bucketRows.length).toEqual(2); + expect(bucketRows[0].textContent).toContain('Bucket 2'); + expect(bucketRows[0].textContent).toContain('2'); }); - it('should show the correct count on each choice per bucket', () => { - const choices = fixtureQueryAll(fixture, '.choice'); - expect(choices[0].textContent.includes('3')); - expect(choices[1].textContent.includes('2')); - expect(choices[2].textContent.includes('2')); - expect(choices[3].textContent.includes('1')); + it('should show the correct count for Choice B in Bucket 1', () => { + const cards = fixtureQueryAll(fixture, '.choice'); + const choiceBCard = cards[0]; + expect(choiceBCard.textContent).toContain('3'); }); - it('should change Show more to Show less when clicked', () => { - let button = fixture.nativeElement.querySelector('a'); - expect(button.innerText).toEqual('Show more'); - button.click(); - fixture.detectChanges(); - button = fixture.nativeElement.querySelector('a'); - expect(button.innerText).toEqual('Show less'); - button.click(); - fixture.detectChanges(); - button = fixture.nativeElement.querySelector('a'); - expect(button.innerText).toEqual('Show more'); + it('should show "Not moved by any students" for choices left in the source bucket', () => { + const cards = fixtureQueryAll(fixture, '.choice'); + const choiceACard = cards[4]; + expect(choiceACard.textContent).toContain('Not moved by any students'); + expect(choiceACard.querySelectorAll('.bucket').length).toEqual(1); }); }); @@ -114,17 +109,18 @@ function getComponentStates(): any { id: '0', type: 'bucket', value: 'Choices', - items: [] - }, - { - id: 'b1', - value: 'Bucket 1', items: [ { isIncorrectPosition: null, id: 'a', value: 'Choice A' - }, + } + ] + }, + { + id: 'b1', + value: 'Bucket 1', + items: [ { isIncorrectPosition: null, id: 'b', @@ -169,17 +165,18 @@ function getComponentStates(): any { id: '0', type: 'bucket', value: 'Choices', - items: [] - }, - { - id: 'b1', - value: 'Bucket 1', items: [ { isIncorrectPosition: null, id: 'a', value: 'Choice A' - }, + } + ] + }, + { + id: 'b1', + value: 'Bucket 1', + items: [ { isIncorrectPosition: null, id: 'b', @@ -240,7 +237,13 @@ function getComponentStates(): any { { id: 'b2', value: 'Bucket 2', - items: [] + items: [ + { + isIncorrectPosition: null, + id: 'b', + value: 'Choice D' + } + ] } ] }, diff --git a/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.ts b/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.ts index fd039becfb9..ddb2039f1de 100644 --- a/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.ts +++ b/src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.ts @@ -1,7 +1,6 @@ import { CommonModule } from '@angular/common'; import { Component, Input, OnInit } from '@angular/core'; -import { MatchContent } from '../../../components/match/MatchContent'; -import { MatchSummaryData } from '../summary-data/MatchSummaryData'; +import { ChoiceData, MatchSummaryData } from '../summary-data/MatchSummaryData'; import { MatchSummaryDataPoint } from '../summary-data/MatchSummaryDataPoint'; import { MatIconModule } from '@angular/material/icon'; import { TeacherSummaryDisplayComponent } from '../teacher-summary-display.component'; @@ -16,71 +15,45 @@ import { TeacherSummaryDisplayComponent } from '../teacher-summary-display.compo templateUrl: './match-summary-display.component.html' }) export class MatchSummaryDisplayComponent extends TeacherSummaryDisplayComponent implements OnInit { - protected bucketData: { value: string; choices: MatchSummaryDataPoint[] }[] = []; - private bucketsShowMore: Map = new Map(); - private bucketValues: Set = new Set(); + protected choiceData: ChoiceData[] = []; @Input() expanded: boolean; - protected isChoiceReuseMatch: boolean; private matchSummaryData: MatchSummaryData; ngOnInit(): void { - this.setIsChoiceReuseMatch(); this.generateSummary(); } - private setIsChoiceReuseMatch(): void { - this.isChoiceReuseMatch = ( - this.projectService.getComponent(this.nodeId, this.componentId) as MatchContent - ).choiceReuseEnabled; - } - private generateSummary(): void { this.getLatestWork().subscribe((componentStates) => { - this.bucketData = []; - this.bucketValues.clear(); + this.choiceData = []; this.matchSummaryData = new MatchSummaryData( this.projectService.injectAssetPaths(componentStates) ); - this.setBucketValues(); - this.setBucketData(); - this.setBucketShowMore(); + this.setChoiceData(); }); } - protected setBucketValues(): void { - this.matchSummaryData - .getBucketsData() - .forEach((bucket) => this.bucketValues.add(bucket.bucketValue)); - } - - protected setBucketData(): void { - this.bucketValues.forEach((value) => - this.bucketData.push({ value: value, choices: this.getBucketDataByValue(value) }) - ); - } - - private getBucketDataByValue(bucketValue: string): MatchSummaryDataPoint[] { - return this.matchSummaryData - .getBucketsData() - .find((bucket) => bucket.bucketValue === bucketValue) - .bucketDataPoints.sort(this.sortChoices); - } - - private sortChoices(choiceA: MatchSummaryDataPoint, choiceB: MatchSummaryDataPoint): number { - return choiceB.getCount() - choiceA.getCount(); + protected setChoiceData(): void { + this.matchSummaryData.getChoicesData().forEach((choice) => { + this.choiceData.push({ + choiceValue: choice.choiceValue, + choiceDataPoints: choice.choiceDataPoints.sort(this.sortBuckets) + }); + }); + this.choiceData.sort(this.sortChoices); } - private setBucketShowMore(): void { - this.bucketValues.forEach((value) => this.bucketsShowMore.set(value, false)); + private getTotalCount(choice: ChoiceData): number { + return choice.choiceDataPoints.reduce((sum, dp) => sum + dp.getCount(), 0); } - protected getBucketShowMore(bucketValue: string): boolean { - return this.bucketsShowMore.get(bucketValue); - } + private sortChoices = (a: ChoiceData, b: ChoiceData): number => { + const countDiff = this.getTotalCount(b) - this.getTotalCount(a); + return countDiff !== 0 ? countDiff : a.choiceValue.localeCompare(b.choiceValue); + }; - protected toggleBucketShowMore(bucketValue: string, event: Event): void { - event.preventDefault(); - this.bucketsShowMore.set(bucketValue, !this.bucketsShowMore.get(bucketValue)); + private sortBuckets(a: MatchSummaryDataPoint, b: MatchSummaryDataPoint): number { + return b.getCount() - a.getCount(); } protected renderDisplay(): void { diff --git a/src/assets/wise5/directives/teacher-summary-display/summary-data/MatchSummaryData.ts b/src/assets/wise5/directives/teacher-summary-display/summary-data/MatchSummaryData.ts index 9c2aa5cb1a1..46c6c122c79 100644 --- a/src/assets/wise5/directives/teacher-summary-display/summary-data/MatchSummaryData.ts +++ b/src/assets/wise5/directives/teacher-summary-display/summary-data/MatchSummaryData.ts @@ -2,84 +2,74 @@ import { ComponentState } from '../../../../../app/domain/componentState'; import { MatchSummaryDataPoint } from './MatchSummaryDataPoint'; import { SummaryData } from '../../summary-display/summary-data/SummaryData'; -type BucketData = { bucketValue: string; bucketDataPoints: MatchSummaryDataPoint[] }; +export type ChoiceData = { choiceValue: string; choiceDataPoints: MatchSummaryDataPoint[] }; /** - * Summary data for all choices in all buckets + * Summary data for all choices, each with a breakdown per bucket */ export class MatchSummaryData extends SummaryData { - private isOrderedMatch: boolean; - protected bucketsData: BucketData[] = []; + protected choicesData: ChoiceData[] = []; constructor(componentStates: ComponentState[]) { super(); - this.extractBucketData(componentStates); + this.extractChoiceData(componentStates); } - getBucketsData(): BucketData[] { - return this.bucketsData; + getChoicesData(): ChoiceData[] { + return this.choicesData; } - private extractBucketData(componentStates: ComponentState[]): void { + private extractChoiceData(componentStates: ComponentState[]): void { componentStates.forEach((componentState) => { - componentState.studentData.buckets.forEach((bucketStudentData) => { - const newBucketData = { bucketValue: bucketStudentData.value, bucketDataPoints: [] }; - bucketStudentData.items.forEach((item) => { - this.extractChoiceDataPerBucket(item.value, bucketStudentData.value, newBucketData); - this.checkIsOrderedMatch(item.isIncorrectPosition); - }); - this.addNewBucketDataToSummaryData(newBucketData); + componentState.studentData.buckets.forEach((bucketStudentData, index) => { + if (index === 0) { + bucketStudentData.items.forEach((item) => this.registerChoice(item.value)); + } else { + bucketStudentData.items.forEach((item) => { + this.extractBucketDataPerChoice(item.value, bucketStudentData.value); + }); + } }); }); } - private extractChoiceDataPerBucket( - itemValue: string, - bucketValue: string, - bucketData: BucketData - ): void { - const summaryDataPoint = this.findSummaryDataPoint(itemValue, bucketValue); - if (summaryDataPoint) { - summaryDataPoint.incrementCount(1); - } else { - const newDataPoint = new MatchSummaryDataPoint(itemValue, 1, bucketValue); - this.summaryDataPoints.push(newDataPoint); - bucketData.bucketDataPoints.push(newDataPoint); + private registerChoice(choiceValue: string): void { + if (!this.findChoiceByValue(choiceValue)) { + this.choicesData.push({ choiceValue, choiceDataPoints: [] }); } } - private addNewBucketDataToSummaryData(newBucketData: BucketData): void { - const bucketMatch = this.findBucketByValue(newBucketData.bucketValue); - if (bucketMatch) { - bucketMatch.bucketDataPoints = bucketMatch.bucketDataPoints.concat( - newBucketData.bucketDataPoints - ); + private extractBucketDataPerChoice(choiceValue: string, bucketValue: string): void { + const dataPoint = this.findSummaryDataPoint(choiceValue, bucketValue); + if (dataPoint) { + dataPoint.incrementCount(1); } else { - this.bucketsData.push(newBucketData); + const newDataPoint = new MatchSummaryDataPoint(bucketValue, 1, choiceValue); + this.summaryDataPoints.push(newDataPoint); + this.addDataPointToChoiceData(choiceValue, newDataPoint); } } - private findBucketByValue(bucketValue: string): BucketData { - return this.bucketsData.find((bucketData) => bucketData.bucketValue === bucketValue); + private addDataPointToChoiceData(choiceValue: string, dataPoint: MatchSummaryDataPoint): void { + const choiceMatch = this.findChoiceByValue(choiceValue); + if (choiceMatch) { + choiceMatch.choiceDataPoints.push(dataPoint); + } else { + this.choicesData.push({ choiceValue, choiceDataPoints: [dataPoint] }); + } } - private findSummaryDataPoint(itemValue: string, bucketValue: string): MatchSummaryDataPoint { - return this.bucketsData - .find((bucket) => bucket.bucketValue === bucketValue) - ?.bucketDataPoints.find((dataPoint) => dataPoint.getId() === itemValue); + private findChoiceByValue(choiceValue: string): ChoiceData { + return this.choicesData.find((c) => c.choiceValue === choiceValue); } - private checkIsOrderedMatch(isIncorrectPosition: boolean): void { - if (!this.isOrderedMatch) { - this.isOrderedMatch = [true, false].includes(isIncorrectPosition); - } + private findSummaryDataPoint(choiceValue: string, bucketValue: string): MatchSummaryDataPoint { + return this.choicesData + .find((c) => c.choiceValue === choiceValue) + ?.choiceDataPoints.find((dp) => dp.getBucketValue() === bucketValue); } protected generateNewDataPoint(id: string | number): MatchSummaryDataPoint { return new MatchSummaryDataPoint(id); } - - getIsOrderedMatch(): boolean { - return this.isOrderedMatch; - } } diff --git a/src/assets/wise5/directives/teacher-summary-display/summary-data/MatchSummaryDataPoint.ts b/src/assets/wise5/directives/teacher-summary-display/summary-data/MatchSummaryDataPoint.ts index 632dd6cbaf4..4f2b5591963 100644 --- a/src/assets/wise5/directives/teacher-summary-display/summary-data/MatchSummaryDataPoint.ts +++ b/src/assets/wise5/directives/teacher-summary-display/summary-data/MatchSummaryDataPoint.ts @@ -4,14 +4,18 @@ import { SummaryDataPoint } from '../../summary-display/summary-data/SummaryData * Summary data for one choice in one bucket */ export class MatchSummaryDataPoint extends SummaryDataPoint { - private bucketValue: string; + private choiceValue: string; - constructor(id: number | string, count?: number, bucketValue?: string) { + constructor(id: number | string, count?: number, choiceValue?: string) { super(id, count); - this.bucketValue = bucketValue; + this.choiceValue = choiceValue; + } + + getChoiceValue(): string { + return this.choiceValue; } getBucketValue(): string { - return this.bucketValue; + return this.getId() as string; } } diff --git a/src/messages.xlf b/src/messages.xlf index 8117d633672..cacb0a16381 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -15155,10 +15155,6 @@ Are you sure you want to proceed? src/assets/wise5/directives/teacher-summary-display/ideas-summary/ideas-summary.component.html 74,77 - - src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html - 29,35 - Team has not saved any work @@ -15878,6 +15874,10 @@ Are you sure you want to proceed? src/assets/wise5/classroomMonitor/dataExport/select-step-and-component-checkboxes/select-step-and-component-checkboxes.component.html 61,63 + + src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html + 4,5 + Team ID @@ -22704,10 +22704,6 @@ If this problem continues, let your teacher know and move on to the next activit src/assets/wise5/directives/teacher-summary-display/ideas-summary/ideas-summary.component.html 72,74 - - src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html - 27,30 - Your students' ideas will show up here as they are detected in the activity. @@ -22716,32 +22712,46 @@ If this problem continues, let your teacher know and move on to the next activit 77,81 - - Source Bucket + + Bucket src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html - 6,10 + 12,15 - - Choice Frequency + + Not moved src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html - 37,38 + 26,27 - - Number of teams that moved each item (choice) into the different buckets (categories). + + Not moved by any students src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html - 41,44 + 27,33 + + + + Bucket Frequency + + src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html + 34,35 + + + + Number of times each item crop_16_9 was moved into the different buckets inventory_2. + + src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html + 38,41 Your students' choices will show up here when they complete the activity. src/assets/wise5/directives/teacher-summary-display/match-summary-display/match-summary-display.component.html - 63,68 + 53,58