Skip to content

Commit

Permalink
refactor(NodeService): Extract student functions (#2027)
Browse files Browse the repository at this point in the history
  • Loading branch information
hirokiterashima authored Dec 20, 2024
1 parent 8b5e5dd commit 7dbece5
Show file tree
Hide file tree
Showing 6 changed files with 155 additions and 167 deletions.
158 changes: 7 additions & 151 deletions src/assets/wise5/services/nodeService.ts
Original file line number Diff line number Diff line change
@@ -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<any> = new Subject<any>();
public nodeSubmitClicked$: Observable<any> = this.nodeSubmitClickedSource.asObservable();
private doneRenderingComponentSource: Subject<any> = new Subject<any>();
public doneRenderingComponent$ = this.doneRenderingComponentSource.asObservable();
private nodeSubmitClickedSource: Subject<any> = new Subject<any>();
public nodeSubmitClicked$: Observable<any> = this.nodeSubmitClickedSource.asObservable();

constructor(
protected dataService: DataService,
protected dialog: MatDialog,
protected configService: ConfigService,
protected constraintService: ConstraintService,
protected dataService: DataService,
protected projectService: ProjectService
) {}

Expand All @@ -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<any> {
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<any> {
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);
}
Expand Down
142 changes: 139 additions & 3 deletions src/assets/wise5/services/studentNodeService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<any> {
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<any> {
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;
}
}
6 changes: 2 additions & 4 deletions src/assets/wise5/services/studentWebSocketService.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
'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 {
constructor(
private AnnotationService: AnnotationService,
private configService: ConfigService,
private dataService: StudentDataService,
private nodeService: NodeService,
private nodeService: StudentNodeService,
private notebookService: NotebookService,
private ProjectService: ProjectService,
private stompService: StompService,
Expand Down
Loading

0 comments on commit 7dbece5

Please sign in to comment.