Skip to content

Commit

Permalink
feat(Dialog Guidance): Multiple feedback authoring (#582)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonathan Lim-Breitbart <breity10@gmail.com>
  • Loading branch information
geoffreykwan and breity authored May 16, 2022
1 parent 2536706 commit 9978839
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 30 deletions.
8 changes: 8 additions & 0 deletions src/assets/wise5/components/dialogGuidance/FeedbackRule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ export class FeedbackRule {
feedback: string | string[];
static operatorPrecedences = { '!': 2, '&&': 1, '||': 1 };

constructor(jsonObject: any = {}) {
for (const key of Object.keys(jsonObject)) {
if (jsonObject[key] != null) {
this[key] = jsonObject[key];
}
}
}

static isSecondToLastSubmitRule(feedbackRule: FeedbackRule): boolean {
return feedbackRule.expression === 'isSecondToLastSubmit';
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
<edit-dialog-guidance-computer-avatar *ngIf="authoringComponentContent.isComputerAvatarEnabled"
[computerAvatarSettings]="authoringComponentContent.computerAvatarSettings">
</edit-dialog-guidance-computer-avatar>
<edit-dialog-guidance-feedback-rules [feedbackRules]="authoringComponentContent.feedbackRules">
<edit-dialog-guidance-feedback-rules
[feedbackRules]="authoringComponentContent.feedbackRules"
[version]="authoringComponentContent.version">
</edit-dialog-guidance-feedback-rules>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class DialogGuidanceService extends ComponentService {
component.feedbackRules = [];
component.isComputerAvatarEnabled = false;
component.computerAvatarSettings = this.getDefaultComputerAvatarSettings();
component.version = 2;
return component;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,60 @@ <h5 fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="4px">
<span class="mat-subheading-2">{{ruleIndex + 1}}</span>
<mat-icon cdkDragHandle title="Drag to reorder" i18n-title>drag_indicator</mat-icon>
</div>
<div fxLayout="column" fxLayoutAlign="center center" fxLayoutGap="8px" fxFlex>
<div fxLayout="column" fxLayoutAlign="center start" fxLayoutGap="8px" fxFlex>
<mat-form-field class="rule-input form-field-no-hint" appearance="fill">
<mat-label i18n>Expression</mat-label>
<input matInput [(ngModel)]="rule.expression" (ngModelChange)='inputChanged.next($event)' />
</mat-form-field>
<mat-form-field class="rule-input form-field-no-hint" appearance="fill">
<mat-label i18n>Feedback</mat-label>
<textarea matInput
[(ngModel)]="rule.feedback"
(ngModelChange)='inputChanged.next($event)'
i18n-placeholder
cdkTextareaAutosize>
<ng-container [ngSwitch]="version">
<ng-container *ngSwitchCase="2">
<div *ngFor="let feedback of rule.feedback; let feedbackIndex = index; let last = last;
trackBy: customTrackBy"
class="feedback-item"
fxLayout="row"
fxLayoutAlign="start center"
fxLayoutGap="8px">
<mat-form-field class="rule-input form-field-no-hint feedback-input"
appearance="fill">
<mat-label *ngIf="rule.feedback.length === 1" i18n>Feedback</mat-label>
<mat-label *ngIf="rule.feedback.length > 1" i18n>Feedback #{{feedbackIndex + 1}}</mat-label>
<textarea matInput
[(ngModel)]="rule.feedback[feedbackIndex]"
(ngModelChange)='inputChanged.next($event)'
cdkTextareaAutosize>
</textarea>
</mat-form-field>
<button mat-icon-button
matSuffix
*ngIf="rule.feedback.length > 1"
matTooltip="Delete feedback"
i18n-matTooltip
matTooltipPosition="before"
(click)="deleteFeedbackInRule(rule, feedbackIndex)">
<mat-icon>clear</mat-icon>
</button>
</mat-form-field>
<button *ngIf="last"
mat-icon-button
matTooltip="Add new feedback"
matTooltipPosition="above"
i18n-matTooltip
(click)="addNewFeedbackToRule(rule)"
i18n>
<mat-icon>add_circle</mat-icon>
</button>
</div>
</ng-container>
<ng-container *ngSwitchDefault>
<mat-form-field class="rule-input form-field-no-hint" appearance="fill">
<mat-label i18n>Feedback</mat-label>
<textarea matInput
[(ngModel)]="rule.feedback"
(ngModelChange)='inputChanged.next($event)'
cdkTextareaAutosize>
</textarea>
</mat-form-field>
</ng-container>
</ng-container>
</div>
<div fxLayout="column" fxLayoutAlign="start center">
<button mat-icon-button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,11 @@ li {
.mat-subheading-2 {
margin: 0;
}

.feedback-item {
width: 100%;
}

.feedback-input {
width: 100%;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,38 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { EditDialogGuidanceFeedbackRulesComponent } from './edit-dialog-guidance-feedback-rules.component';
import { TeacherProjectService } from '../../../services/teacherProjectService';
import { UtilService } from '../../../services/utilService';
import { UpgradeModule } from '@angular/upgrade/static';
import { FeedbackRule } from '../FeedbackRule';

class MockTeacherProjectService {
nodeChanged() {}
}

let component: EditDialogGuidanceFeedbackRulesComponent;
let nodeChangedSpy;
const feedbackString1: string = 'you hit idea1';
const feedbackString2: string = 'you hit idea2';
let nodeChangedSpy: jasmine.Spy;

describe('EditDialogGuidanceFeedbackRulesComponent', () => {
let fixture: ComponentFixture<EditDialogGuidanceFeedbackRulesComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [UpgradeModule],
declarations: [EditDialogGuidanceFeedbackRulesComponent],
providers: [{ provide: TeacherProjectService, useClass: MockTeacherProjectService }],
providers: [
{ provide: TeacherProjectService, useClass: MockTeacherProjectService },
UtilService
],
schemas: [NO_ERRORS_SCHEMA]
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(EditDialogGuidanceFeedbackRulesComponent);
component = fixture.componentInstance;
component.version = 2;
component.feedbackRules = [
{ expression: 'idea1', feedback: 'you hit idea1' },
{ expression: 'idea2', feedback: 'you hit idea2' }
new FeedbackRule({ id: '1111111111', expression: 'idea1', feedback: [feedbackString1] }),
new FeedbackRule({ id: '2222222222', expression: 'idea2', feedback: [feedbackString2] })
];
nodeChangedSpy = spyOn(TestBed.inject(TeacherProjectService), 'nodeChanged');
fixture.detectChanges();
Expand All @@ -35,6 +43,8 @@ describe('EditDialogGuidanceFeedbackRulesComponent', () => {
deleteRule();
moveUp();
moveDown();
addNewFeedbackToRule();
deleteFeedbackInRule();
});

function addNewRule() {
Expand All @@ -50,12 +60,36 @@ function addNewRule() {
expectFeedbackExpressions(['idea1', 'idea2', '']);
expect(nodeChangedSpy).toHaveBeenCalled();
});

it('should create new rule with feedback version 1', () => {
component.version = 1;
component.feedbackRules = [];
component.addNewRule('beginning');
expect(component.feedbackRules.length).toEqual(1);
const feedbackRule = component.feedbackRules[0];
expect(feedbackRule.expression).toEqual('');
expect(feedbackRule.feedback).toEqual('');
expect(nodeChangedSpy).toHaveBeenCalled();
});

it('should create new rule with feedback version 2', () => {
component.version = 2;
component.feedbackRules = [];
component.addNewRule('beginning');
expect(component.feedbackRules.length).toEqual(1);
const feedbackRule = component.feedbackRules[0];
expect(typeof feedbackRule.id).toEqual('string');
expect(feedbackRule.expression).toEqual('');
expect(feedbackRule.feedback).toEqual(['']);
expect(nodeChangedSpy).toHaveBeenCalled();
});
});
}

function deleteRule() {
describe('deleteRule()', () => {
it('should delete rule at specified index', () => {
spyOn(window, 'confirm').and.returnValue(true);
component.deleteRule(0);
expectFeedbackExpressions(['idea2']);
expect(nodeChangedSpy).toHaveBeenCalled();
Expand Down Expand Up @@ -89,3 +123,27 @@ function expectFeedbackExpressions(expectedExpressionsInOrder: string[]): void {
expect(component.feedbackRules[i].expression).toEqual(expectedExpressionsInOrder[i]);
}
}

function addNewFeedbackToRule() {
describe('addNewFeedbackToRule', () => {
it('should add new feedback to rule', () => {
component.addNewFeedbackToRule(component.feedbackRules[0]);
expect(component.feedbackRules[0].feedback).toEqual([feedbackString1, '']);
});
});
}

function deleteFeedbackInRule() {
describe('deleteFeedbackInRule', () => {
it('should delete feedback in rule', () => {
spyOn(window, 'confirm').and.returnValue(true);
const feedbackRule = new FeedbackRule({
expression: '',
feedback: ['Hello', 'World'],
id: '1234567890'
});
component.deleteFeedbackInRule(feedbackRule, 1);
expect(feedbackRule.feedback).toEqual(['Hello']);
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { TeacherProjectService } from '../../../services/teacherProjectService';
import { UtilService } from '../../../services/utilService';
import { FeedbackRule } from '../FeedbackRule';

@Component({
selector: 'edit-dialog-guidance-feedback-rules',
templateUrl: './edit-dialog-guidance-feedback-rules.component.html',
styleUrls: ['./edit-dialog-guidance-feedback-rules.component.scss']
})
export class EditDialogGuidanceFeedbackRulesComponent implements OnInit {
@Input() feedbackRules: any = [];
@Input() feedbackRules: FeedbackRule[] = [];
inputChanged: Subject<string> = new Subject<string>();
subscriptions: Subscription = new Subscription();
@Input() version: number;

constructor(private ProjectService: TeacherProjectService) {}
constructor(private ProjectService: TeacherProjectService, private utilService: UtilService) {}

ngOnInit(): void {
this.subscriptions.add(
Expand Down Expand Up @@ -46,7 +49,7 @@ export class EditDialogGuidanceFeedbackRulesComponent implements OnInit {
}

addNewRule(position: string): void {
const newFeedbackRule = { expression: '', feedback: '' };
const newFeedbackRule = this.createNewFeedbackRule();
if (position === 'beginning') {
this.feedbackRules.unshift(newFeedbackRule);
} else {
Expand All @@ -55,8 +58,34 @@ export class EditDialogGuidanceFeedbackRulesComponent implements OnInit {
this.ProjectService.nodeChanged();
}

deleteRule(ruleIndex: number): void {
this.feedbackRules.splice(ruleIndex, 1);
private createNewFeedbackRule(): any {
if (this.version === 1) {
return { expression: '', feedback: '' };
} else {
return { id: this.utilService.generateKey(10), expression: '', feedback: [''] };
}
}

addNewFeedbackToRule(rule: FeedbackRule): void {
(rule.feedback as string[]).push('');
this.ProjectService.nodeChanged();
}

deleteFeedbackInRule(rule: FeedbackRule, feedbackIndex: number): void {
if (confirm($localize`Are you sure you want to delete this feedback?`)) {
(rule.feedback as string[]).splice(feedbackIndex, 1);
this.ProjectService.nodeChanged();
}
}

deleteRule(ruleIndex: number): void {
if (confirm($localize`Are you sure you want to delete this feedback rule?`)) {
this.feedbackRules.splice(ruleIndex, 1);
this.ProjectService.nodeChanged();
}
}

customTrackBy(index: number): number {
return index;
}
}
Loading

0 comments on commit 9978839

Please sign in to comment.