From c6c1d643369a4239054800197f3ef108ab088c2c Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Fri, 15 Sep 2023 15:13:39 -0700 Subject: [PATCH] feat(Feedback Rule): New myChoiceChosen token (#1410) --- .../edit-dynamic-prompt.component.ts | 2 +- .../edit-question-bank.component.ts | 2 +- .../services/studentPeerGroupService.spec.ts | 2 +- .../common/cRater/CRaterResponse.ts | 11 +- .../common/feedbackRule/FeedbackRule.ts | 2 +- .../FeedbackRuleEvaluator.spec.ts | 13 +- .../feedbackRule/FeedbackRuleEvaluator.ts | 24 ++- ...dbackRuleEvaluatorMultipleStudents.spec.ts | 11 +- .../FeedbackRuleEvaluatorMultipleStudents.ts | 19 ++- .../feedbackRule/FeedbackRuleExpression.ts | 2 +- .../common/feedbackRule/Response.ts | 14 ++ .../MyChoiceChosenTermEvaluator.spec.ts | 38 +++++ .../MyChoiceChosenTermEvaluator.ts | 22 +++ .../TermEvaluator/TermEvaluator.ts | 21 ++- .../TermEvaluatorFactory.spec.ts | 7 +- .../TermEvaluator/TermEvaluatorFactory.ts | 7 + .../dialog-guidance-student.component.ts | 5 +- .../open-response-student.component.ts | 5 +- .../peer-chat-question-bank.component.ts | 32 ++-- .../dynamic-prompt/DynamicPromptEvaluator.ts | 72 +++++++++ .../DynamicPromptMultipleChoiceEvaluator.ts | 32 ++++ .../DynamicPromptOpenResponseEvaluator.ts | 44 ++++++ .../dynamic-prompt.component.spec.ts | 12 +- .../dynamic-prompt.component.ts | 139 ++++-------------- .../wise5/services/constraintService.ts | 2 +- src/assets/wise5/services/projectService.ts | 13 +- .../wise5/services/studentPeerGroupService.ts | 6 +- src/messages.xlf | 52 +++---- 28 files changed, 406 insertions(+), 205 deletions(-) create mode 100644 src/assets/wise5/components/common/feedbackRule/Response.ts create mode 100644 src/assets/wise5/components/common/feedbackRule/TermEvaluator/MyChoiceChosenTermEvaluator.spec.ts create mode 100644 src/assets/wise5/components/common/feedbackRule/TermEvaluator/MyChoiceChosenTermEvaluator.ts create mode 100644 src/assets/wise5/directives/dynamic-prompt/DynamicPromptEvaluator.ts create mode 100644 src/assets/wise5/directives/dynamic-prompt/DynamicPromptMultipleChoiceEvaluator.ts create mode 100644 src/assets/wise5/directives/dynamic-prompt/DynamicPromptOpenResponseEvaluator.ts diff --git a/src/app/authoring-tool/edit-dynamic-prompt/edit-dynamic-prompt.component.ts b/src/app/authoring-tool/edit-dynamic-prompt/edit-dynamic-prompt.component.ts index 74659dca34c..5da42e9917c 100644 --- a/src/app/authoring-tool/edit-dynamic-prompt/edit-dynamic-prompt.component.ts +++ b/src/app/authoring-tool/edit-dynamic-prompt/edit-dynamic-prompt.component.ts @@ -8,7 +8,7 @@ import { DynamicPrompt } from '../../../assets/wise5/directives/dynamic-prompt/D styleUrls: ['./edit-dynamic-prompt.component.scss'] }) export class EditDynamicPromptComponent implements OnInit { - allowedReferenceComponentTypes: string[] = ['OpenResponse']; + protected allowedReferenceComponentTypes: string[] = ['MultipleChoice', 'OpenResponse']; @Input() componentContent: any; @Output() dynamicPromptChangedEvent = new EventEmitter(); diff --git a/src/app/authoring-tool/edit-question-bank/edit-question-bank.component.ts b/src/app/authoring-tool/edit-question-bank/edit-question-bank.component.ts index cb2f423afce..c4e237d8637 100644 --- a/src/app/authoring-tool/edit-question-bank/edit-question-bank.component.ts +++ b/src/app/authoring-tool/edit-question-bank/edit-question-bank.component.ts @@ -9,7 +9,7 @@ import { TeacherProjectService } from '../../../assets/wise5/services/teacherPro styleUrls: ['./edit-question-bank.component.scss'] }) export class EditQuestionBankComponent implements OnInit { - allowedReferenceComponentTypes: string[] = ['OpenResponse']; + allowedReferenceComponentTypes: string[] = ['MultipleChoice', 'OpenResponse']; @Input() componentContent: any; constructor(private projectService: TeacherProjectService) {} diff --git a/src/app/services/studentPeerGroupService.spec.ts b/src/app/services/studentPeerGroupService.spec.ts index a2a6a8f3acb..523fe55b0cf 100644 --- a/src/app/services/studentPeerGroupService.spec.ts +++ b/src/app/services/studentPeerGroupService.spec.ts @@ -137,7 +137,7 @@ function retrieveQuestionBankStudentData() { describe('retrieveQuestionBankStudentData()', () => { it('should retrieve question bank student data in preview', () => { setIsPreview(true); - spyOn(projectService, 'getReferenceComponent').and.returnValue({ + spyOn(projectService, 'getReferenceComponentForField').and.returnValue({ nodeId: nodeId1, componentId: componentId1 }); diff --git a/src/assets/wise5/components/common/cRater/CRaterResponse.ts b/src/assets/wise5/components/common/cRater/CRaterResponse.ts index d026da913a5..49b0084b9ba 100644 --- a/src/assets/wise5/components/common/cRater/CRaterResponse.ts +++ b/src/assets/wise5/components/common/cRater/CRaterResponse.ts @@ -1,18 +1,15 @@ +import { Response } from '../feedbackRule/Response'; import { CRaterIdea } from './CRaterIdea'; import { CRaterScore } from './CRaterScore'; -export class CRaterResponse { +export class CRaterResponse extends Response { ideas: CRaterIdea[] = []; score: number; scores: CRaterScore[]; - submitCounter: number; constructor(jsonObject: any = {}) { - for (const key of Object.keys(jsonObject)) { - if (jsonObject[key] != null) { - this[key] = jsonObject[key]; - } - } + super(jsonObject); + this.populateFields(jsonObject); } getDetectedIdeaCount(): number { diff --git a/src/assets/wise5/components/common/feedbackRule/FeedbackRule.ts b/src/assets/wise5/components/common/feedbackRule/FeedbackRule.ts index 00c5cae1b61..cfc2dcb9ffc 100644 --- a/src/assets/wise5/components/common/feedbackRule/FeedbackRule.ts +++ b/src/assets/wise5/components/common/feedbackRule/FeedbackRule.ts @@ -15,7 +15,7 @@ export class FeedbackRule { } static isSpecialRule(feedbackRule: FeedbackRule): boolean { - return ['isFinalSubmit', 'isSecondToLastSubmit', 'isNonScorable'].includes( + return ['isDefault', 'isFinalSubmit', 'isSecondToLastSubmit', 'isNonScorable'].includes( feedbackRule.expression ); } diff --git a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.spec.ts b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.spec.ts index e13a312ec05..f3b7a7cfcb4 100644 --- a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.spec.ts +++ b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.spec.ts @@ -18,7 +18,8 @@ let evaluator: FeedbackRuleEvaluator; describe('FeedbackRuleEvaluator', () => { beforeEach(() => { evaluator = new FeedbackRuleEvaluator( - new FeedbackRuleComponent(DEFAULT_FEEDBACK_RULES, 5, true) + new FeedbackRuleComponent(DEFAULT_FEEDBACK_RULES, 5, true), + null ); }); matchRule_OneIdea(); @@ -76,7 +77,8 @@ function matchRule_hasKIScore() { describe('hasKIScore()', () => { beforeEach(() => { evaluator = new FeedbackRuleEvaluator( - new FeedbackRuleComponent(HAS_KI_SCORE_FEEDBACK_RULES, 5, true) + new FeedbackRuleComponent(HAS_KI_SCORE_FEEDBACK_RULES, 5, true), + null ); }); matchRule_hasKIScoreScoreInRange_ShouldMatchRule(); @@ -119,7 +121,10 @@ function matchRule_ideaCount() { feedback: 'ideaCountLessThan(3)' }) ]; - evaluator = new FeedbackRuleEvaluator(new FeedbackRuleComponent(feedbackRules, 5, true)); + evaluator = new FeedbackRuleEvaluator( + new FeedbackRuleComponent(feedbackRules, 5, true), + null + ); }); matchRule_ideaCount_MatchRulesBasedOnNumIdeasFound(); }); @@ -142,7 +147,7 @@ function matchNoRule_ReturnDefault() { function matchNoRule_NoDefaultFeedbackAuthored_ReturnApplicationDefault() { it(`should return application default rule when no rule is matched and no default is authored`, () => { - evaluator = new FeedbackRuleEvaluator(new FeedbackRuleComponent([], 5, true)); + evaluator = new FeedbackRuleEvaluator(new FeedbackRuleComponent([], 5, true), null); expectFeedback(['idea10', 'idea11'], [KI_SCORE_1], 1, evaluator.defaultFeedback); }); } diff --git a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.ts b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.ts index a4aab4cd4fc..eea56789645 100644 --- a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.ts +++ b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.ts @@ -1,15 +1,24 @@ +import { Component } from '../../../common/Component'; +import { ConstraintService } from '../../../services/constraintService'; import { FeedbackRuleComponent } from '../../feedbackRule/FeedbackRuleComponent'; import { CRaterResponse } from '../cRater/CRaterResponse'; import { FeedbackRule } from './FeedbackRule'; import { FeedbackRuleExpression } from './FeedbackRuleExpression'; +import { Response } from './Response'; import { TermEvaluator } from './TermEvaluator/TermEvaluator'; import { TermEvaluatorFactory } from './TermEvaluator/TermEvaluatorFactory'; -export class FeedbackRuleEvaluator { +export class FeedbackRuleEvaluator { defaultFeedback = $localize`Thanks for submitting your response.`; - protected factory = new TermEvaluatorFactory(); + protected factory; + protected referenceComponent: Component; - constructor(protected component: FeedbackRuleComponent) {} + constructor( + protected component: FeedbackRuleComponent, + protected constraintService: ConstraintService + ) { + this.factory = new TermEvaluatorFactory(constraintService); + } getFeedbackRule(responses: T): FeedbackRule { return ( @@ -38,7 +47,7 @@ export class FeedbackRuleEvaluator { return ( this.hasMaxSubmitAndIsFinalSubmitRule(rule) && this.component.hasMaxSubmitCountAndUsedAllSubmits( - (responses[responses.length - 1] as CRaterResponse).submitCounter + responses[responses.length - 1].submitCounter ) ); } @@ -50,7 +59,7 @@ export class FeedbackRuleEvaluator { protected satisfiesSecondToLastSubmitRule(responses: T, rule: FeedbackRule): boolean { return ( this.hasMaxSubmitAndIsSecondToLastSubmitRule(rule) && - this.isSecondToLastSubmit((responses[responses.length - 1] as CRaterResponse).submitCounter) + this.isSecondToLastSubmit(responses[responses.length - 1].submitCounter) ); } @@ -114,6 +123,7 @@ export class FeedbackRuleEvaluator { protected evaluateTerm(term: string, responses: T): boolean { const evaluator: TermEvaluator = this.factory.getTermEvaluator(term); + evaluator.setReferenceComponent(this.referenceComponent); return TermEvaluator.requiresAllResponses(term) ? evaluator.evaluate(responses) : evaluator.evaluate(responses[responses.length - 1]); @@ -131,4 +141,8 @@ export class FeedbackRuleEvaluator { }) ); } + + setReferenceComponent(referenceComponent: Component): void { + this.referenceComponent = referenceComponent; + } } diff --git a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents.spec.ts b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents.spec.ts index 68ee74645e0..3791399ffcd 100644 --- a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents.spec.ts +++ b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents.spec.ts @@ -15,7 +15,8 @@ let evaluator: FeedbackRuleEvaluatorMultipleStudents; describe('FeedbackRuleEvaluatorMultipleStudents', () => { beforeEach(() => { evaluator = new FeedbackRuleEvaluatorMultipleStudents( - new FeedbackRuleComponent(DEFAULT_FEEDBACK_RULES, 5, true) + new FeedbackRuleComponent(DEFAULT_FEEDBACK_RULES, 5, true), + null ); }); matchRules_OneIdea(); @@ -38,7 +39,8 @@ function matchRules_HasKIScore() { describe('hasKIScoreScore', () => { beforeEach(() => { evaluator = new FeedbackRuleEvaluatorMultipleStudents( - new FeedbackRuleComponent(HAS_KI_SCORE_FEEDBACK_RULES, 5, true) + new FeedbackRuleComponent(HAS_KI_SCORE_FEEDBACK_RULES, 5, true), + null ); }); matchRules_hasKIScoreScoreInRange_ShouldMatchRule(); @@ -69,7 +71,10 @@ function matchNoRule_ReturnDefault() { function matchNoRule_NoDefaultFeedbackAuthored_ReturnApplicationDefault() { it(`should return application default rule when no rule is matched and no default is authored`, () => { - evaluator = new FeedbackRuleEvaluatorMultipleStudents(new FeedbackRuleComponent([], 5, true)); + evaluator = new FeedbackRuleEvaluatorMultipleStudents( + new FeedbackRuleComponent([], 5, true), + null + ); expectRules([createCRaterResponse(['idea10', 'idea11'], [KI_SCORE_1], 1)], ['default']); }); } diff --git a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents.ts b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents.ts index d3a80b59cd4..fc43424c16a 100644 --- a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents.ts +++ b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents.ts @@ -1,10 +1,11 @@ import { CRaterResponse } from '../cRater/CRaterResponse'; import { FeedbackRule } from './FeedbackRule'; import { FeedbackRuleEvaluator } from './FeedbackRuleEvaluator'; +import { Response } from './Response'; import { TermEvaluator } from './TermEvaluator/TermEvaluator'; -export class FeedbackRuleEvaluatorMultipleStudents extends FeedbackRuleEvaluator { - getFeedbackRules(responses: CRaterResponse[]): FeedbackRule[] { +export class FeedbackRuleEvaluatorMultipleStudents extends FeedbackRuleEvaluator { + getFeedbackRules(responses: Response[]): FeedbackRule[] { const matchedRules = this.component .getFeedbackRules() .filter((rule) => this.satisfiesRule(responses, Object.assign(new FeedbackRule(), rule))); @@ -13,10 +14,7 @@ export class FeedbackRuleEvaluatorMultipleStudents extends FeedbackRuleEvaluator : [this.getDefaultRule(this.component.getFeedbackRules())]; } - protected satisfiesFinalSubmitRule( - responses: CRaterResponse[], - feedbackRule: FeedbackRule - ): boolean { + protected satisfiesFinalSubmitRule(responses: Response[], feedbackRule: FeedbackRule): boolean { return ( this.hasMaxSubmitAndIsFinalSubmitRule(feedbackRule) && responses.some((response: CRaterResponse) => { @@ -26,12 +24,12 @@ export class FeedbackRuleEvaluatorMultipleStudents extends FeedbackRuleEvaluator } protected satisfiesSecondToLastSubmitRule( - responses: CRaterResponse[], + responses: Response[], feedbackRule: FeedbackRule ): boolean { return ( this.hasMaxSubmitAndIsSecondToLastSubmitRule(feedbackRule) && - responses.some((response: CRaterResponse) => { + responses.some((response: Response) => { return this.isSecondToLastSubmit(response.submitCounter); }) ); @@ -49,9 +47,10 @@ export class FeedbackRuleEvaluatorMultipleStudents extends FeedbackRuleEvaluator ); } - protected evaluateTerm(term: string, responses: CRaterResponse[]): boolean { + protected evaluateTerm(term: string, responses: Response[]): boolean { const evaluator: TermEvaluator = this.factory.getTermEvaluator(term); - return responses.some((response: CRaterResponse) => { + evaluator.setReferenceComponent(this.referenceComponent); + return responses.some((response: Response) => { return evaluator.evaluate(response); }); } diff --git a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleExpression.ts b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleExpression.ts index a01e0b5a206..63b4b968049 100644 --- a/src/assets/wise5/components/common/feedbackRule/FeedbackRuleExpression.ts +++ b/src/assets/wise5/components/common/feedbackRule/FeedbackRuleExpression.ts @@ -28,7 +28,7 @@ export class FeedbackRuleExpression { return this.text .replace(/ /g, '') .split( - /(hasKIScore\(\d\)|accumulatedIdeaCountEquals\(\d\)|accumulatedIdeaCountLessThan\(\d\)|accumulatedIdeaCountMoreThan\(\d\)|ideaCountEquals\(\S+\)|ideaCountLessThan\(\S+\)|ideaCountMoreThan\(\S+\)|isSubmitNumber\(\d+\)|&&|\|\||!|\(|\))/g + /(hasKIScore\(\d\)|accumulatedIdeaCountEquals\(\d\)|accumulatedIdeaCountLessThan\(\d\)|accumulatedIdeaCountMoreThan\(\d\)|ideaCountEquals\(\S+\)|ideaCountLessThan\(\S+\)|ideaCountMoreThan\(\S+\)|myChoiceChosen\(\S+\)|isSubmitNumber\(\d+\)|&&|\|\||!|\(|\))/g ) .filter((el) => el !== ''); } diff --git a/src/assets/wise5/components/common/feedbackRule/Response.ts b/src/assets/wise5/components/common/feedbackRule/Response.ts new file mode 100644 index 00000000000..4c533c04579 --- /dev/null +++ b/src/assets/wise5/components/common/feedbackRule/Response.ts @@ -0,0 +1,14 @@ +export class Response { + submitCounter: number; + constructor(jsonObject: any = {}) { + this.populateFields(jsonObject); + } + + protected populateFields(jsonObject: any = {}): void { + for (const key of Object.keys(jsonObject)) { + if (jsonObject[key] != null) { + this[key] = jsonObject[key]; + } + } + } +} diff --git a/src/assets/wise5/components/common/feedbackRule/TermEvaluator/MyChoiceChosenTermEvaluator.spec.ts b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/MyChoiceChosenTermEvaluator.spec.ts new file mode 100644 index 00000000000..a05e9bd5bc2 --- /dev/null +++ b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/MyChoiceChosenTermEvaluator.spec.ts @@ -0,0 +1,38 @@ +import { Component } from '../../../../common/Component'; +import { ComponentContent } from '../../../../common/ComponentContent'; +import { ConstraintService } from '../../../../services/constraintService'; +import { CRaterResponse } from '../../cRater/CRaterResponse'; +import { MyChoiceChosenTermEvaluator } from './MyChoiceChosenTermEvaluator'; + +class ConstraintServiceStub { + evaluateCriteria(criteria: any): boolean { + return true; + } +} + +describe('MyChoiceChosenTermEvaluator', () => { + let evaluator1, mockConstraintService; + beforeEach(() => { + evaluator1 = new MyChoiceChosenTermEvaluator('myChoiceChosen("choice1")'); + evaluator1.setReferenceComponent( + new Component({ id: 'componentA' } as ComponentContent, 'node1') + ); + mockConstraintService = new ConstraintServiceStub(); + evaluator1.setConstraintService(mockConstraintService as ConstraintService); + }); + describe('evaluate()', () => { + [ + { description: 'choice is chosen', choiceChosen: true, expected: true }, + { description: 'choice is not chosen', choiceChosen: false, expected: false } + ].forEach(({ description, choiceChosen, expected }) => { + describe(description, () => { + beforeEach(() => { + spyOn(mockConstraintService, 'evaluateCriteria').and.returnValue(choiceChosen); + }); + it(`returns ${expected}`, () => { + expect(evaluator1.evaluate(new CRaterResponse())).toEqual(expected); + }); + }); + }); + }); +}); diff --git a/src/assets/wise5/components/common/feedbackRule/TermEvaluator/MyChoiceChosenTermEvaluator.ts b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/MyChoiceChosenTermEvaluator.ts new file mode 100644 index 00000000000..5c59cd99e59 --- /dev/null +++ b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/MyChoiceChosenTermEvaluator.ts @@ -0,0 +1,22 @@ +import { Response } from '../Response'; +import { TermEvaluator } from './TermEvaluator'; + +export class MyChoiceChosenTermEvaluator extends TermEvaluator { + private choiceId: string; + + constructor(term: string) { + super(term); + this.choiceId = term.match(/myChoiceChosen\("(\w+)"\)/)[1]; + } + + evaluate(response: Response | Response[]): boolean { + return this.constraintService.evaluateCriteria({ + name: 'choiceChosen', + params: { + nodeId: this.referenceComponent.nodeId, + componentId: this.referenceComponent.id, + choiceIds: this.choiceId + } + }); + } +} diff --git a/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluator.ts b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluator.ts index 6501200935b..ae7134e62e4 100644 --- a/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluator.ts +++ b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluator.ts @@ -1,13 +1,22 @@ -import { CRaterResponse } from '../../cRater/CRaterResponse'; +import { Component } from '../../../../common/Component'; +import { ConstraintService } from '../../../../services/constraintService'; +import { Response } from '../Response'; export abstract class TermEvaluator { + protected referenceComponent: Component; + protected constraintService: ConstraintService; + constructor(protected term: string) {} - abstract evaluate(response: CRaterResponse | CRaterResponse[]): boolean; + abstract evaluate(response: Response | Response[]): boolean; static isAccumulatedIdeaCountTerm(term: string): boolean { return /accumulatedIdeaCount(MoreThan|Equals|LessThan)\([\d+]\)/.test(term); } + static isMyChoiceChosenTerm(term: string): boolean { + return /myChoiceChosen\("\w+"\)/.test(term); + } + static isHasKIScoreTerm(term: string): boolean { return /hasKIScore\([1-5]\)/.test(term); } @@ -30,4 +39,12 @@ export abstract class TermEvaluator { TermEvaluator.isIdeaCountWithResponseIndexTerm(term) ); } + + setConstraintService(service: ConstraintService): void { + this.constraintService = service; + } + + setReferenceComponent(referenceComponent: Component): void { + this.referenceComponent = referenceComponent; + } } diff --git a/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluatorFactory.spec.ts b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluatorFactory.spec.ts index c874b3d56f4..3fdec1e21cd 100644 --- a/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluatorFactory.spec.ts +++ b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluatorFactory.spec.ts @@ -1,3 +1,4 @@ +import { MyChoiceChosenTermEvaluator } from './MyChoiceChosenTermEvaluator'; import { HasKIScoreTermEvaluator } from './HasKIScoreTermEvaluator'; import { IdeaCountTermEvaluator } from './IdeaCountTermEvaluator'; import { IdeaTermEvaluator } from './IdeaTermEvaluator'; @@ -5,10 +6,14 @@ import { IsSubmitNumberEvaluator } from './IsSubmitNumberEvaluator'; import { TermEvaluatorFactory } from './TermEvaluatorFactory'; describe('TermEvaluatorFactory', () => { - const factory = new TermEvaluatorFactory(); + const factory = new TermEvaluatorFactory(null); describe('getTermEvaluator()', () => { it('should return correct evaluator', () => { [ + { + term: 'myChoiceChosen("choice1")', + instanceType: MyChoiceChosenTermEvaluator + }, { term: 'hasKIScore(3)', instanceType: HasKIScoreTermEvaluator }, { term: 'ideaCountMoreThan(1)', instanceType: IdeaCountTermEvaluator }, { term: 'ideaCountEquals(3)', instanceType: IdeaCountTermEvaluator }, diff --git a/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluatorFactory.ts b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluatorFactory.ts index 8f6538f07d3..c633508a146 100644 --- a/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluatorFactory.ts +++ b/src/assets/wise5/components/common/feedbackRule/TermEvaluator/TermEvaluatorFactory.ts @@ -1,4 +1,6 @@ +import { ConstraintService } from '../../../../services/constraintService'; import { AccumulatedIdeaCountTermEvaluator } from './AccumulatedIdeaCountTermEvaluator'; +import { MyChoiceChosenTermEvaluator } from './MyChoiceChosenTermEvaluator'; import { HasKIScoreTermEvaluator } from './HasKIScoreTermEvaluator'; import { IdeaCountTermEvaluator } from './IdeaCountTermEvaluator'; import { IdeaCountWithResponseIndexTermEvaluator } from './IdeaCountWithResponseIndexTermEvaluator'; @@ -7,6 +9,8 @@ import { IsSubmitNumberEvaluator } from './IsSubmitNumberEvaluator'; import { TermEvaluator } from './TermEvaluator'; export class TermEvaluatorFactory { + constructor(private constraintService: ConstraintService) {} + getTermEvaluator(term: string): TermEvaluator { let evaluator: TermEvaluator; if (TermEvaluator.isHasKIScoreTerm(term)) { @@ -19,9 +23,12 @@ export class TermEvaluatorFactory { evaluator = new IsSubmitNumberEvaluator(term); } else if (TermEvaluator.isAccumulatedIdeaCountTerm(term)) { evaluator = new AccumulatedIdeaCountTermEvaluator(term); + } else if (TermEvaluator.isMyChoiceChosenTerm(term)) { + evaluator = new MyChoiceChosenTermEvaluator(term); } else { evaluator = new IdeaTermEvaluator(term); } + evaluator.setConstraintService(this.constraintService); return evaluator; } } diff --git a/src/assets/wise5/components/dialogGuidance/dialog-guidance-student/dialog-guidance-student.component.ts b/src/assets/wise5/components/dialogGuidance/dialog-guidance-student/dialog-guidance-student.component.ts index a2e0cc1f0ac..d1d2253caae 100644 --- a/src/assets/wise5/components/dialogGuidance/dialog-guidance-student/dialog-guidance-student.component.ts +++ b/src/assets/wise5/components/dialogGuidance/dialog-guidance-student/dialog-guidance-student.component.ts @@ -26,6 +26,7 @@ import { ComponentStudent } from '../../component-student.component'; import { DialogGuidanceComponent } from '../DialogGuidanceComponent'; import { copy } from '../../../common/object/object'; import { RawCRaterResponse } from '../../common/cRater/RawCRaterResponse'; +import { ConstraintService } from '../../../services/constraintService'; @Component({ selector: 'dialog-guidance-student', @@ -50,6 +51,7 @@ export class DialogGuidanceStudentComponent extends ComponentStudent { protected componentService: ComponentService, protected computerAvatarService: ComputerAvatarService, protected configService: ConfigService, + private constraintService: ConstraintService, protected cRaterService: CRaterService, protected dialog: MatDialog, protected dialogGuidanceFeedbackService: DialogGuidanceFeedbackService, @@ -86,7 +88,8 @@ export class DialogGuidanceStudentComponent extends ComponentStudent { this.component.getFeedbackRules(), this.getMaxSubmitCount(), this.component.isMultipleFeedbackTextsForSameRuleAllowed() - ) + ), + this.constraintService ); if (this.component.isComputerAvatarEnabled()) { this.initializeComputerAvatar(); diff --git a/src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts b/src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts index f8fec484ec2..b458105b290 100644 --- a/src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts +++ b/src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts @@ -21,6 +21,7 @@ import { OpenResponseService } from '../openResponseService'; import { copy } from '../../../common/object/object'; import { RawCRaterResponse } from '../../common/cRater/RawCRaterResponse'; import { hasConnectedComponent } from '../../../common/ComponentContent'; +import { ConstraintService } from '../../../services/constraintService'; @Component({ selector: 'open-response-student', @@ -38,6 +39,7 @@ export class OpenResponseStudent extends ComponentStudent { protected AnnotationService: AnnotationService, private changeDetector: ChangeDetectorRef, protected ComponentService: ComponentService, + private constraintService: ConstraintService, protected ConfigService: ConfigService, private CRaterService: CRaterService, protected dialog: MatDialog, @@ -399,7 +401,8 @@ export class OpenResponseStudent extends ComponentStudent { this.getFeedbackRules(), this.getMaxSubmitCount(), this.isMultipleFeedbackTextsForSameRuleAllowed() - ) + ), + this.constraintService ); const rule: FeedbackRule = feedbackRuleEvaluator.getFeedbackRule([response]); autoComment = this.getFeedbackText(rule); diff --git a/src/assets/wise5/components/peerChat/peer-chat-question-bank/peer-chat-question-bank.component.ts b/src/assets/wise5/components/peerChat/peer-chat-question-bank/peer-chat-question-bank.component.ts index c5882108ede..a816d8d082c 100644 --- a/src/assets/wise5/components/peerChat/peer-chat-question-bank/peer-chat-question-bank.component.ts +++ b/src/assets/wise5/components/peerChat/peer-chat-question-bank/peer-chat-question-bank.component.ts @@ -3,7 +3,6 @@ import { Observable } from 'rxjs'; import { PeerGroupService } from '../../../services/peerGroupService'; import { ProjectService } from '../../../services/projectService'; import { OpenResponseContent } from '../../openResponse/OpenResponseContent'; -import { QuestionBank } from './QuestionBank'; import { Component as WISEComponent } from '../../../common/Component'; import { PeerGroupStudentData } from '../../../../../app/domain/peerGroupStudentData'; import { CRaterResponse } from '../../common/cRater/CRaterResponse'; @@ -14,6 +13,7 @@ import { concatMap, map } from 'rxjs/operators'; import { PeerGroup } from '../PeerGroup'; import { QuestionBankContent } from './QuestionBankContent'; import { copy } from '../../../common/object/object'; +import { ConstraintService } from '../../../services/constraintService'; @Component({ selector: 'peer-chat-question-bank', @@ -26,14 +26,20 @@ export class PeerChatQuestionBankComponent implements OnInit { @Output() displayedQuestionBankRulesChange = new EventEmitter(); questions: string[]; - constructor(private peerGroupService: PeerGroupService, private projectService: ProjectService) {} + constructor( + private constraintService: ConstraintService, + private peerGroupService: PeerGroupService, + private projectService: ProjectService + ) {} ngOnInit(): void { if (this.displayedQuestionBankRules == null) { - const referenceComponent = this.getReferenceComponent(this.content.questionBank); + const referenceComponent = this.projectService.getReferenceComponent( + this.content.questionBank + ); if ( this.content.questionBank.isPeerGroupingTagSpecified() && - referenceComponent.content.type === 'OpenResponse' + ['MultipleChoice', 'OpenResponse'].includes(referenceComponent.content.type) ) { this.evaluatePeerGroup(referenceComponent); } @@ -42,12 +48,6 @@ export class PeerChatQuestionBankComponent implements OnInit { } } - private getReferenceComponent(questionBank: QuestionBank): WISEComponent { - const nodeId = questionBank.getReferenceNodeId(); - const componentId = questionBank.getReferenceComponentId(); - return new WISEComponent(this.projectService.getComponent(nodeId, componentId), nodeId); - } - private evaluatePeerGroup(referenceComponent: WISEComponent): void { this.getPeerGroupData( this.content.questionBank.getPeerGroupingTag(), @@ -68,10 +68,10 @@ export class PeerChatQuestionBankComponent implements OnInit { referenceComponent: WISEComponent, peerGroupStudentData: PeerGroupStudentData[] ): QuestionBankRule[] { - const cRaterResponses = peerGroupStudentData.map((peerMemberData: PeerGroupStudentData) => { + const responses = peerGroupStudentData.map((peerMemberData: PeerGroupStudentData) => { return new CRaterResponse({ - ideas: peerMemberData.annotation.data.ideas, - scores: peerMemberData.annotation.data.scores, + ideas: peerMemberData.annotation?.data.ideas, + scores: peerMemberData.annotation?.data.scores, submitCounter: peerMemberData.studentWork.studentData.submitCounter }); }); @@ -80,10 +80,12 @@ export class PeerChatQuestionBankComponent implements OnInit { this.content.questionBank.getRules(), (referenceComponent.content as OpenResponseContent).maxSubmitCount, false - ) + ), + this.constraintService ); + feedbackRuleEvaluator.setReferenceComponent(referenceComponent); return this.filterQuestions( - feedbackRuleEvaluator.getFeedbackRules(cRaterResponses) as QuestionBankRule[], + feedbackRuleEvaluator.getFeedbackRules(responses) as QuestionBankRule[], this.content.questionBank.maxQuestionsToShow ); } diff --git a/src/assets/wise5/directives/dynamic-prompt/DynamicPromptEvaluator.ts b/src/assets/wise5/directives/dynamic-prompt/DynamicPromptEvaluator.ts new file mode 100644 index 00000000000..444dc03c550 --- /dev/null +++ b/src/assets/wise5/directives/dynamic-prompt/DynamicPromptEvaluator.ts @@ -0,0 +1,72 @@ +import { Observable, concatMap, map } from 'rxjs'; +import { Component } from '../../common/Component'; +import { PeerGroupStudentData } from '../../../../app/domain/peerGroupStudentData'; +import { PeerGroup } from '../../components/peerChat/PeerGroup'; +import { DynamicPromptComponent } from './dynamic-prompt.component'; +import { FeedbackRuleEvaluator } from '../../components/common/feedbackRule/FeedbackRuleEvaluator'; +import { Response } from '../../components/common/feedbackRule/Response'; +import { FeedbackRuleEvaluatorMultipleStudents } from '../../components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents'; +import { FeedbackRuleComponent } from '../../components/feedbackRule/FeedbackRuleComponent'; +import { FeedbackRule } from '../../components/common/feedbackRule/FeedbackRule'; + +export abstract class DynamicPromptEvaluator { + constructor(protected component: DynamicPromptComponent) {} + + evaluate(referenceComponent: Component): void { + if (this.component.dynamicPrompt.isPeerGroupingTagSpecified()) { + this.evaluatePeerGroup(referenceComponent); + } else { + this.evaluatePersonal(referenceComponent); + } + } + + protected getFeedbackRuleEvaluator( + referenceComponent: Component + ): FeedbackRuleEvaluator { + const feedbackRuleComponent = new FeedbackRuleComponent( + this.component.dynamicPrompt.getRules(), + referenceComponent.content.maxSubmitCount, + false + ); + const evaluator = this.component.dynamicPrompt.isPeerGroupingTagSpecified() + ? new FeedbackRuleEvaluatorMultipleStudents( + feedbackRuleComponent, + this.component.constraintService + ) + : new FeedbackRuleEvaluator(feedbackRuleComponent, this.component.constraintService); + evaluator.setReferenceComponent(referenceComponent); + return evaluator; + } + + protected getPeerGroupData(): Observable { + return this.component.peerGroupService + .retrievePeerGroup(this.component.dynamicPrompt.getPeerGroupingTag()) + .pipe( + concatMap((peerGroup: PeerGroup) => { + return this.component.peerGroupService + .retrieveDynamicPromptStudentData( + peerGroup.id, + this.component.nodeId, + this.component.componentId + ) + .pipe( + map((peerGroupStudentData: PeerGroupStudentData[]) => { + return peerGroupStudentData; + }) + ); + }) + ); + } + + protected getSubmitCounter(componentState: any): number { + return componentState.studentData.submitCounter; + } + + protected setPromptAndEmitRule(feedbackRule: FeedbackRule): void { + this.component.prompt = feedbackRule.prompt; + this.component.dynamicPromptChanged.emit(feedbackRule); // TODO: change to two-way binding variable + } + + abstract evaluatePeerGroup(referenceComponent: Component): void; + abstract evaluatePersonal(referenceComponent: Component): void; +} diff --git a/src/assets/wise5/directives/dynamic-prompt/DynamicPromptMultipleChoiceEvaluator.ts b/src/assets/wise5/directives/dynamic-prompt/DynamicPromptMultipleChoiceEvaluator.ts new file mode 100644 index 00000000000..0d552382bb2 --- /dev/null +++ b/src/assets/wise5/directives/dynamic-prompt/DynamicPromptMultipleChoiceEvaluator.ts @@ -0,0 +1,32 @@ +import { PeerGroupStudentData } from '../../../../app/domain/peerGroupStudentData'; +import { Component } from '../../common/Component'; +import { Response } from '../../components/common/feedbackRule/Response'; +import { DynamicPromptEvaluator } from './DynamicPromptEvaluator'; + +export class DynamicPromptMultipleChoiceEvaluator extends DynamicPromptEvaluator { + evaluatePeerGroup(referenceComponent: Component): void { + this.getPeerGroupData().subscribe((peerGroupStudentData: PeerGroupStudentData[]) => { + const responses = peerGroupStudentData.map((peerMemberData: PeerGroupStudentData) => { + return new Response({ + submitCounter: this.getSubmitCounter(peerMemberData.studentWork) + }); + }); + const feedbackRuleEvaluator = this.getFeedbackRuleEvaluator(referenceComponent); + this.setPromptAndEmitRule(feedbackRuleEvaluator.getFeedbackRule(responses)); + }); + } + + evaluatePersonal(referenceComponent: Component): void { + const feedbackRuleEvaluator = this.getFeedbackRuleEvaluator(referenceComponent); + const nodeId = this.component.dynamicPrompt.getReferenceNodeId(); + const componentId = referenceComponent.content.id; + const latestComponentState = this.component.dataService.getLatestComponentStateByNodeIdAndComponentId( + nodeId, + componentId + ); + const response = new Response({ + submitCounter: this.getSubmitCounter(latestComponentState) + }); + this.setPromptAndEmitRule(feedbackRuleEvaluator.getFeedbackRule([response])); + } +} diff --git a/src/assets/wise5/directives/dynamic-prompt/DynamicPromptOpenResponseEvaluator.ts b/src/assets/wise5/directives/dynamic-prompt/DynamicPromptOpenResponseEvaluator.ts new file mode 100644 index 00000000000..310de2b0c84 --- /dev/null +++ b/src/assets/wise5/directives/dynamic-prompt/DynamicPromptOpenResponseEvaluator.ts @@ -0,0 +1,44 @@ +import { PeerGroupStudentData } from '../../../../app/domain/peerGroupStudentData'; +import { Component } from '../../common/Component'; +import { CRaterResponse } from '../../components/common/cRater/CRaterResponse'; +import { DynamicPromptEvaluator } from './DynamicPromptEvaluator'; + +export class DynamicPromptOpenResponseEvaluator extends DynamicPromptEvaluator { + evaluatePeerGroup(referenceComponent: Component): void { + this.getPeerGroupData().subscribe((peerGroupStudentData: PeerGroupStudentData[]) => { + const cRaterResponses = peerGroupStudentData.map((peerMemberData: PeerGroupStudentData) => { + return new CRaterResponse({ + ideas: peerMemberData.annotation.data.ideas, + scores: peerMemberData.annotation.data.scores, + submitCounter: this.getSubmitCounter(peerMemberData.studentWork) + }); + }); + const feedbackRuleEvaluator = this.getFeedbackRuleEvaluator(referenceComponent); + this.setPromptAndEmitRule(feedbackRuleEvaluator.getFeedbackRule(cRaterResponses)); + }); + } + + evaluatePersonal(referenceComponent: Component): void { + const nodeId = this.component.dynamicPrompt.getReferenceNodeId(); + const componentId = referenceComponent.content.id; + const latestComponentState = this.component.dataService.getLatestComponentStateByNodeIdAndComponentId( + nodeId, + componentId + ); + const latestAutoScoreAnnotation = this.component.annotationService.getLatestScoreAnnotation( + nodeId, + componentId, + this.component.configService.getWorkgroupId(), + 'autoScore' + ); + if (latestComponentState != null && latestAutoScoreAnnotation != null) { + const cRaterResponse = new CRaterResponse({ + ideas: latestAutoScoreAnnotation.data.ideas, + scores: latestAutoScoreAnnotation.data.scores, + submitCounter: this.getSubmitCounter(latestComponentState) + }); + const feedbackRuleEvaluator = this.getFeedbackRuleEvaluator(referenceComponent); + this.setPromptAndEmitRule(feedbackRuleEvaluator.getFeedbackRule([cRaterResponse])); + } + } +} diff --git a/src/assets/wise5/directives/dynamic-prompt/dynamic-prompt.component.spec.ts b/src/assets/wise5/directives/dynamic-prompt/dynamic-prompt.component.spec.ts index 0900c20b968..d5d016d5f1c 100644 --- a/src/assets/wise5/directives/dynamic-prompt/dynamic-prompt.component.spec.ts +++ b/src/assets/wise5/directives/dynamic-prompt/dynamic-prompt.component.spec.ts @@ -62,9 +62,9 @@ function peerGroupingTagDisabled(): void { }); it('should display the post prompt', () => { - const prompts = fixture.debugElement.nativeElement.querySelectorAll('.prompt'); - const lastPrompt = prompts[prompts.length - 1]; - expect(lastPrompt.textContent).toEqual(postPrompt); + expect(fixture.debugElement.nativeElement.querySelectorAll('.prompt')[2].textContent).toEqual( + postPrompt + ); }); it('should display the dynamic prompt', () => { @@ -114,9 +114,9 @@ function createPeerGroupStudentData( } function expectDynamicPromptToEqual(text: string): void { - const prompts = fixture.debugElement.nativeElement.querySelectorAll('.prompt'); - const dynamicPrompt = prompts[prompts.length - 2]; - expect(dynamicPrompt.textContent).toEqual(text); + expect(fixture.debugElement.nativeElement.querySelectorAll('.prompt')[1].textContent).toEqual( + text + ); } function createDynamicPrompt(): DynamicPrompt { diff --git a/src/assets/wise5/directives/dynamic-prompt/dynamic-prompt.component.ts b/src/assets/wise5/directives/dynamic-prompt/dynamic-prompt.component.ts index a10d888cb1c..bfa9c423302 100644 --- a/src/assets/wise5/directives/dynamic-prompt/dynamic-prompt.component.ts +++ b/src/assets/wise5/directives/dynamic-prompt/dynamic-prompt.component.ts @@ -1,21 +1,14 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Observable } from 'rxjs'; -import { concatMap, map } from 'rxjs/operators'; -import { PeerGroupStudentData } from '../../../../app/domain/peerGroupStudentData'; -import { ComponentContent } from '../../common/ComponentContent'; -import { CRaterResponse } from '../../components/common/cRater/CRaterResponse'; import { FeedbackRule } from '../../components/common/feedbackRule/FeedbackRule'; -import { FeedbackRuleEvaluator } from '../../components/common/feedbackRule/FeedbackRuleEvaluator'; -import { FeedbackRuleComponent } from '../../components/feedbackRule/FeedbackRuleComponent'; -import { OpenResponseContent } from '../../components/openResponse/OpenResponseContent'; -import { PeerGroup } from '../../components/peerChat/PeerGroup'; import { AnnotationService } from '../../services/annotationService'; import { ConfigService } from '../../services/configService'; import { PeerGroupService } from '../../services/peerGroupService'; import { ProjectService } from '../../services/projectService'; import { StudentDataService } from '../../services/studentDataService'; import { DynamicPrompt } from './DynamicPrompt'; -import { FeedbackRuleEvaluatorMultipleStudents } from '../../components/common/feedbackRule/FeedbackRuleEvaluatorMultipleStudents'; +import { ConstraintService } from '../../services/constraintService'; +import { DynamicPromptOpenResponseEvaluator } from './DynamicPromptOpenResponseEvaluator'; +import { DynamicPromptMultipleChoiceEvaluator } from './DynamicPromptMultipleChoiceEvaluator'; @Component({ selector: 'dynamic-prompt', @@ -23,118 +16,38 @@ import { FeedbackRuleEvaluatorMultipleStudents } from '../../components/common/f styleUrls: ['./dynamic-prompt.component.scss'] }) export class DynamicPromptComponent implements OnInit { + annotationService: AnnotationService; @Input() componentId: string; + configService: ConfigService; + constraintService: ConstraintService; + dataService: StudentDataService; @Input() dynamicPrompt: DynamicPrompt; @Output() dynamicPromptChanged: EventEmitter = new EventEmitter(); @Input() nodeId: string; prompt: string; + peerGroupService: PeerGroupService; constructor( - private annotationService: AnnotationService, - private configService: ConfigService, - private peerGroupService: PeerGroupService, - private projectService: ProjectService, - private dataService: StudentDataService - ) {} - - ngOnInit(): void { - const referenceComponentContent = this.getReferenceComponent(this.dynamicPrompt); - if (referenceComponentContent.type === 'OpenResponse') { - this.evaluateOpenResponseComponent(referenceComponentContent as OpenResponseContent); - } - } - - private getReferenceComponent(dynamicPrompt: DynamicPrompt): ComponentContent { - const nodeId = dynamicPrompt.getReferenceNodeId(); - const componentId = dynamicPrompt.getReferenceComponentId(); - return this.projectService.getComponent(nodeId, componentId); - } - - private evaluateOpenResponseComponent(referenceComponentContent: OpenResponseContent): void { - if (this.dynamicPrompt.isPeerGroupingTagSpecified()) { - this.evaluatePeerGroupOpenResponse(referenceComponentContent); - } else { - this.evaluatePersonalOpenResponse(referenceComponentContent); - } + annotationService: AnnotationService, + configService: ConfigService, + constraintService: ConstraintService, + dataService: StudentDataService, + peerGroupService: PeerGroupService, + private projectService: ProjectService + ) { + this.annotationService = annotationService; + this.configService = configService; + this.constraintService = constraintService; + this.dataService = dataService; + this.peerGroupService = peerGroupService; } - private evaluatePeerGroupOpenResponse(referenceComponentContent: OpenResponseContent): void { - this.getPeerGroupData( - this.dynamicPrompt.getPeerGroupingTag(), - this.nodeId, - this.componentId - ).subscribe((peerGroupStudentData: PeerGroupStudentData[]) => { - const cRaterResponses = peerGroupStudentData.map((peerMemberData: PeerGroupStudentData) => { - return new CRaterResponse({ - ideas: peerMemberData.annotation.data.ideas, - scores: peerMemberData.annotation.data.scores, - submitCounter: this.getSubmitCounter(peerMemberData.studentWork) - }); - }); - const feedbackRuleEvaluator = new FeedbackRuleEvaluatorMultipleStudents( - new FeedbackRuleComponent( - this.dynamicPrompt.getRules(), - referenceComponentContent.maxSubmitCount, - false - ) - ); - const feedbackRule: FeedbackRule = feedbackRuleEvaluator.getFeedbackRule(cRaterResponses); - this.prompt = feedbackRule.prompt; - this.dynamicPromptChanged.emit(feedbackRule); // TODO: change to two-way binding variable - }); - } - - private getPeerGroupData( - peerGroupingTag: string, - nodeId: string, - componentId: string - ): Observable { - return this.peerGroupService.retrievePeerGroup(peerGroupingTag).pipe( - concatMap((peerGroup: PeerGroup) => { - return this.peerGroupService - .retrieveDynamicPromptStudentData(peerGroup.id, nodeId, componentId) - .pipe( - map((peerGroupStudentData: PeerGroupStudentData[]) => { - return peerGroupStudentData; - }) - ); - }) - ); - } - - private evaluatePersonalOpenResponse(referenceComponentContent: OpenResponseContent): void { - const nodeId = this.dynamicPrompt.getReferenceNodeId(); - const componentId = referenceComponentContent.id; - const latestComponentState = this.dataService.getLatestComponentStateByNodeIdAndComponentId( - nodeId, - componentId - ); - const latestAutoScoreAnnotation = this.annotationService.getLatestScoreAnnotation( - nodeId, - componentId, - this.configService.getWorkgroupId(), - 'autoScore' - ); - if (latestComponentState != null && latestAutoScoreAnnotation != null) { - const cRaterResponse = new CRaterResponse({ - ideas: latestAutoScoreAnnotation.data.ideas, - scores: latestAutoScoreAnnotation.data.scores, - submitCounter: this.getSubmitCounter(latestComponentState) - }); - const feedbackRuleEvaluator = new FeedbackRuleEvaluator( - new FeedbackRuleComponent( - this.dynamicPrompt.getRules(), - referenceComponentContent.maxSubmitCount, - false - ) - ); - const feedbackRule: FeedbackRule = feedbackRuleEvaluator.getFeedbackRule([cRaterResponse]); - this.prompt = feedbackRule.prompt; - this.dynamicPromptChanged.emit(feedbackRule); + ngOnInit(): void { + const referenceComponent = this.projectService.getReferenceComponent(this.dynamicPrompt); + if (referenceComponent.content.type === 'OpenResponse') { + new DynamicPromptOpenResponseEvaluator(this).evaluate(referenceComponent); + } else if (referenceComponent.content.type === 'MultipleChoice') { + new DynamicPromptMultipleChoiceEvaluator(this).evaluate(referenceComponent); } } - - private getSubmitCounter(componentState: any): number { - return componentState.studentData.submitCounter; - } } diff --git a/src/assets/wise5/services/constraintService.ts b/src/assets/wise5/services/constraintService.ts index 69fc53d8e73..b7ebd4a3faf 100644 --- a/src/assets/wise5/services/constraintService.ts +++ b/src/assets/wise5/services/constraintService.ts @@ -144,7 +144,7 @@ export class ConstraintService { : multipleRemovalCriteria.every((criteria) => this.evaluateCriteria(criteria)); } - private evaluateCriteria(criteria: any): boolean { + evaluateCriteria(criteria: any): boolean { const strategy = this.criteriaFunctionNameToStrategy[criteria.name]; return strategy == null || this.evaluateStrategy(criteria, strategy); } diff --git a/src/assets/wise5/services/projectService.ts b/src/assets/wise5/services/projectService.ts index e50a7ad3f12..730e034a63d 100644 --- a/src/assets/wise5/services/projectService.ts +++ b/src/assets/wise5/services/projectService.ts @@ -15,6 +15,9 @@ import { MultipleChoiceContent } from '../components/multipleChoice/MultipleChoi import { TransitionLogic } from '../common/TransitionLogic'; import { Transition } from '../common/Transition'; import { ReferenceComponent } from '../../../app/domain/referenceComponent'; +import { QuestionBank } from '../components/peerChat/peer-chat-question-bank/QuestionBank'; +import { DynamicPrompt } from '../directives/dynamic-prompt/DynamicPrompt'; +import { Component } from '../common/Component'; @Injectable() export class ProjectService { @@ -1971,12 +1974,18 @@ export class ProjectService { * } * @returns the referenceComponent object from a component */ - getReferenceComponent( + getReferenceComponentForField( nodeId: string, componentId: string, - fieldName: string + fieldName: 'dynamicPrompt' | 'questionBank' ): ReferenceComponent { const component = this.getComponent(nodeId, componentId); return component[fieldName]?.referenceComponent; } + + getReferenceComponent(content: QuestionBank | DynamicPrompt): Component { + const nodeId = content.getReferenceNodeId(); + const componentId = content.getReferenceComponentId(); + return new Component(this.getComponent(nodeId, componentId), nodeId); + } } diff --git a/src/assets/wise5/services/studentPeerGroupService.ts b/src/assets/wise5/services/studentPeerGroupService.ts index 61fa6a1d87f..557f3df8011 100644 --- a/src/assets/wise5/services/studentPeerGroupService.ts +++ b/src/assets/wise5/services/studentPeerGroupService.ts @@ -76,7 +76,7 @@ export class StudentPeerGroupService extends PeerGroupService { peerGroupId: number, nodeId: string, componentId: string, - fieldName: string, + fieldName: 'dynamicPrompt' | 'questionBank', urlEnding: string ): Observable { if (this.configService.isPreview()) { @@ -90,9 +90,9 @@ export class StudentPeerGroupService extends PeerGroupService { private getPreviewPeerGroupStudentData( nodeId: string, componentId: string, - fieldName: string + fieldName: 'dynamicPrompt' | 'questionBank' ): Observable { - const referenceComponent = this.projectService.getReferenceComponent( + const referenceComponent = this.projectService.getReferenceComponentForField( nodeId, componentId, fieldName diff --git a/src/messages.xlf b/src/messages.xlf index 0bd4267c218..812bfe1466d 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -15515,11 +15515,11 @@ Are you sure you want to proceed? src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 158 + 160 src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 174 + 176 @@ -15815,7 +15815,7 @@ Are you ready to receive feedback on this answer? Thanks for submitting your response. src/assets/wise5/components/common/feedbackRule/FeedbackRuleEvaluator.ts - 9 + 12 @@ -16390,7 +16390,7 @@ Are you ready to receive feedback on this answer? src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 161 + 163 @@ -19335,7 +19335,7 @@ Current Score: Are you ready to receive feedback on this answer? src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 165 + 167 @@ -19344,7 +19344,7 @@ Are you ready to receive feedback on this answer? Are you ready to receive feedback on this answer? src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 177 + 179 @@ -19353,21 +19353,21 @@ Are you ready to receive feedback on this answer? Are you ready to submit this answer? src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 181 + 183 We are scoring your work... src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 295 + 297 Please Wait src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 296 + 298 @@ -19375,7 +19375,7 @@ Are you ready to submit this answer? If this problem continues, let your teacher know and move on to the next activity. Your work will still be saved. src/assets/wise5/components/openResponse/open-response-student/open-response-student.component.ts - 318 + 320 @@ -20767,112 +20767,112 @@ If this problem continues, let your teacher know and move on to the next activit Complete <b></b> src/assets/wise5/services/projectService.ts - 1171 + 1174 Visit <b></b> src/assets/wise5/services/projectService.ts - 1177 + 1180 Correctly answer <b></b> src/assets/wise5/services/projectService.ts - 1183 + 1186 Obtain a score of <b></b> on <b></b> src/assets/wise5/services/projectService.ts - 1198 + 1201 You must choose "" on "" src/assets/wise5/services/projectService.ts - 1206 + 1209 Submit <b></b> time on <b></b> src/assets/wise5/services/projectService.ts - 1218 + 1221 Submit <b></b> times on <b></b> src/assets/wise5/services/projectService.ts - 1220 + 1223 Take the branch path from <b></b> to <b></b> src/assets/wise5/services/projectService.ts - 1227 + 1230 Write <b></b> words on <b></b> src/assets/wise5/services/projectService.ts - 1233 + 1236 "" is visible src/assets/wise5/services/projectService.ts - 1239 + 1242 "" is visitable src/assets/wise5/services/projectService.ts - 1245 + 1248 Add <b></b> note on <b></b> src/assets/wise5/services/projectService.ts - 1252 + 1255 Add <b></b> notes on <b></b> src/assets/wise5/services/projectService.ts - 1254 + 1257 You must fill in <b></b> row in the <b>Table</b> on <b></b> src/assets/wise5/services/projectService.ts - 1261 + 1264 You must fill in <b></b> rows in the <b>Table</b> on <b></b> src/assets/wise5/services/projectService.ts - 1263 + 1266 Wait for your teacher to unlock the item src/assets/wise5/services/projectService.ts - 1266 + 1269