From 7dbece513511875044e3684880bfdcc428eccc5d Mon Sep 17 00:00:00 2001 From: Hiroki Terashima Date: Fri, 20 Dec 2024 11:00:26 -0800 Subject: [PATCH] refactor(NodeService): Extract student functions (#2027) --- src/assets/wise5/services/nodeService.ts | 158 +----------------- .../wise5/services/studentNodeService.ts | 142 +++++++++++++++- .../wise5/services/studentWebSocketService.ts | 6 +- .../wise5/services/teacherNodeService.ts | 6 +- src/assets/wise5/vle/node/node.component.ts | 4 +- src/messages.xlf | 6 +- 6 files changed, 155 insertions(+), 167 deletions(-) diff --git a/src/assets/wise5/services/nodeService.ts b/src/assets/wise5/services/nodeService.ts index 22f751b3ca0..80fc2698b07 100644 --- a/src/assets/wise5/services/nodeService.ts +++ b/src/assets/wise5/services/nodeService.ts @@ -1,27 +1,21 @@ import { Injectable } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; import { ConfigService } from './configService'; import { ProjectService } from './projectService'; -import { ChooseBranchPathDialogComponent } from '../../../app/preview/modules/choose-branch-path-dialog/choose-branch-path-dialog.component'; import { DataService } from '../../../app/services/data.service'; import { Observable, Subject } from 'rxjs'; import { ConstraintService } from './constraintService'; -import { TransitionLogic } from '../common/TransitionLogic'; @Injectable() export abstract class NodeService { - private transitionResults = {}; - private chooseTransitionPromises = {}; - private nodeSubmitClickedSource: Subject = new Subject(); - public nodeSubmitClicked$: Observable = this.nodeSubmitClickedSource.asObservable(); private doneRenderingComponentSource: Subject = new Subject(); public doneRenderingComponent$ = this.doneRenderingComponentSource.asObservable(); + private nodeSubmitClickedSource: Subject = new Subject(); + public nodeSubmitClicked$: Observable = this.nodeSubmitClickedSource.asObservable(); constructor( - protected dataService: DataService, - protected dialog: MatDialog, protected configService: ConfigService, protected constraintService: ConstraintService, + protected dataService: DataService, protected projectService: ProjectService ) {} @@ -46,152 +40,14 @@ export abstract class NodeService { abstract getPrevNodeId(currentId?: string): string; - /** - * Close the current node (and open the current node's parent group) - */ - closeNode() { - let currentNode = null; - currentNode = this.dataService.getCurrentNode(); + closeNode(): void { + const currentNode = this.dataService.getCurrentNode(); if (currentNode) { - let currentNodeId = currentNode.id; - let parentNode = this.projectService.getParentGroup(currentNodeId); - let parentNodeId = parentNode.id; - this.setCurrentNode(parentNodeId); + const parentNode = this.projectService.getParentGroup(currentNode.id); + this.setCurrentNode(parentNode.id); } } - /** - * Choose the transition the student will take - * @param nodeId the current node id - * @param transitionLogic an object containing transitions and parameters - * for how to choose a transition - * @returns a promise that will return a transition - */ - protected chooseTransition(nodeId: string, transitionLogic: TransitionLogic): Promise { - if (this.configService.isPreview() && this.chooseTransitionPromises[nodeId] != null) { - return this.chooseTransitionPromises[nodeId]; - } - const promise = this.getChooseTransitionPromise(nodeId, transitionLogic); - if (this.configService.isPreview()) { - const availableTransitions = this.getAvailableTransitions(transitionLogic.transitions); - const transitionResult = this.transitionResults[nodeId]; - if (availableTransitions.length > 1 && transitionResult == null) { - this.chooseTransitionPromises[nodeId] = promise; - } - } - return promise; - } - - private getChooseTransitionPromise( - nodeId: string, - transitionLogic: TransitionLogic - ): Promise { - return new Promise((resolve) => { - let transitionResult = this.transitionResults[nodeId]; - if (transitionResult == null || transitionLogic.canChangePath) { - /* - * we have not previously calculated the transition or the - * transition logic allows the student to change branch paths - * so we will calculate the transition again - */ - const transitions = transitionLogic.transitions; - const availableTransitions = this.getAvailableTransitions(transitions); - if (availableTransitions.length == 0) { - transitionResult = null; - } else if (availableTransitions.length == 1) { - transitionResult = availableTransitions[0]; - } else if (availableTransitions.length > 1) { - if (this.configService.isPreview()) { - // we are in preview mode so we will let the user choose the branch path to go to - if (transitionResult != null) { - /* - * the user has previously chosen the branch path so we will use the transition - * they last chose and not ask them again - */ - } else { - this.letUserChooseTransition(availableTransitions, resolve); - } - } else { - transitionResult = this.chooseTransitionAutomatically( - transitionLogic.howToChooseAmongAvailablePaths, - availableTransitions, - transitionResult - ); - } - } - } - if (transitionResult != null) { - this.transitionResults[nodeId] = transitionResult; - resolve(transitionResult); - } - }); - } - - private getAvailableTransitions(transitions: any): any[] { - return transitions.filter( - (transition) => - transition.criteria == null || this.constraintService.evaluateCriterias(transition.criteria) - ); - } - - private letUserChooseTransition(transitions: any[], resolve: (value: any) => void): void { - this.dialog - .open(ChooseBranchPathDialogComponent, { - data: transitions.map((transition) => ({ - nodeId: transition.to, - nodeTitle: this.projectService.getNodePositionAndTitle(transition.to), - transition: transition - })), - disableClose: true - }) - .afterClosed() - .subscribe((result) => resolve(result)); - } - - private chooseTransitionAutomatically( - howToChooseAmongAvailablePaths: string, - availableTransitions: any[], - transitionResult: any - ): any { - if ([null, '', 'random'].includes(howToChooseAmongAvailablePaths)) { - const randomIndex = Math.floor(Math.random() * availableTransitions.length); - transitionResult = availableTransitions[randomIndex]; - } else if (howToChooseAmongAvailablePaths === 'workgroupId') { - const index = this.configService.getWorkgroupId() % availableTransitions.length; - transitionResult = availableTransitions[index]; - } else if (howToChooseAmongAvailablePaths === 'firstAvailable') { - transitionResult = availableTransitions[0]; - } else if (howToChooseAmongAvailablePaths === 'lastAvailable') { - transitionResult = availableTransitions[availableTransitions.length - 1]; - } - return transitionResult; - } - - /** - * Evaluate the transition logic for the current node and create branch - * path taken event if necessary. - */ - evaluateTransitionLogic(): void { - const currentNode = this.projectService.getNode(this.dataService.getCurrentNodeId()); - const transitionLogic = currentNode.getTransitionLogic(); - const branchEvents = this.dataService.getBranchPathTakenEventsByNodeId(currentNode.id); - const alreadyBranched = branchEvents.length > 0; - if ((alreadyBranched && transitionLogic.canChangePath) || !alreadyBranched) { - this.chooseTransition(currentNode.id, transitionLogic).then((transition) => { - if (transition != null) { - this.saveBranchPathTakenEvent(currentNode.id, transition.to); - } - }); - } - } - - private saveBranchPathTakenEvent(fromNodeId: string, toNodeId: string): void { - this.dataService.saveVLEEvent(fromNodeId, null, null, 'Navigation', 'branchPathTaken', { - fromNodeId: fromNodeId, - toNodeId: toNodeId - }); - } - broadcastNodeSubmitClicked(args: any) { this.nodeSubmitClickedSource.next(args); } diff --git a/src/assets/wise5/services/studentNodeService.ts b/src/assets/wise5/services/studentNodeService.ts index 6a60b24fc62..86144c2eadd 100644 --- a/src/assets/wise5/services/studentNodeService.ts +++ b/src/assets/wise5/services/studentNodeService.ts @@ -9,18 +9,22 @@ import { DialogWithCloseComponent } from '../directives/dialog-with-close/dialog import { Constraint } from '../../../app/domain/constraint'; import { TransitionLogic } from '../common/TransitionLogic'; import { StudentDataService } from './studentDataService'; +import { ChooseBranchPathDialogComponent } from '../../../app/preview/modules/choose-branch-path-dialog/choose-branch-path-dialog.component'; @Injectable() export class StudentNodeService extends NodeService { + private chooseTransitionPromises = {}; + private transitionResults = {}; + constructor( - protected dataService: StudentDataService, - protected dialog: MatDialog, protected configService: ConfigService, protected constraintService: ConstraintService, + protected dataService: StudentDataService, + private dialog: MatDialog, private nodeStatusService: NodeStatusService, protected projectService: ProjectService ) { - super(dataService, dialog, configService, constraintService, projectService); + super(configService, constraintService, dataService, projectService); } setCurrentNode(nodeId: string): void { @@ -151,4 +155,136 @@ export class StudentNodeService extends NodeService { }); } } + + /** + * Evaluate the transition logic for the current node and create branch + * path taken event if necessary. + */ + evaluateTransitionLogic(): void { + const currentNode = this.projectService.getNode(this.dataService.getCurrentNodeId()); + const transitionLogic = currentNode.getTransitionLogic(); + const branchEvents = this.dataService.getBranchPathTakenEventsByNodeId(currentNode.id); + const alreadyBranched = branchEvents.length > 0; + if ((alreadyBranched && transitionLogic.canChangePath) || !alreadyBranched) { + this.chooseTransition(currentNode.id, transitionLogic).then((transition) => { + if (transition != null) { + this.saveBranchPathTakenEvent(currentNode.id, transition.to); + } + }); + } + } + + private saveBranchPathTakenEvent(fromNodeId: string, toNodeId: string): void { + this.dataService.saveVLEEvent(fromNodeId, null, null, 'Navigation', 'branchPathTaken', { + fromNodeId: fromNodeId, + toNodeId: toNodeId + }); + } + + /** + * Choose the transition the student will take + * @param nodeId the current node id + * @param transitionLogic an object containing transitions and parameters + * for how to choose a transition + * @returns a promise that will return a transition + */ + protected chooseTransition(nodeId: string, transitionLogic: TransitionLogic): Promise { + if (this.configService.isPreview() && this.chooseTransitionPromises[nodeId] != null) { + return this.chooseTransitionPromises[nodeId]; + } + const promise = this.getChooseTransitionPromise(nodeId, transitionLogic); + if (this.configService.isPreview()) { + const availableTransitions = this.getAvailableTransitions(transitionLogic.transitions); + const transitionResult = this.transitionResults[nodeId]; + if (availableTransitions.length > 1 && transitionResult == null) { + this.chooseTransitionPromises[nodeId] = promise; + } + } + return promise; + } + + private getChooseTransitionPromise( + nodeId: string, + transitionLogic: TransitionLogic + ): Promise { + return new Promise((resolve) => { + let transitionResult = this.transitionResults[nodeId]; + if (transitionResult == null || transitionLogic.canChangePath) { + /* + * we have not previously calculated the transition or the + * transition logic allows the student to change branch paths + * so we will calculate the transition again + */ + const transitions = transitionLogic.transitions; + const availableTransitions = this.getAvailableTransitions(transitions); + if (availableTransitions.length == 0) { + transitionResult = null; + } else if (availableTransitions.length == 1) { + transitionResult = availableTransitions[0]; + } else if (availableTransitions.length > 1) { + if (this.configService.isPreview()) { + // we are in preview mode so we will let the user choose the branch path to go to + if (transitionResult != null) { + /* + * the user has previously chosen the branch path so we will use the transition + * they last chose and not ask them again + */ + } else { + this.letUserChooseTransition(availableTransitions, resolve); + } + } else { + transitionResult = this.chooseTransitionAutomatically( + transitionLogic.howToChooseAmongAvailablePaths, + availableTransitions, + transitionResult + ); + } + } + } + if (transitionResult != null) { + this.transitionResults[nodeId] = transitionResult; + resolve(transitionResult); + } + }); + } + + private getAvailableTransitions(transitions: any): any[] { + return transitions.filter( + (transition) => + transition.criteria == null || this.constraintService.evaluateCriterias(transition.criteria) + ); + } + + private letUserChooseTransition(transitions: any[], resolve: (value: any) => void): void { + this.dialog + .open(ChooseBranchPathDialogComponent, { + data: transitions.map((transition) => ({ + nodeId: transition.to, + nodeTitle: this.projectService.getNodePositionAndTitle(transition.to), + transition: transition + })), + disableClose: true + }) + .afterClosed() + .subscribe((result) => resolve(result)); + } + + private chooseTransitionAutomatically( + howToChooseAmongAvailablePaths: string, + availableTransitions: any[], + transitionResult: any + ): any { + if ([null, '', 'random'].includes(howToChooseAmongAvailablePaths)) { + const randomIndex = Math.floor(Math.random() * availableTransitions.length); + transitionResult = availableTransitions[randomIndex]; + } else if (howToChooseAmongAvailablePaths === 'workgroupId') { + const index = this.configService.getWorkgroupId() % availableTransitions.length; + transitionResult = availableTransitions[index]; + } else if (howToChooseAmongAvailablePaths === 'firstAvailable') { + transitionResult = availableTransitions[0]; + } else if (howToChooseAmongAvailablePaths === 'lastAvailable') { + transitionResult = availableTransitions[availableTransitions.length - 1]; + } + return transitionResult; + } } diff --git a/src/assets/wise5/services/studentWebSocketService.ts b/src/assets/wise5/services/studentWebSocketService.ts index 0683e1233f7..2753ccd28b4 100644 --- a/src/assets/wise5/services/studentWebSocketService.ts +++ b/src/assets/wise5/services/studentWebSocketService.ts @@ -1,16 +1,14 @@ -'use strict'; - import { Injectable } from '@angular/core'; import { AnnotationService } from './annotationService'; import { TagService } from './tagService'; import { StudentDataService } from './studentDataService'; -import { NodeService } from './nodeService'; import { ProjectService } from './projectService'; import { Message } from '@stomp/stompjs'; import { NotebookService } from './notebookService'; import { StompService } from './stompService'; import { ConfigService } from './configService'; import { Annotation } from '../common/Annotation'; +import { StudentNodeService } from './studentNodeService'; @Injectable() export class StudentWebSocketService { @@ -18,7 +16,7 @@ export class StudentWebSocketService { private AnnotationService: AnnotationService, private configService: ConfigService, private dataService: StudentDataService, - private nodeService: NodeService, + private nodeService: StudentNodeService, private notebookService: NotebookService, private ProjectService: ProjectService, private stompService: StompService, diff --git a/src/assets/wise5/services/teacherNodeService.ts b/src/assets/wise5/services/teacherNodeService.ts index 5a12fd6caae..9835c431921 100644 --- a/src/assets/wise5/services/teacherNodeService.ts +++ b/src/assets/wise5/services/teacherNodeService.ts @@ -3,7 +3,6 @@ import { NodeService } from './nodeService'; import { Subject, Observable } from 'rxjs'; import { TeacherDataService } from './teacherDataService'; import { TeacherProjectService } from './teacherProjectService'; -import { MatDialog } from '@angular/material/dialog'; import { ConfigService } from './configService'; import { ConstraintService } from './constraintService'; @@ -18,13 +17,12 @@ export class TeacherNodeService extends NodeService { public starterStateResponse$: Observable = this.starterStateResponseSource.asObservable(); constructor( - protected dataService: TeacherDataService, - protected dialog: MatDialog, protected configService: ConfigService, protected constraintService: ConstraintService, + protected dataService: TeacherDataService, protected projectService: TeacherProjectService ) { - super(dataService, dialog, configService, constraintService, projectService); + super(configService, constraintService, dataService, projectService); } broadcastComponentShowSubmitButtonValueChanged(args: any): void { diff --git a/src/assets/wise5/vle/node/node.component.ts b/src/assets/wise5/vle/node/node.component.ts index cc4e3beef94..19cbfe11500 100644 --- a/src/assets/wise5/vle/node/node.component.ts +++ b/src/assets/wise5/vle/node/node.component.ts @@ -7,7 +7,6 @@ import { ComponentService } from '../../components/componentService'; import { ComponentStateWrapper } from '../../components/ComponentStateWrapper'; import { ConfigService } from '../../services/configService'; import { ConstraintService } from '../../services/constraintService'; -import { NodeService } from '../../services/nodeService'; import { NodeStatusService } from '../../services/nodeStatusService'; import { SessionService } from '../../services/sessionService'; import { StudentDataService } from '../../services/studentDataService'; @@ -19,6 +18,7 @@ import { ComponentComponent } from '../../components/component/component.compone import { MatButtonModule } from '@angular/material/button'; import { ComponentStateInfoComponent } from '../../common/component-state-info/component-state-info.component'; import { HelpIconComponent } from '../../themes/default/themeComponents/helpIcon/help-icon.component'; +import { StudentNodeService } from '../../services/studentNodeService'; @Component({ imports: [ @@ -70,7 +70,7 @@ export class NodeComponent implements OnInit { private componentService: ComponentService, private configService: ConfigService, private constraintService: ConstraintService, - private nodeService: NodeService, + private nodeService: StudentNodeService, private nodeStatusService: NodeStatusService, private projectService: VLEProjectService, private sessionService: SessionService, diff --git a/src/messages.xlf b/src/messages.xlf index 487aac2186b..c1cb85b0cd9 100644 --- a/src/messages.xlf +++ b/src/messages.xlf @@ -21989,21 +21989,21 @@ If this problem continues, let your teacher know and move on to the next activit Sorry, you cannot view this item yet. src/assets/wise5/services/studentNodeService.ts - 40 + 44 Item Locked src/assets/wise5/services/studentNodeService.ts - 44 + 48 <p>To visit <b></b> you need to:</p><ul> src/assets/wise5/services/studentNodeService.ts - 51 + 55