Skip to content

Commit 94d8534

Browse files
feat(CRater authoring): Populate rubric automatically (#2239)
Co-authored-by: Jonathan Lim-Breitbart <breity10@gmail.com>
1 parent ab4652e commit 94d8534

File tree

16 files changed

+266
-272
lines changed

16 files changed

+266
-272
lines changed

src/app/services/cRaterService.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { HttpTestingController, provideHttpClientTesting } from '@angular/common
55
import { CRaterIdea } from '../../assets/wise5/components/common/cRater/CRaterIdea';
66
import { CRaterScore } from '../../assets/wise5/components/common/cRater/CRaterScore';
77
import { RawCRaterResponse } from '../../assets/wise5/components/common/cRater/RawCRaterResponse';
8-
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
8+
import { provideHttpClient } from '@angular/common/http';
99
import { ProjectService } from '../../assets/wise5/services/projectService';
1010
import { ComponentContent } from '../../assets/wise5/common/ComponentContent';
1111
let service: CRaterService;
@@ -24,7 +24,7 @@ describe('CRaterService', () => {
2424
ConfigService,
2525
CRaterService,
2626
{ provide: ProjectService, useClass: MockProjectService },
27-
provideHttpClient(withInterceptorsFromDi()),
27+
provideHttpClient(),
2828
provideHttpClientTesting()
2929
]
3030
});
@@ -182,7 +182,7 @@ function makeCRaterVerifyRequest() {
182182
it('should make a CRater verify request', () => {
183183
spyOn(configService, 'getCRaterRequestURL').and.returnValue('/c-rater');
184184
const itemId = 'ColdBeverage1Sub';
185-
service.makeCRaterVerifyRequest(itemId);
185+
service.makeCRaterVerifyRequest(itemId).subscribe();
186186
http.expectOne({
187187
url: `/c-rater/verify?itemId=${itemId}`,
188188
method: 'GET'
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<mat-form-field>
2+
<mat-label i18n>AI Model ID</mat-label>
3+
<mat-select [(ngModel)]="selectedItemId" (selectionChange)="getItemRubric()">
4+
@for (itemId of itemIds; track itemId) {
5+
<mat-option [value]="itemId">{{ itemId }}</mat-option>
6+
}
7+
</mat-select>
8+
</mat-form-field>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { CRaterItemSelectComponent } from './crater-item-select.component';
3+
import { MockProviders } from 'ng-mocks';
4+
import { CRaterService } from '../../../../services/cRaterService';
5+
import { TeacherProjectService } from '../../../../services/teacherProjectService';
6+
import { HarnessLoader } from '@angular/cdk/testing';
7+
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
8+
import { MatSelectHarness } from '@angular/material/select/testing';
9+
import { Observable } from 'rxjs';
10+
11+
let loader: HarnessLoader;
12+
describe('CRaterItemSelectComponent', () => {
13+
let component: CRaterItemSelectComponent;
14+
let fixture: ComponentFixture<CRaterItemSelectComponent>;
15+
16+
beforeEach(async () => {
17+
await TestBed.configureTestingModule({
18+
imports: [CRaterItemSelectComponent],
19+
providers: [MockProviders(CRaterService, TeacherProjectService)]
20+
}).compileComponents();
21+
22+
fixture = TestBed.createComponent(CRaterItemSelectComponent);
23+
loader = TestbedHarnessEnvironment.loader(fixture);
24+
component = fixture.componentInstance;
25+
component.componentContent = { itemId: 'berkeley_HeLa', type: 'DialogGuidance' };
26+
fixture.detectChanges();
27+
});
28+
29+
it('should show selected item id', async () => {
30+
const select = await loader.getHarness(MatSelectHarness);
31+
expect(await select.getValueText()).toBe('berkeley_HeLa');
32+
});
33+
34+
it('should get rubric and update component when item id is changed', async () => {
35+
const cRaterService = TestBed.inject(CRaterService);
36+
const rubricSpy = spyOn(cRaterService, 'makeCRaterVerifyRequest').and.returnValue(
37+
new Observable<any>((observer) => {
38+
observer.next({
39+
rubric: {
40+
description: 'Test description',
41+
ideas: [
42+
{ name: '1', text: 'Idea 1 text' },
43+
{ name: '2', text: 'Idea 2 text' }
44+
]
45+
}
46+
});
47+
observer.complete();
48+
})
49+
);
50+
const saveSpy = spyOn(TestBed.inject(TeacherProjectService), 'saveProject');
51+
const select = await loader.getHarness(MatSelectHarness);
52+
await select.open();
53+
const options = await select.getOptions();
54+
await options[0].click(); // Select the first item (berkeley_BowlsInAFridge)
55+
expect(rubricSpy).toHaveBeenCalled();
56+
expect(saveSpy).toHaveBeenCalled();
57+
expect(component.componentContent.itemId).toEqual('berkeley_BowlsInAFridge');
58+
expect(component.componentContent.cRaterRubric).toEqual({
59+
description: 'Test description',
60+
ideas: [
61+
{ name: '1', text: 'Idea 1 text' },
62+
{ name: '2', text: 'Idea 2 text' }
63+
]
64+
});
65+
});
66+
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Component, Input } from '@angular/core';
2+
import { MatFormFieldModule } from '@angular/material/form-field';
3+
import { MatOption, MatSelect } from '@angular/material/select';
4+
import { CRaterService } from '../../../../services/cRaterService';
5+
import { TeacherProjectService } from '../../../../services/teacherProjectService';
6+
import { FormsModule } from '@angular/forms';
7+
8+
@Component({
9+
imports: [FormsModule, MatFormFieldModule, MatSelect, MatOption],
10+
selector: 'c-rater-item-select',
11+
templateUrl: './crater-item-select.component.html'
12+
})
13+
export class CRaterItemSelectComponent {
14+
@Input() componentContent: any;
15+
protected itemIds = [
16+
'berkeley_BowlsInAFridge',
17+
'berkeley_CarOnAColdDay',
18+
'berkeley_COAL-II',
19+
'berkeley_CovidImpacts',
20+
'berkeley_FoodJustice',
21+
'berkeley_FoodJusticeShortLabelIdeaKI',
22+
'berkeley_GenEx-Siblings',
23+
'berkeley_HeLa',
24+
'berkeley_Impacts',
25+
'berkeley_Lizards',
26+
'berkeley_MusicalInstruments-Speed',
27+
'berkeley_PhotoEnergyStory',
28+
'berkeley_PlateTectonics_MtHood',
29+
'berkeley_SPOON-II'
30+
];
31+
protected selectedItemId: string;
32+
33+
constructor(
34+
private cRaterService: CRaterService,
35+
private projectService: TeacherProjectService
36+
) {}
37+
38+
ngOnInit(): void {
39+
if (this.componentContent.type === 'OpenResponse') {
40+
this.selectedItemId = this.componentContent.cRater.itemId;
41+
} else if (this.componentContent.type === 'DialogGuidance') {
42+
this.selectedItemId = this.componentContent.itemId;
43+
}
44+
}
45+
46+
protected getItemRubric(): void {
47+
this.cRaterService.makeCRaterVerifyRequest(this.selectedItemId).subscribe((response) => {
48+
const cRaterRubric = response.rubric ?? { description: '', ideas: [] };
49+
if (this.componentContent.type === 'OpenResponse') {
50+
this.componentContent.cRater.rubric = cRaterRubric;
51+
this.componentContent.cRater.itemId = this.selectedItemId;
52+
} else if (this.componentContent.type === 'DialogGuidance') {
53+
this.componentContent.cRaterRubric = cRaterRubric;
54+
this.componentContent.itemId = this.selectedItemId;
55+
}
56+
this.projectService.saveProject();
57+
});
58+
}
59+
}
Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
<div class="description notice-bg-bg">
2-
<h5 class="flex flex-row items-center gap-1 !text-xl">
3-
<span i18n>Model Description</span>
4-
</h5>
5-
<mat-card appearance="outlined" class="description-content">
6-
<mat-form-field class="description-input form-field-no-hint" appearance="fill">
7-
<mat-label i18n>User-Friendly Description</mat-label>
8-
<textarea
9-
matInput
10-
[(ngModel)]="cRaterRubric.description"
11-
(ngModelChange)="inputChanged.next($event)"
12-
cdkTextareaAutosize
13-
></textarea>
14-
</mat-form-field>
15-
</mat-card>
16-
</div>
1+
<mat-form-field class="description-input" appearance="fill">
2+
<mat-label i18n>User-Friendly Description</mat-label>
3+
<textarea
4+
matInput
5+
[(ngModel)]="cRaterRubric.description"
6+
(ngModelChange)="inputChanged.next($event)"
7+
cdkTextareaAutosize
8+
></textarea>
9+
</mat-form-field>

src/assets/wise5/components/common/cRater/edit-crater-description/edit-crater-description.component.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@ import { Component, Input } from '@angular/core';
33
import { CRaterRubric } from '../CRaterRubric';
44
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
55
import { FormsModule } from '@angular/forms';
6-
import { MatCardModule } from '@angular/material/card';
76
import { MatFormFieldModule } from '@angular/material/form-field';
87
import { MatInputModule } from '@angular/material/input';
98
import { Subject, Subscription } from 'rxjs';
109
import { TeacherProjectService } from '../../../../services/teacherProjectService';
1110

1211
@Component({
1312
selector: 'edit-crater-description',
14-
imports: [CdkTextareaAutosize, FormsModule, MatCardModule, MatFormFieldModule, MatInputModule],
13+
imports: [CdkTextareaAutosize, FormsModule, MatFormFieldModule, MatInputModule],
1514
templateUrl: './edit-crater-description.component.html',
1615
styleUrl: './edit-crater-description.component.scss'
1716
})

src/assets/wise5/components/common/cRater/edit-crater-idea-descriptions/edit-crater-idea-descriptions.component.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ h5 {
1212
ul {
1313
margin: 16px 0 0 0;
1414
padding: 0 0 16px;
15+
max-height: 60vh;
16+
overflow: auto;
1517
}
1618

1719
li {

src/assets/wise5/components/common/cRater/edit-crater-info/edit-crater-info.component.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,13 @@ import { Component, Input } from '@angular/core';
22
import { CRaterRubric } from '../CRaterRubric';
33
import { EditCRaterDescriptionComponent } from '../edit-crater-description/edit-crater-description.component';
44
import { EditCRaterIdeaDescriptionsComponent } from '../edit-crater-idea-descriptions/edit-crater-idea-descriptions.component';
5-
import { MatCardModule } from '@angular/material/card';
65

76
@Component({
87
selector: 'edit-crater-info',
9-
imports: [EditCRaterDescriptionComponent, EditCRaterIdeaDescriptionsComponent, MatCardModule],
10-
styles: ['.wrapper { padding: 16px; }'],
11-
template: `<mat-card appearance="outlined" class="wrapper">
12-
<h5 class="gap-1 !text-xl">
13-
<span i18n>Edit CRater Information</span>
14-
</h5>
8+
imports: [EditCRaterDescriptionComponent, EditCRaterIdeaDescriptionsComponent],
9+
template: `<h5 class="!text-xl" i18n>AI Model Details</h5>
1510
<edit-crater-description [cRaterRubric]="cRaterRubric" />
16-
<edit-crater-idea-descriptions [ideaDescriptions]="cRaterRubric.ideas" />
17-
</mat-card>`
11+
<edit-crater-idea-descriptions [ideaDescriptions]="cRaterRubric.ideas" />`
1812
})
1913
export class EditCRaterInfoComponent {
2014
@Input() cRaterRubric: CRaterRubric;

src/assets/wise5/components/dialogGuidance/dialog-guidance-authoring/dialog-guidance-authoring.component.html

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,7 @@
33
[componentContent]="componentContent"
44
(promptChangedEvent)="promptChanged($event)"
55
/>
6-
<mat-form-field>
7-
<mat-label i18n>Item ID</mat-label>
8-
<input
9-
matInput
10-
[(ngModel)]="componentContent.itemId"
11-
(ngModelChange)="inputChange.next($event)"
12-
type="text"
13-
/>
14-
</mat-form-field>
6+
<c-rater-item-select [componentContent]="componentContent" />
157
<edit-component-max-submit [componentContent]="componentContent" />
168
<mat-checkbox
179
color="primary"
@@ -29,4 +21,5 @@
2921
[feedbackRules]="componentContent.feedbackRules"
3022
[version]="componentContent.version"
3123
/>
24+
<edit-crater-info [cRaterRubric]="componentContent.cRaterRubric" />
3225
</div>

src/assets/wise5/components/dialogGuidance/dialog-guidance-authoring/dialog-guidance-authoring.component.spec.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
1+
import { provideHttpClient } from '@angular/common/http';
22
import { ComponentFixture, TestBed } from '@angular/core/testing';
33
import { ProjectLocale } from '../../../../../app/domain/projectLocale';
44
import { ProjectAssetService } from '../../../../../app/services/projectAssetService';
@@ -8,6 +8,8 @@ import { TeacherNodeService } from '../../../services/teacherNodeService';
88
import { TeacherProjectService } from '../../../services/teacherProjectService';
99
import { TeacherProjectTranslationService } from '../../../services/teacherProjectTranslationService';
1010
import { DialogGuidanceAuthoringComponent } from './dialog-guidance-authoring.component';
11+
import { MockComponent } from 'ng-mocks';
12+
import { EditComponentPrompt } from '../../../../../app/authoring-tool/edit-component-prompt/edit-component-prompt.component';
1113

1214
const componentContent = {
1315
id: 'i64ex48j1z',
@@ -18,31 +20,31 @@ const componentContent = {
1820
showSubmitButton: false,
1921
isComputerAvatarEnabled: false
2022
};
21-
2223
describe('DialogGuidanceAuthoringComponent', () => {
2324
let component: DialogGuidanceAuthoringComponent;
2425
let fixture: ComponentFixture<DialogGuidanceAuthoringComponent>;
2526

2627
beforeEach(() => {
2728
TestBed.configureTestingModule({
28-
imports: [DialogGuidanceAuthoringComponent, StudentTeacherCommonServicesModule],
29+
imports: [
30+
DialogGuidanceAuthoringComponent,
31+
MockComponent(EditComponentPrompt),
32+
StudentTeacherCommonServicesModule
33+
],
2934
providers: [
3035
ProjectAssetService,
3136
TeacherNodeService,
3237
TeacherProjectService,
3338
TeacherProjectTranslationService,
34-
provideHttpClient(withInterceptorsFromDi())
39+
provideHttpClient()
3540
]
3641
});
37-
spyOn(TestBed.inject(TeacherProjectService), 'getLocale').and.returnValue(
38-
new ProjectLocale({ default: 'en-US' })
39-
);
4042
fixture = TestBed.createComponent(DialogGuidanceAuthoringComponent);
4143
component = fixture.componentInstance;
42-
spyOn(TestBed.inject(TeacherProjectService), 'isDefaultLocale').and.returnValue(true);
43-
spyOn(TestBed.inject(TeacherProjectService), 'getComponent').and.returnValue(
44-
copy(componentContent)
45-
);
44+
const projectService = TestBed.inject(TeacherProjectService);
45+
spyOn(projectService, 'getLocale').and.returnValue(new ProjectLocale({ default: 'en-US' }));
46+
spyOn(projectService, 'isDefaultLocale').and.returnValue(true);
47+
spyOn(projectService, 'getComponent').and.returnValue(copy(componentContent));
4648
component.componentContent = copy(componentContent);
4749
fixture.detectChanges();
4850
});

0 commit comments

Comments
 (0)