Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@if (hasRubricData) {
<span class="flex items-center">
<mat-icon class="mat-18" color="primary">auto_awesome</mat-icon>&nbsp;<a
tabindex="0"
(click)="openIdeasRubric()"
(keyup.enter)="openIdeasRubric()"
i18n
>Detectable ideas</a
>
</span>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Component, Input } from '@angular/core';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { CRaterRubric } from '../../../assets/wise5/components/common/cRater/CRaterRubric';
import { CRaterRubricComponent } from '../../../assets/wise5/components/common/cRater/crater-rubric/crater-rubric.component';
import { RubricEventService } from '../../../assets/wise5/components/common/cRater/crater-rubric/RubricEventService';

@Component({
imports: [MatIconModule],
selector: 'show-crater-rubric',
templateUrl: './show-crater-rubric.component.html'
})
export class ShowCRaterRubricComponent {
@Input() cRaterRubric: CRaterRubric;
protected hasRubricData: boolean;
private rubricDialog: MatDialogRef<CRaterRubricComponent>;

constructor(
protected dialog: MatDialog,
protected rubricEventService: RubricEventService
) {}

ngOnInit(): void {
this.hasRubricData = this.cRaterRubric?.hasRubricData() ?? false;
}

ngOnDestroy(): void {
if (this.rubricEventService.getIsRubricOpen()) {
this.rubricDialog.close();
}
}

protected openIdeasRubric(): void {
if (!this.rubricEventService.getIsRubricOpen()) {
this.rubricDialog = this.dialog.open(CRaterRubricComponent, {
panelClass: 'dialog-sm',
position: { right: '0', bottom: '0' },
hasBackdrop: false,
data: this.cRaterRubric,
autoFocus: false
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@if (ideas.length > 0) {
<div class="flex flex-row-reverse gap-1 items-start">
<div class="flex gap-1 items-start" [ngClass]="{ 'flex-row-reverse': alignEnd }">
<div class="avatar highlighted-bg border border-gray-300">
<mat-icon class="mat-18" aria-label="AI analysis" aria-label-i18n>auto_awesome</mat-icon>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CRaterRubric, getUniqueIdeas } from '../../common/cRater/CRaterRubric';
import { CRaterIdea } from '../../common/cRater/CRaterIdea';
import { MatIconModule } from '@angular/material/icon';

@Component({
imports: [MatIconModule],
imports: [CommonModule, MatIconModule],
selector: 'detected-ideas',
styleUrl: './detected-ideas.component.scss',
templateUrl: './detected-ideas.component.html'
})
export class DetectedIdeasComponent {
@Input() alignEnd: boolean = false;
@Input() cRaterRubric: CRaterRubric;
protected ideas: CRaterIdea[];
@Input() responses: any;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
@if (showDetectedIdeas && hasRubricData) {
<div class="flex items-center justify-end">
<mat-icon class="mat-18" color="primary">auto_awesome</mat-icon>&nbsp;<a
tabindex="0"
(click)="openIdeasRubric()"
(keyup.enter)="openIdeasRubric()"
i18n
>Detectable ideas</a
>
@if (showDetectedIdeas) {
<div class="flex justify-end">
<show-crater-rubric [cRaterRubric]="cRaterRubric" />
</div>
}
@for (response of responses; track response.timestamp) {
@if (response.user === 'Computer' && showDetectedIdeas && cRaterRubric.ideas.length) {
<detected-ideas [cRaterRubric]="cRaterRubric" [responses]="[response]" />
<detected-ideas [cRaterRubric]="cRaterRubric" [responses]="[response]" [alignEnd]="true" />
}
<dialog-response [response]="response" [computerAvatar]="computerAvatar" />
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,53 +1,27 @@
import { Component, Input } from '@angular/core';
import { ComputerAvatar } from '../../../common/computer-avatar/ComputerAvatar';
import { CRaterRubric } from '../../common/cRater/CRaterRubric';
import { CRaterRubricComponent } from '../../common/cRater/crater-rubric/crater-rubric.component';
import { DetectedIdeasComponent } from '../detected-ideas/detected-ideas.component';
import { DialogResponse } from '../DialogResponse';
import { DialogResponseComponent } from '../dialog-response/dialog-response.component';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { MatIconModule } from '@angular/material/icon';
import { RubricEventService } from '../../common/cRater/crater-rubric/RubricEventService';
import { ShowCRaterRubricComponent } from '../../../../../app/classroom-monitor/show-crater-rubric/show-crater-rubric.component';

@Component({
imports: [DetectedIdeasComponent, DialogResponseComponent, MatIconModule],
imports: [
DetectedIdeasComponent,
DialogResponseComponent,
MatIconModule,
ShowCRaterRubricComponent
],
selector: 'dialog-responses',
styleUrl: './dialog-responses.component.scss',
templateUrl: './dialog-responses.component.html'
})
export class DialogResponsesComponent {
@Input() computerAvatar: ComputerAvatar;
@Input() cRaterRubric: CRaterRubric;
protected hasRubricData: boolean;
@Input() isWaitingForComputerResponse: boolean;
@Input() responses: DialogResponse[] = [];
private rubricDialog: MatDialogRef<CRaterRubricComponent>;
@Input() showDetectedIdeas: boolean = false;

constructor(
protected dialog: MatDialog,
protected rubricEventService: RubricEventService
) {}

ngOnInit(): void {
this.hasRubricData = this.cRaterRubric?.hasRubricData() ?? false;
}

ngOnDestroy(): void {
if (this.rubricEventService.getIsRubricOpen()) {
this.rubricDialog.close();
}
}

protected openIdeasRubric(): void {
if (!this.rubricEventService.getIsRubricOpen()) {
this.rubricDialog = this.dialog.open(CRaterRubricComponent, {
panelClass: 'dialog-sm',
position: { right: '0', bottom: '0' },
hasBackdrop: false,
data: this.cRaterRubric,
autoFocus: false
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
@if (showDetectedIdeas) {
<div class="flex justify-end mb-4">
<show-crater-rubric [cRaterRubric]="cRaterRubric" />
</div>
}
<div class="component--grading__response__content" [innerHTML]="studentResponse"></div>
@for (audioAttachment of audioAttachments; track audioAttachment) {
<div class="component__attachment flex items-center justify-start">
Expand All @@ -11,3 +16,10 @@
<img [src]="otherAttachment.iconURL" class="component__attachment__content" />
</div>
}
@if (showDetectedIdeas) {
<detected-ideas
[cRaterRubric]="cRaterRubric"
[responses]="[cRaterAnnotation.data]"
class="block mt-4"
/>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { StudentTeacherCommonServicesModule } from '../../../../../app/student-teacher-common-services.module';
import { OpenResponseShowWorkComponent } from './open-response-show-work.component';
import { AnnotationService } from '../../../services/annotationService';
import { UserService } from '../../../../../app/services/user.service';
import { ProjectService } from '../../../services/projectService';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';

describe('OpenResponseShowWorkComponent', () => {
let component: OpenResponseShowWorkComponent;
let fixture: ComponentFixture<OpenResponseShowWorkComponent>;
let annotationService: AnnotationService;
let userService: UserService;
let projectService: ProjectService;
const mockComponentState = {
id: 1,
studentData: {
response: 'Student answer',
attachments: [
{ type: 'audio', url: 'audio1.mp3' },
{ type: 'audio', url: 'audio2.mp3' },
{ type: 'image', url: 'image1.png' },
{ type: 'file', url: 'document.pdf' }
]
}
};
const mockCRaterAnnotation = {
id: 1,
type: 'autoScore',
data: {
ideas: [{ name: 'idea1', detected: true }]
},
studentWorkId: 1
};

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [OpenResponseShowWorkComponent, StudentTeacherCommonServicesModule],
providers: [
{ provide: UserService, useValue: { isTeacher() {} } },
provideHttpClient(withInterceptorsFromDi())
]
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(OpenResponseShowWorkComponent);
component = fixture.componentInstance;
annotationService = TestBed.inject(AnnotationService);
userService = TestBed.inject(UserService);
projectService = TestBed.inject(ProjectService);
component.componentState = mockComponentState;
component.nodeId = 'node1';
component.componentId = 'component1';
spyOn(projectService, 'getComponent').and.returnValue({
id: 'component1',
type: 'OpenResponse',
cRater: {
rubric: {
ideas: [{ name: 'idea1' }]
}
}
} as any);
spyOn(projectService, 'injectAssetPaths').and.callFake((arg) => arg);
});

it('should create', () => {
fixture.detectChanges();
expect(component).toBeTruthy();
});

describe('ngOnInit', () => {
it('should initialize student response and process attachments', () => {
spyOn(annotationService, 'getAnnotationsByStudentWorkId').and.returnValue([]);
fixture.detectChanges();
expect(component['studentResponse']).toBe('Student answer');
expect(component['audioAttachments'].length).toBe(2);
expect(component['otherAttachments'].length).toBe(2);
});

it('should set up CRater rubric and annotations when CRater is configured', () => {
spyOn(annotationService, 'getAnnotationsByStudentWorkId').and.returnValue([
mockCRaterAnnotation
] as any);
spyOn(userService, 'isTeacher').and.returnValue(true);
fixture.detectChanges();
expect(annotationService.getAnnotationsByStudentWorkId).toHaveBeenCalledWith(1);
});

it('should show detected ideas when user is teacher and CRater annotation exists', () => {
spyOn(annotationService, 'getAnnotationsByStudentWorkId').and.returnValue([
mockCRaterAnnotation
] as any);
spyOn(userService, 'isTeacher').and.returnValue(true);
fixture.detectChanges();
expect(component['showDetectedIdeas']).toBe(true);
});

it('should not show detected ideas when user is not a teacher', () => {
spyOn(annotationService, 'getAnnotationsByStudentWorkId').and.returnValue([]);
spyOn(userService, 'isTeacher').and.returnValue(false);
fixture.detectChanges();
expect(component['showDetectedIdeas']).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
import { Component } from '@angular/core';
import { Component, Input } from '@angular/core';
import { ComponentShowWorkDirective } from '../../component-show-work.directive';
import { CommonModule } from '@angular/common';
import { CRaterRubric } from '../../common/cRater/CRaterRubric';
import { DetectedIdeasComponent } from '../../dialogGuidance/detected-ideas/detected-ideas.component';
import { NodeService } from '../../../services/nodeService';
import { ProjectService } from '../../../services/projectService';
import { AnnotationService } from '../../../services/annotationService';
import { Annotation } from '../../../common/Annotation';
import { ShowCRaterRubricComponent } from '../../../../../app/classroom-monitor/show-crater-rubric/show-crater-rubric.component';
import { UserService } from '../../../../../app/services/user.service';

@Component({
imports: [CommonModule],
imports: [CommonModule, DetectedIdeasComponent, ShowCRaterRubricComponent],
selector: 'open-response-show-work',
templateUrl: 'open-response-show-work.component.html'
})
export class OpenResponseShowWorkComponent extends ComponentShowWorkDirective {
private annotations: Annotation[] = [];
protected audioAttachments: any[] = [];
private cRaterAnnotation: Annotation;
protected cRaterRubric: CRaterRubric;
protected otherAttachments: any[] = [];
protected showDetectedIdeas: boolean = false;
protected studentResponse: string = '';

constructor(
private annotationService: AnnotationService,
nodeService: NodeService,
projectService: ProjectService,
protected userService: UserService
) {
super(nodeService, projectService);
}

ngOnInit(): void {
if (this.componentState != null && this.componentState !== '') {
this.studentResponse = this.componentState.studentData.response;
this.componentContent = this.projectService.getComponent(this.nodeId, this.componentId);
if (this.componentContent.cRater) {
this.cRaterRubric = new CRaterRubric(this.componentContent.cRater.rubric);
this.annotations = this.annotationService.getAnnotationsByStudentWorkId(
this.componentState.id
);
this.cRaterAnnotation = this.annotations.find((annotation) => annotation.type === 'autoScore' && annotation.data.ideas);
}
this.showDetectedIdeas =
this.userService.isTeacher() &&
this.cRaterRubric?.ideas.length &&
this.cRaterAnnotation != null;
this.processAttachments();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ProjectService } from '../../../services/projectService';
import { MockComponent, MockProviders } from 'ng-mocks';
import { OpenResponseShowWorkComponent } from '../../openResponse/open-response-show-work/open-response-show-work.component';
import { NodeService } from '../../../services/nodeService';
import { AnnotationService } from '../../../services/annotationService';
import { UserService } from '../../../../../app/services/user.service';

let component: ShowMyWorkGradingComponent;
const componentId: string = 'component1';
Expand All @@ -17,7 +19,15 @@ describe('ShowMyWorkGradingComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ShowMyWorkGradingComponent, MockComponent(OpenResponseShowWorkComponent)],
providers: [MockProviders(TeacherDataService, ProjectService, NodeService)]
providers: [
MockProviders(
AnnotationService,
NodeService,
ProjectService,
TeacherDataService,
UserService
)
]
});
projectService = TestBed.inject(ProjectService);
spyOn(projectService, 'getComponent').and.returnValue({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { MockComponent, MockProviders } from 'ng-mocks';
import { OpenResponseShowWorkComponent } from '../../openResponse/open-response-show-work/open-response-show-work.component';
import { NodeService } from '../../../services/nodeService';
import { ProjectService } from '../../../services/projectService';
import { AnnotationService } from '../../../services/annotationService';
import { ConfigService } from '../../../../../app/services/config.service';
import { UserService } from '../../../../../app/services/user.service';

describe('ShowWorkStudentComponent', () => {
let component: ShowWorkStudentComponent;
Expand All @@ -12,7 +15,9 @@ describe('ShowWorkStudentComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [MockComponent(OpenResponseShowWorkComponent), ShowWorkStudentComponent],
providers: [MockProviders(NodeService, ProjectService)]
providers: [
MockProviders(AnnotationService, ConfigService, NodeService, ProjectService, UserService)
]
}).compileComponents();
});

Expand Down
Loading
Loading