Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2180 - Queued Assessments - Assessment Workflow Enqueuer Scheduler - Start Assessment (Part 1) #2285

Merged
merged 17 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import {
APPLICATION_NOT_FOUND,
APPLICATION_NOT_VALID,
EducationProgramOfferingService,
StudentAssessmentService,
INVALID_OPERATION_IN_THE_CURRENT_STATUS,
ASSESSMENT_INVALID_OPERATION_IN_THE_CURRENT_STATE,
CRAIncomeVerificationService,
Expand Down Expand Up @@ -89,7 +88,6 @@ export class ApplicationStudentsController extends BaseController {
private readonly programYearService: ProgramYearService,
private readonly offeringService: EducationProgramOfferingService,
private readonly confirmationOfEnrollmentService: ConfirmationOfEnrollmentService,
private readonly assessmentService: StudentAssessmentService,
private readonly applicationControllerService: ApplicationControllerService,
private readonly craIncomeVerificationService: CRAIncomeVerificationService,
private readonly supportingUserService: SupportingUserService,
Expand Down Expand Up @@ -236,16 +234,14 @@ export class ApplicationStudentsController extends BaseController {
studyStartDate,
studyEndDate,
);
const { createdAssessment } =
await this.applicationService.submitApplication(
applicationId,
studentToken.userId,
student.id,
programYear.id,
submissionResult.data.data,
payload.associatedFiles,
);
await this.assessmentService.startAssessment(createdAssessment.id);
await this.applicationService.submitApplication(
applicationId,
studentToken.userId,
student.id,
programYear.id,
submissionResult.data.data,
payload.associatedFiles,
);
} catch (error) {
switch (error.name) {
case APPLICATION_NOT_FOUND:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
import {
ASSESSMENT_ALREADY_IN_PROGRESS,
StudentAppealService,
StudentAssessmentService,
} from "../../services";
import { AuthorizedParties } from "../../auth/authorized-parties.enum";
import {
Expand Down Expand Up @@ -57,7 +56,6 @@ import { StudentAppealControllerService } from "./student-appeal.controller.serv
export class StudentAppealAESTController extends BaseController {
constructor(
private readonly studentAppealService: StudentAppealService,
private readonly studentAssessmentService: StudentAssessmentService,
private readonly studentAppealControllerService: StudentAppealControllerService,
) {
super();
Expand Down Expand Up @@ -107,20 +105,11 @@ export class StudentAppealAESTController extends BaseController {
@UserToken() userToken: IUserToken,
): Promise<void> {
try {
const savedAppeal = await this.studentAppealService.approveRequests(
await this.studentAppealService.approveRequests(
appealId,
payload.requests,
userToken.userId,
);

// The appeal approval will create a student assessment to be processed only
// if at least one request was approved, hence sometimes an appeal will not result
// is an assessment creation if all requests are declined.
if (savedAppeal.studentAssessment) {
await this.studentAssessmentService.startAssessment(
savedAppeal.studentAssessment.id,
);
}
} catch (error: unknown) {
if (error instanceof CustomNamedError) {
switch (error.name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import {
ApplicationWithdrawalImportTextService,
FormService,
INVALID_OPERATION_IN_THE_CURRENT_STATUS,
StudentAssessmentService,
StudentScholasticStandingsService,
} from "../../services";
import { ApiProcessError, ClientTypeBaseRoute } from "../../types";
Expand Down Expand Up @@ -77,7 +76,6 @@ export class ScholasticStandingInstitutionsController extends BaseController {
constructor(
private readonly formService: FormService,
private readonly studentScholasticStandingsService: StudentScholasticStandingsService,
private readonly studentAssessmentService: StudentAssessmentService,
private readonly scholasticStandingControllerService: ScholasticStandingControllerService,
private readonly applicationWithdrawalImportTextService: ApplicationWithdrawalImportTextService,
) {
Expand Down Expand Up @@ -115,20 +113,12 @@ export class ScholasticStandingInstitutionsController extends BaseController {
if (!submissionResult.valid) {
throw new BadRequestException("Invalid submission.");
}
const scholasticStanding =
await this.studentScholasticStandingsService.processScholasticStanding(
locationId,
applicationId,
userToken.userId,
submissionResult.data.data,
);

// Start assessment.
if (scholasticStanding.studentAssessment) {
await this.studentAssessmentService.startAssessment(
scholasticStanding.studentAssessment.id,
);
}
await this.studentScholasticStandingsService.processScholasticStanding(
locationId,
applicationId,
userToken.userId,
submissionResult.data.data,
);
} catch (error: unknown) {
if (error instanceof CustomNamedError) {
switch (error.name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1202,14 +1202,6 @@ export class EducationProgramOfferingService extends RecordDataModelService<Educ
});
promises.push(deleteAssessmentPromise);
}
if (application.applicationStatus === ApplicationStatus.Completed) {
const startAssessmentPromise =
this.workflowClientService.startApplicationAssessment(
application.workflowName,
application.currentAssessment.id,
);
promises.push(startAssessmentPromise);
guru-aot marked this conversation as resolved.
Show resolved Hide resolved
}
if (promises.length >= maxPromisesAllowed) {
// Waits for promises to be process when it reaches maximum allowable parallel
// count.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
STUDENT_APPEAL_INVALID_OPERATION,
STUDENT_APPEAL_NOT_FOUND,
} from "./constants";
import { StudentAssessmentService } from "../student-assessment/student-assessment.service";
import { NotificationActionsService } from "@sims/services/notifications";
import { NoteSharedService } from "@sims/services";

Expand All @@ -43,7 +42,6 @@ export class StudentAppealService extends RecordDataModelService<StudentAppeal>
constructor(
private readonly dataSource: DataSource,
private readonly studentAppealRequestsService: StudentAppealRequestsService,
private readonly studentAssessmentService: StudentAssessmentService,
private readonly notificationActionsService: NotificationActionsService,
private readonly noteSharedService: NoteSharedService,
) {
Expand Down Expand Up @@ -352,10 +350,6 @@ export class StudentAppealService extends RecordDataModelService<StudentAppeal>
);
}

await this.studentAssessmentService.assertAllAssessmentsCompleted(
guru-aot marked this conversation as resolved.
Show resolved Hide resolved
appealToUpdate.application.id,
);

const auditUser = { id: auditUserId } as User;
const auditDate = new Date();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,19 @@ import {
StudentAssessmentStatus,
} from "@sims/sims-db";
import { Brackets, DataSource } from "typeorm";
import { InjectQueue } from "@nestjs/bull";
import { Queue } from "bull";
import { CustomNamedError, QueueNames } from "@sims/utilities";
import { CustomNamedError } from "@sims/utilities";
import {
ASSESSMENT_ALREADY_IN_PROGRESS,
ASSESSMENT_INVALID_OPERATION_IN_THE_CURRENT_STATE,
ASSESSMENT_NOT_FOUND,
} from "./student-assessment.constants";
import { AssessmentHistory } from "./student-assessment.models";
import { StartAssessmentQueueInDTO } from "@sims/services/queue";

/**
* Manages the student assessment related operations.
*/
@Injectable()
export class StudentAssessmentService extends RecordDataModelService<StudentAssessment> {
constructor(
dataSource: DataSource,
@InjectQueue(QueueNames.StartApplicationAssessment)
private readonly startAssessmentQueue: Queue<StartAssessmentQueueInDTO>,
) {
constructor(dataSource: DataSource) {
super(dataSource.getRepository(StudentAssessment));
}

Expand Down Expand Up @@ -131,58 +123,6 @@ export class StudentAssessmentService extends RecordDataModelService<StudentAsse
return query.getMany();
}

/**
* Starts the assessment workflow of one Student Application.
* @param assessmentId Student assessment that need to be processed.
*/
async startAssessment(assessmentId: number): Promise<void> {
const assessment = await this.repo
.createQueryBuilder("assessment")
.select([
"assessment.id",
"assessment.assessmentWorkflowId",
"assessment.triggerType",
"application.id",
"application.applicationStatus",
"application.data",
])
.innerJoin("assessment.application", "application")
.where("assessment.id = :assessmentId", { assessmentId })
.getOne();

if (assessment.assessmentWorkflowId) {
throw new CustomNamedError(
`Student assessment was already started and has a workflow associated with. Assessment id ${assessmentId}`,
ASSESSMENT_INVALID_OPERATION_IN_THE_CURRENT_STATE,
);
}

if (
assessment.triggerType === AssessmentTriggerType.OriginalAssessment &&
assessment.application.applicationStatus !== ApplicationStatus.Submitted
) {
throw new CustomNamedError(
`An assessment with a trigger type '${AssessmentTriggerType.OriginalAssessment}' can only be started with a Student Application in the status '${ApplicationStatus.Submitted}'. Assessment id ${assessmentId}`,
ASSESSMENT_INVALID_OPERATION_IN_THE_CURRENT_STATE,
);
}

if (
assessment.triggerType !== AssessmentTriggerType.OriginalAssessment &&
assessment.application.applicationStatus !== ApplicationStatus.Completed
) {
throw new CustomNamedError(
`An assessment with a trigger type other than '${AssessmentTriggerType.OriginalAssessment}' can only be started with a Student Application in the status '${ApplicationStatus.Completed}'. Assessment id ${assessmentId}`,
ASSESSMENT_INVALID_OPERATION_IN_THE_CURRENT_STATE,
);
}

await this.startAssessmentQueue.add({
workflowName: assessment.application.data.workflowName,
assessmentId: assessment.id,
});
}

/**
* Updates assessment and application statuses when
* the student is confirming the NOA (Notice of Assessment).
Expand Down Expand Up @@ -331,45 +271,4 @@ export class StudentAssessmentService extends RecordDataModelService<StudentAsse

return mapFromRawAndEntities<AssessmentHistory>(queryResult, "status");
}

/**
* Checks if some student assessment is still being processed.
* Only one student assessment can be processed at a given time because
* any assessment can generate Over Awards and they must be taken into
* the consideration every time.
* * Alongside with the check, the DB has an index to prevent that a new
* * assessment record is created when there is already one with the
* * assessment data not populated (submitted/pending).
* @param application application to have the assessments verified.
* @returns true if there is an assessment that is not finalized yet.
*/
async hasIncompleteAssessment(application: number): Promise<boolean> {
const queryResult = await this.repo
.createQueryBuilder("assessment")
.select("1")
.innerJoin("assessment.application", "application")
.andWhere("application.id = :application", { application })
.andWhere("assessment.assessmentData IS NULL")
.limit(1)
.getRawOne();
return !!queryResult;
}

/**
* Validate if the student has any student assessment that it is not
* finished yet (submitted/pending). If there is a student assessment
* already being processed, throws an exception.
* @param application application to have the assessments verified.
*/
async assertAllAssessmentsCompleted(application: number) {
const hasIncompleteAssessment = await this.hasIncompleteAssessment(
application,
);
if (hasIncompleteAssessment) {
throw new CustomNamedError(
"There is already an assessment waiting to be completed. Another assessment cannot be initiated at this time.",
ASSESSMENT_ALREADY_IN_PROGRESS,
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
INVALID_OPERATION_IN_THE_CURRENT_STATUS,
} from "../application/application.service";
import { ScholasticStanding } from "./student-scholastic-standings.model";
import { StudentAssessmentService } from "../student-assessment/student-assessment.service";
import { StudentRestrictionService } from "../restriction/student-restriction.service";
import { APPLICATION_CHANGE_NOT_ELIGIBLE } from "../../constants";
import { SCHOLASTIC_STANDING_MINIMUM_UNSUCCESSFUL_WEEKS } from "../../utilities";
Expand All @@ -40,7 +39,6 @@ export class StudentScholasticStandingsService extends RecordDataModelService<St

constructor(
private readonly dataSource: DataSource,
private readonly studentAssessmentService: StudentAssessmentService,
private readonly studentRestrictionService: StudentRestrictionService,
private readonly notificationActionsService: NotificationActionsService,
private readonly studentRestrictionSharedService: StudentRestrictionSharedService,
Expand Down Expand Up @@ -107,10 +105,6 @@ export class StudentScholasticStandingsService extends RecordDataModelService<St
INVALID_OPERATION_IN_THE_CURRENT_STATUS,
);
}
// Check if any assessment already in progress for this application.
await this.studentAssessmentService.assertAllAssessmentsCompleted(
guru-aot marked this conversation as resolved.
Show resolved Hide resolved
application.id,
);

// Save scholastic standing and create reassessment.
return this.saveScholasticStandingCreateReassessment(
Expand Down
2 changes: 1 addition & 1 deletion sources/packages/backend/apps/db-migrations/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { ormConfig } from "./data-source";
await dataSource.query(`SET search_path TO ${ormConfig.schema}, public;`);
await dataSource.query(`SET SCHEMA '${ormConfig.schema}';`);
console.info(`**** Running migration ****`);
await dataSource.runMigrations();
await dataSource.runMigrations({ transaction: "each" });
andrepestana-aot marked this conversation as resolved.
Show resolved Hide resolved
console.info(`**** Running setupDB: [Complete] ****`);
} catch (error) {
console.error(`Exception occurs during setup db process: ${error}`);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { getSQLFileData } from "../utilities/sqlLoader";

export class AddAssessmentWorkflowEnqueuerQueue1694116500443
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
getSQLFileData("Add-assessment-workflow-enqueuer-queue.sql", "Queue"),
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
getSQLFileData(
"Rollback-add-assessment-workflow-enqueuer-queue.sql",
"Queue",
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { getSQLFileData } from "../utilities/sqlLoader";

export class UpdateStudentAssessmentStatus1694202849264
implements MigrationInterface
{
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
getSQLFileData(
"Update-student-assessment-status.sql",
"StudentAssessments",
),
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
getSQLFileData(
"Rollback-update-student-assessment-status.sql",
"StudentAssessments",
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
INSERT INTO
sims.queue_configurations(queue_name, queue_configuration)
VALUES
(
'assessment-workflow-enqueuer',
'{
"dashboardReadonly": false,
guru-aot marked this conversation as resolved.
Show resolved Hide resolved
"cron": "*/30 * * * * *",
"cleanUpPeriod": 3600000
}' :: json
);
Loading