@playcademy/sandbox 0.10.1-beta.4 → 0.10.1-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +329 -102
- package/dist/server.js +329 -102
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1128,7 +1128,7 @@ var package_default;
|
|
|
1128
1128
|
var init_package = __esm(() => {
|
|
1129
1129
|
package_default = {
|
|
1130
1130
|
name: "@playcademy/sandbox",
|
|
1131
|
-
version: "0.10.1-beta.
|
|
1131
|
+
version: "0.10.1-beta.5",
|
|
1132
1132
|
description: "Local development server for Playcademy game development",
|
|
1133
1133
|
type: "module",
|
|
1134
1134
|
exports: {
|
|
@@ -47989,7 +47989,7 @@ function rejectSystemManagedReview(input, context2) {
|
|
|
47989
47989
|
});
|
|
47990
47990
|
}
|
|
47991
47991
|
}
|
|
47992
|
-
var TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, CreateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, VerifyTimebackMetricDiscrepancyRequestSchema, AssessmentPurposeSchema, AssessmentStatusSchema, AssessmentKeySchema, DiagnosticKeySchema, DiagnosticRoutingManifestSchema, DiagnosticAssessmentDefinitionInputSchema, AssessmentStandardRefSchema3, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
47992
|
+
var TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, CreateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, VerifyTimebackMetricDiscrepancyRequestSchema, AssessmentPurposeSchema, AssessmentStatusSchema, AssessmentKeySchema, DiagnosticKeySchema, DiagnosticRoutingManifestSchema, DiagnosticAssessmentDefinitionInputSchema, AssessmentStandardRefSchema3, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, ReviseAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
47993
47993
|
var init_schemas4 = __esm(() => {
|
|
47994
47994
|
init_esm();
|
|
47995
47995
|
init_src();
|
|
@@ -48272,6 +48272,11 @@ var init_schemas4 = __esm(() => {
|
|
|
48272
48272
|
requireMasteryStandard(input, context2);
|
|
48273
48273
|
rejectSystemManagedReview(input, context2);
|
|
48274
48274
|
});
|
|
48275
|
+
ReviseAssessmentRequestSchema = exports_external.object({
|
|
48276
|
+
currentQtiTestIdentifier: exports_external.string().trim().min(1, "The current assessment identifier is required"),
|
|
48277
|
+
title: exports_external.string().trim().min(1, "Assessment title is required"),
|
|
48278
|
+
questions: exports_external.array(exports_external.record(exports_external.string(), exports_external.unknown())).min(1, "A revision must contain at least one question").max(500, "A revision cannot contain more than 500 questions")
|
|
48279
|
+
});
|
|
48275
48280
|
AssessmentAssociationImportEntrySchema = exports_external.object({
|
|
48276
48281
|
assessmentKey: AssessmentKeySchema,
|
|
48277
48282
|
qtiTestIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
@@ -106222,6 +106227,69 @@ var init_timeback_assessment_import_util = __esm(() => {
|
|
|
106222
106227
|
init_timeback_qti_authoring_util();
|
|
106223
106228
|
});
|
|
106224
106229
|
|
|
106230
|
+
// ../api-core/src/utils/timeback-assessment-revision.util.ts
|
|
106231
|
+
function assertRevisableAssessment(row, reviewBankIdentifier) {
|
|
106232
|
+
if (row.purpose === "review" || row.qtiTestIdentifier === reviewBankIdentifier) {
|
|
106233
|
+
throw new ValidationError("The review bank is system-managed. Revise its mastery sources instead.");
|
|
106234
|
+
}
|
|
106235
|
+
if (row.purpose === "diagnostic") {
|
|
106236
|
+
throw new ValidationError("Diagnostics are revised by importing a new revision and archiving the current one.");
|
|
106237
|
+
}
|
|
106238
|
+
if (row.status !== "live") {
|
|
106239
|
+
throw new ValidationError(row.status === "draft" ? "Draft assessments are edited directly. Only live assessments can be revised in place." : "Archived assessments cannot be revised. Restore it to live first.");
|
|
106240
|
+
}
|
|
106241
|
+
if (!row.assessmentKey) {
|
|
106242
|
+
throw new ValidationError("This unmanaged assessment cannot be revised. Republish it with an explicit assessment key.");
|
|
106243
|
+
}
|
|
106244
|
+
}
|
|
106245
|
+
function assertRevisableTestStructure(current) {
|
|
106246
|
+
const parts2 = qtiTestParts(current);
|
|
106247
|
+
const sectionCount = parts2.reduce((count, part) => count + part["qti-assessment-section"].length, 0);
|
|
106248
|
+
if (parts2.length !== 1 || sectionCount !== 1) {
|
|
106249
|
+
throw new ValidationError(`Assessment ${current.identifier} has ${parts2.length} test part(s) and ${sectionCount} section(s). Revisions can only replace content in single-section assessments.`);
|
|
106250
|
+
}
|
|
106251
|
+
}
|
|
106252
|
+
function buildRevisionSourceTestInput(input) {
|
|
106253
|
+
assertRevisableTestStructure(input.current);
|
|
106254
|
+
const { "qti-outcome-declaration": outcomeDeclarations, ...structure } = buildQtiTestStructureInput(input.current, input.sourceIdentifier);
|
|
106255
|
+
return {
|
|
106256
|
+
identifier: input.sourceIdentifier,
|
|
106257
|
+
title: input.title,
|
|
106258
|
+
...qtiTestOptionalAttributes(input.current),
|
|
106259
|
+
metadata: {
|
|
106260
|
+
...sourceMetadata(input.current.metadata),
|
|
106261
|
+
ownerSystem: PLAYCADEMY_QTI_OWNER_SYSTEM,
|
|
106262
|
+
ownerGameSlug: input.gameSlug,
|
|
106263
|
+
assessmentKey: input.assessmentKey,
|
|
106264
|
+
integrationId: input.integrationId,
|
|
106265
|
+
subject: input.subject,
|
|
106266
|
+
grade: String(input.grade),
|
|
106267
|
+
[PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY]: emptyReviewBankManifest(input.sourceIdentifier)
|
|
106268
|
+
},
|
|
106269
|
+
...outcomeDeclarations && outcomeDeclarations.length > 0 ? { "qti-outcome-declaration": outcomeDeclarations } : {},
|
|
106270
|
+
"qti-test-part": structure["qti-test-part"].map((part) => ({
|
|
106271
|
+
...part,
|
|
106272
|
+
"qti-assessment-section": part["qti-assessment-section"].map((section) => ({
|
|
106273
|
+
...section,
|
|
106274
|
+
"qti-assessment-item-ref": []
|
|
106275
|
+
}))
|
|
106276
|
+
}))
|
|
106277
|
+
};
|
|
106278
|
+
}
|
|
106279
|
+
function revisionConflict(requestedIdentifier, currentIdentifier) {
|
|
106280
|
+
return new ConflictError("This assessment was revised by someone else. Reload to see the current version before revising again.", {
|
|
106281
|
+
requestedQtiTestIdentifier: requestedIdentifier,
|
|
106282
|
+
currentQtiTestIdentifier: currentIdentifier
|
|
106283
|
+
});
|
|
106284
|
+
}
|
|
106285
|
+
var init_timeback_assessment_revision_util = __esm(() => {
|
|
106286
|
+
init_assessment_runtime2();
|
|
106287
|
+
init_timeback3();
|
|
106288
|
+
init_errors2();
|
|
106289
|
+
init_timeback_assessment_publication_util();
|
|
106290
|
+
init_timeback_qti_authoring_util();
|
|
106291
|
+
});
|
|
106292
|
+
|
|
106225
106293
|
// ../api-core/src/utils/timeback-qti-library.util.ts
|
|
106226
106294
|
function newPlaycademyTestIdentifier() {
|
|
106227
106295
|
return `${PLAYCADEMY_QTI_SOURCE_PREFIX}${crypto.randomUUID()}`;
|
|
@@ -106647,6 +106715,109 @@ class TimebackAssessmentsService {
|
|
|
106647
106715
|
throw error88;
|
|
106648
106716
|
}
|
|
106649
106717
|
}
|
|
106718
|
+
async reviseAssessment(integrationId, input) {
|
|
106719
|
+
const client2 = this.requireClient();
|
|
106720
|
+
const reviewBankIdentifier = reviewBankSinkIdentifier(integrationId);
|
|
106721
|
+
const current = await this.requireCurrentRevisionRow(integrationId, input.currentQtiTestIdentifier);
|
|
106722
|
+
assertRevisableAssessment(current, reviewBankIdentifier);
|
|
106723
|
+
const ownership = await this.requireQtiTestOwnershipContext(integrationId);
|
|
106724
|
+
const { integration } = ownership;
|
|
106725
|
+
const creations = input.questions.map((question) => {
|
|
106726
|
+
const creation = parseQtiQuestionCreationInput(question);
|
|
106727
|
+
if (creation.kind === "authoring") {
|
|
106728
|
+
assertSupportedQtiQuestionInteraction(creation.input);
|
|
106729
|
+
}
|
|
106730
|
+
return creation;
|
|
106731
|
+
});
|
|
106732
|
+
assertAssessmentHasQuestions(creations);
|
|
106733
|
+
const currentTest = await client2.qtiApi.assessmentTests.get(current.qtiTestIdentifier);
|
|
106734
|
+
const sourceIdentifier = input.sourceTestIdentifier;
|
|
106735
|
+
const sourceInput = buildRevisionSourceTestInput({
|
|
106736
|
+
sourceIdentifier,
|
|
106737
|
+
title: input.title,
|
|
106738
|
+
current: currentTest,
|
|
106739
|
+
assessmentKey: current.assessmentKey,
|
|
106740
|
+
integrationId,
|
|
106741
|
+
gameSlug: ownership.gameSlug,
|
|
106742
|
+
subject: integration.subject,
|
|
106743
|
+
grade: integration.grade
|
|
106744
|
+
});
|
|
106745
|
+
const createdItemIdentifiers = [];
|
|
106746
|
+
let sourceCreateAttempted = false;
|
|
106747
|
+
try {
|
|
106748
|
+
sourceCreateAttempted = true;
|
|
106749
|
+
await client2.qtiApi.assessmentTests.create(sourceInput);
|
|
106750
|
+
const sourceTest = await client2.qtiApi.assessmentTests.get(sourceIdentifier);
|
|
106751
|
+
for (const creation of creations) {
|
|
106752
|
+
const created = await this.createQuestionInOwnedTest({
|
|
106753
|
+
client: client2,
|
|
106754
|
+
integrationId,
|
|
106755
|
+
qtiTestIdentifier: sourceIdentifier,
|
|
106756
|
+
test: sourceTest,
|
|
106757
|
+
gameSlug: ownership.gameSlug,
|
|
106758
|
+
creation
|
|
106759
|
+
});
|
|
106760
|
+
createdItemIdentifiers.push(created.reference.identifier);
|
|
106761
|
+
}
|
|
106762
|
+
const loaded = await loadHydratedQtiTest(client2, sourceIdentifier);
|
|
106763
|
+
await this.validateAssessmentHasQuestions(loaded);
|
|
106764
|
+
const publication = await this.publishManagedAssessment(current, current.purpose, null, loaded);
|
|
106765
|
+
if (!publication.artifactsAvailable) {
|
|
106766
|
+
throw new ValidationError("The revised assessment could not be compiled for the runtime. The current version stays live; try again.");
|
|
106767
|
+
}
|
|
106768
|
+
const result = await this.deps.db.transaction(async (tx) => {
|
|
106769
|
+
const rows = await tx.select().from(gameTimebackAssessmentTests).where(and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.assessmentKey, current.assessmentKey))).for("update");
|
|
106770
|
+
const row = rows[0];
|
|
106771
|
+
if (!row) {
|
|
106772
|
+
throw new NotFoundError(`Assessment not found: ${input.currentQtiTestIdentifier}`);
|
|
106773
|
+
}
|
|
106774
|
+
if (row.qtiTestIdentifier !== input.currentQtiTestIdentifier) {
|
|
106775
|
+
throw revisionConflict(input.currentQtiTestIdentifier, row.qtiTestIdentifier);
|
|
106776
|
+
}
|
|
106777
|
+
assertRevisableAssessment(row, reviewBankIdentifier);
|
|
106778
|
+
const previousQtiTestIdentifier = row.qtiTestIdentifier;
|
|
106779
|
+
const unchanged = publication.qtiTestIdentifier === previousQtiTestIdentifier;
|
|
106780
|
+
if (unchanged) {
|
|
106781
|
+
setAttribute("app.assessment.operation", "revise_unchanged");
|
|
106782
|
+
return { row, previousQtiTestIdentifier, unchanged };
|
|
106783
|
+
}
|
|
106784
|
+
const [updatedRow] = await tx.update(gameTimebackAssessmentTests).set({
|
|
106785
|
+
qtiTestIdentifier: publication.qtiTestIdentifier,
|
|
106786
|
+
updatedAt: new Date
|
|
106787
|
+
}).where(eq(gameTimebackAssessmentTests.id, row.id)).returning();
|
|
106788
|
+
setAttribute("app.assessment.operation", "revise");
|
|
106789
|
+
addEvent("assessment.revised", {
|
|
106790
|
+
"app.assessment.assessment_key": row.assessmentKey,
|
|
106791
|
+
"app.assessment.previous_qti_test_identifier": previousQtiTestIdentifier,
|
|
106792
|
+
"app.assessment.qti_test_identifier": publication.qtiTestIdentifier
|
|
106793
|
+
});
|
|
106794
|
+
return { row: updatedRow ?? row, previousQtiTestIdentifier, unchanged };
|
|
106795
|
+
});
|
|
106796
|
+
let warning;
|
|
106797
|
+
if (!result.unchanged && result.row.purpose === "mastery") {
|
|
106798
|
+
try {
|
|
106799
|
+
const sync = await this.synchronizeReviewBank(integrationId);
|
|
106800
|
+
if (sync.warnings.length > 0) {
|
|
106801
|
+
warning = `Review bank: ${sync.warnings.map((entry) => entry.message).join(" ")}`;
|
|
106802
|
+
}
|
|
106803
|
+
} catch (error88) {
|
|
106804
|
+
addEvent("assessment.revision_review_bank_sync_failed", {
|
|
106805
|
+
"app.assessment.qti_test_identifier": result.row.qtiTestIdentifier,
|
|
106806
|
+
"exception.type": errorType(error88),
|
|
106807
|
+
"app.error.message": errorMessage(error88)
|
|
106808
|
+
});
|
|
106809
|
+
warning = `The review bank could not be synchronized: ${errorMessage(error88)}. Use Sync Review Bank to retry.`;
|
|
106810
|
+
}
|
|
106811
|
+
}
|
|
106812
|
+
return {
|
|
106813
|
+
...this.associationSummary(result.row, warning),
|
|
106814
|
+
previousQtiTestIdentifier: result.previousQtiTestIdentifier,
|
|
106815
|
+
unchanged: result.unchanged
|
|
106816
|
+
};
|
|
106817
|
+
} finally {
|
|
106818
|
+
await this.cleanupQtiAssessmentCopy(client2, sourceCreateAttempted ? sourceIdentifier : undefined, createdItemIdentifiers);
|
|
106819
|
+
}
|
|
106820
|
+
}
|
|
106650
106821
|
async removeAssessment(integrationId, qtiTestIdentifier) {
|
|
106651
106822
|
return this.withLockedAssessmentAssociations(integrationId, qtiTestIdentifier, async (row, tx) => {
|
|
106652
106823
|
if (qtiTestIdentifier === reviewBankSinkIdentifier(integrationId)) {
|
|
@@ -106919,106 +107090,118 @@ class TimebackAssessmentsService {
|
|
|
106919
107090
|
assertAllAssessmentAssociationsDraft(associations);
|
|
106920
107091
|
const ownership = await this.requireOwnedDraftAssessment(integrationId, qtiTestIdentifier, row, tx);
|
|
106921
107092
|
const creation = parseQtiQuestionCreationInput(input);
|
|
106922
|
-
const
|
|
106923
|
-
|
|
106924
|
-
|
|
106925
|
-
|
|
106926
|
-
|
|
106927
|
-
|
|
106928
|
-
|
|
106929
|
-
|
|
106930
|
-
const copy = buildQtiQuestionCopyInput({
|
|
106931
|
-
source,
|
|
106932
|
-
targetIdentifier: itemIdentifier,
|
|
106933
|
-
targetTestIdentifier: qtiTestIdentifier,
|
|
106934
|
-
gameSlug: ownership.gameSlug,
|
|
106935
|
-
integrationId
|
|
106936
|
-
});
|
|
106937
|
-
title = copy.title;
|
|
106938
|
-
metadata2 = copy.metadata;
|
|
106939
|
-
xml = copy.xml;
|
|
106940
|
-
fallbackItem = {
|
|
106941
|
-
...source,
|
|
106942
|
-
identifier: itemIdentifier,
|
|
106943
|
-
title,
|
|
106944
|
-
rawXml: xml,
|
|
106945
|
-
metadata: metadata2
|
|
106946
|
-
};
|
|
106947
|
-
} else {
|
|
106948
|
-
structuredInput = creation.input;
|
|
106949
|
-
assertSupportedQtiQuestionInteraction(structuredInput);
|
|
106950
|
-
title = typeof structuredInput.title === "string" ? structuredInput.title.trim() : "";
|
|
106951
|
-
metadata2 = buildOwnedQtiQuestionMetadata(structuredInput, {
|
|
106952
|
-
gameSlug: ownership.gameSlug,
|
|
106953
|
-
testIdentifier: qtiTestIdentifier
|
|
106954
|
-
});
|
|
106955
|
-
xml = buildQuestionXml(structuredInput, itemIdentifier, title);
|
|
106956
|
-
fallbackItem = {
|
|
106957
|
-
...structuredInput,
|
|
106958
|
-
identifier: itemIdentifier,
|
|
106959
|
-
title,
|
|
106960
|
-
...xml ? { rawXml: xml } : {}
|
|
106961
|
-
};
|
|
106962
|
-
}
|
|
106963
|
-
const section = await this.qtiSectionItems(client2, qtiTestIdentifier, undefined, undefined, ownership.test);
|
|
106964
|
-
const href = qtiItemHref(client2, itemIdentifier);
|
|
106965
|
-
let item;
|
|
106966
|
-
let itemCreationAttempted = false;
|
|
106967
|
-
let referenceCreationAttempted = false;
|
|
106968
|
-
try {
|
|
106969
|
-
itemCreationAttempted = true;
|
|
106970
|
-
item = xml ? await client2.course.createAssessmentItemXml({ xml, metadata: metadata2 }) : await client2.qtiApi.assessmentItems.create({
|
|
106971
|
-
...structuredInput,
|
|
106972
|
-
identifier: itemIdentifier,
|
|
106973
|
-
metadata: metadata2
|
|
106974
|
-
});
|
|
106975
|
-
referenceCreationAttempted = true;
|
|
106976
|
-
await section.items.add({
|
|
106977
|
-
identifier: itemIdentifier,
|
|
106978
|
-
href
|
|
106979
|
-
});
|
|
106980
|
-
} catch (error88) {
|
|
106981
|
-
let referenceRemoved = !referenceCreationAttempted;
|
|
106982
|
-
const creationStatus = isApiError(error88) ? error88.statusCode : undefined;
|
|
106983
|
-
const itemCreationDefinitelyRejected = !referenceCreationAttempted && typeof creationStatus === "number" && creationStatus >= 400 && creationStatus < 500 && creationStatus !== 408;
|
|
106984
|
-
if (referenceCreationAttempted) {
|
|
106985
|
-
try {
|
|
106986
|
-
await section.items.remove(itemIdentifier);
|
|
106987
|
-
referenceRemoved = true;
|
|
106988
|
-
} catch (cleanupError) {
|
|
106989
|
-
referenceRemoved = isApiError(cleanupError) && cleanupError.statusCode === 404;
|
|
106990
|
-
addEvent("assessment.qti_question_reference_cleanup_failed", {
|
|
106991
|
-
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
106992
|
-
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
106993
|
-
"exception.type": errorType(cleanupError),
|
|
106994
|
-
"app.error.message": errorMessage(cleanupError)
|
|
106995
|
-
});
|
|
106996
|
-
}
|
|
106997
|
-
}
|
|
106998
|
-
if (itemCreationAttempted && !itemCreationDefinitelyRejected && referenceRemoved) {
|
|
106999
|
-
try {
|
|
107000
|
-
await client2.qtiApi.assessmentItems.delete(itemIdentifier);
|
|
107001
|
-
} catch (cleanupError) {
|
|
107002
|
-
addEvent("assessment.qti_question_cleanup_failed", {
|
|
107003
|
-
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107004
|
-
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
107005
|
-
"exception.type": errorType(cleanupError),
|
|
107006
|
-
"app.error.message": errorMessage(cleanupError)
|
|
107007
|
-
});
|
|
107008
|
-
}
|
|
107009
|
-
}
|
|
107010
|
-
throw error88;
|
|
107011
|
-
}
|
|
107093
|
+
const created = await this.createQuestionInOwnedTest({
|
|
107094
|
+
client: client2,
|
|
107095
|
+
integrationId,
|
|
107096
|
+
qtiTestIdentifier,
|
|
107097
|
+
test: ownership.test,
|
|
107098
|
+
gameSlug: ownership.gameSlug,
|
|
107099
|
+
creation
|
|
107100
|
+
});
|
|
107012
107101
|
setAttribute("app.assessment.operation", creation.kind === "copy" ? "copy_question" : "create_question");
|
|
107013
|
-
return
|
|
107014
|
-
|
|
107015
|
-
|
|
107016
|
-
|
|
107017
|
-
|
|
107018
|
-
|
|
107019
|
-
|
|
107102
|
+
return created;
|
|
107103
|
+
});
|
|
107104
|
+
}
|
|
107105
|
+
async createQuestionInOwnedTest(input) {
|
|
107106
|
+
const { client: client2, integrationId, qtiTestIdentifier, creation } = input;
|
|
107107
|
+
const itemIdentifier = newPlaycademyQuestionIdentifier(qtiTestIdentifier);
|
|
107108
|
+
let title;
|
|
107109
|
+
let metadata2;
|
|
107110
|
+
let xml;
|
|
107111
|
+
let structuredInput;
|
|
107112
|
+
let fallbackItem;
|
|
107113
|
+
if (creation.kind === "copy") {
|
|
107114
|
+
const source = await client2.qtiApi.assessmentItems.get(creation.sourceItemIdentifier);
|
|
107115
|
+
const copy = buildQtiQuestionCopyInput({
|
|
107116
|
+
source,
|
|
107117
|
+
targetIdentifier: itemIdentifier,
|
|
107118
|
+
targetTestIdentifier: qtiTestIdentifier,
|
|
107119
|
+
gameSlug: input.gameSlug,
|
|
107120
|
+
integrationId
|
|
107121
|
+
});
|
|
107122
|
+
title = copy.title;
|
|
107123
|
+
metadata2 = copy.metadata;
|
|
107124
|
+
xml = copy.xml;
|
|
107125
|
+
fallbackItem = {
|
|
107126
|
+
...source,
|
|
107127
|
+
identifier: itemIdentifier,
|
|
107128
|
+
title,
|
|
107129
|
+
rawXml: xml,
|
|
107130
|
+
metadata: metadata2
|
|
107131
|
+
};
|
|
107132
|
+
} else {
|
|
107133
|
+
structuredInput = creation.input;
|
|
107134
|
+
assertSupportedQtiQuestionInteraction(structuredInput);
|
|
107135
|
+
title = typeof structuredInput.title === "string" ? structuredInput.title.trim() : "";
|
|
107136
|
+
metadata2 = buildOwnedQtiQuestionMetadata(structuredInput, {
|
|
107137
|
+
gameSlug: input.gameSlug,
|
|
107138
|
+
testIdentifier: qtiTestIdentifier
|
|
107139
|
+
});
|
|
107140
|
+
xml = buildQuestionXml(structuredInput, itemIdentifier, title);
|
|
107141
|
+
fallbackItem = {
|
|
107142
|
+
...structuredInput,
|
|
107143
|
+
identifier: itemIdentifier,
|
|
107144
|
+
title,
|
|
107145
|
+
...xml ? { rawXml: xml } : {}
|
|
107146
|
+
};
|
|
107147
|
+
}
|
|
107148
|
+
const section = await this.qtiSectionItems(client2, qtiTestIdentifier, undefined, undefined, input.test);
|
|
107149
|
+
const href = qtiItemHref(client2, itemIdentifier);
|
|
107150
|
+
let item;
|
|
107151
|
+
let itemCreationAttempted = false;
|
|
107152
|
+
let referenceCreationAttempted = false;
|
|
107153
|
+
try {
|
|
107154
|
+
itemCreationAttempted = true;
|
|
107155
|
+
item = xml ? await client2.course.createAssessmentItemXml({ xml, metadata: metadata2 }) : await client2.qtiApi.assessmentItems.create({
|
|
107156
|
+
...structuredInput,
|
|
107157
|
+
identifier: itemIdentifier,
|
|
107020
107158
|
metadata: metadata2
|
|
107021
107159
|
});
|
|
107160
|
+
referenceCreationAttempted = true;
|
|
107161
|
+
await section.items.add({
|
|
107162
|
+
identifier: itemIdentifier,
|
|
107163
|
+
href
|
|
107164
|
+
});
|
|
107165
|
+
} catch (error88) {
|
|
107166
|
+
let referenceRemoved = !referenceCreationAttempted;
|
|
107167
|
+
const creationStatus = isApiError(error88) ? error88.statusCode : undefined;
|
|
107168
|
+
const itemCreationDefinitelyRejected = !referenceCreationAttempted && typeof creationStatus === "number" && creationStatus >= 400 && creationStatus < 500 && creationStatus !== 408;
|
|
107169
|
+
if (referenceCreationAttempted) {
|
|
107170
|
+
try {
|
|
107171
|
+
await section.items.remove(itemIdentifier);
|
|
107172
|
+
referenceRemoved = true;
|
|
107173
|
+
} catch (cleanupError) {
|
|
107174
|
+
referenceRemoved = isApiError(cleanupError) && cleanupError.statusCode === 404;
|
|
107175
|
+
addEvent("assessment.qti_question_reference_cleanup_failed", {
|
|
107176
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107177
|
+
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
107178
|
+
"exception.type": errorType(cleanupError),
|
|
107179
|
+
"app.error.message": errorMessage(cleanupError)
|
|
107180
|
+
});
|
|
107181
|
+
}
|
|
107182
|
+
}
|
|
107183
|
+
if (itemCreationAttempted && !itemCreationDefinitelyRejected && referenceRemoved) {
|
|
107184
|
+
try {
|
|
107185
|
+
await client2.qtiApi.assessmentItems.delete(itemIdentifier);
|
|
107186
|
+
} catch (cleanupError) {
|
|
107187
|
+
addEvent("assessment.qti_question_cleanup_failed", {
|
|
107188
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107189
|
+
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
107190
|
+
"exception.type": errorType(cleanupError),
|
|
107191
|
+
"app.error.message": errorMessage(cleanupError)
|
|
107192
|
+
});
|
|
107193
|
+
}
|
|
107194
|
+
}
|
|
107195
|
+
throw error88;
|
|
107196
|
+
}
|
|
107197
|
+
return buildCreatedQtiQuestionReference({
|
|
107198
|
+
item,
|
|
107199
|
+
fallbackItem,
|
|
107200
|
+
itemIdentifier,
|
|
107201
|
+
href,
|
|
107202
|
+
partIdentifier: section.partIdentifier,
|
|
107203
|
+
sectionIdentifier: section.sectionIdentifier,
|
|
107204
|
+
metadata: metadata2
|
|
107022
107205
|
});
|
|
107023
107206
|
}
|
|
107024
107207
|
async updateQuestion(integrationId, qtiTestIdentifier, itemIdentifier, input) {
|
|
@@ -107266,6 +107449,22 @@ class TimebackAssessmentsService {
|
|
|
107266
107449
|
}
|
|
107267
107450
|
return row;
|
|
107268
107451
|
}
|
|
107452
|
+
async requireCurrentRevisionRow(integrationId, qtiTestIdentifier) {
|
|
107453
|
+
const row = await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
107454
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.qtiTestIdentifier, qtiTestIdentifier))
|
|
107455
|
+
});
|
|
107456
|
+
if (row) {
|
|
107457
|
+
return row;
|
|
107458
|
+
}
|
|
107459
|
+
const assessmentKey = assessmentKeyFromManagedQtiIdentifier(qtiTestIdentifier);
|
|
107460
|
+
const successor = assessmentKey ? await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
107461
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.assessmentKey, assessmentKey))
|
|
107462
|
+
}) : undefined;
|
|
107463
|
+
if (successor) {
|
|
107464
|
+
throw revisionConflict(qtiTestIdentifier, successor.qtiTestIdentifier);
|
|
107465
|
+
}
|
|
107466
|
+
throw new NotFoundError(`Assessment not found: ${qtiTestIdentifier}`);
|
|
107467
|
+
}
|
|
107269
107468
|
async requireDraftAssessmentRow(integrationId, qtiTestIdentifier, lockedRow) {
|
|
107270
107469
|
const row = lockedRow ?? await this.requireAssessmentRow(integrationId, qtiTestIdentifier);
|
|
107271
107470
|
assertDraftAssessment(row);
|
|
@@ -107413,9 +107612,21 @@ class TimebackAssessmentsService {
|
|
|
107413
107612
|
itemHref: (identifier) => qtiItemHref(client2, identifier)
|
|
107414
107613
|
});
|
|
107415
107614
|
const artifactSource = await this.ensureManagedQtiPublication(client2, plan);
|
|
107416
|
-
const artifactsAvailable = await this.storeManagedArtifacts(artifactSource, managedAssessmentDiagnosticRoutingManifest(artifactSource.test, plan.assessmentKey));
|
|
107615
|
+
const artifactsAvailable = await this.managedArtifactsStored(plan.qtiTestIdentifier) || await this.storeManagedArtifacts(artifactSource, managedAssessmentDiagnosticRoutingManifest(artifactSource.test, plan.assessmentKey));
|
|
107417
107616
|
const warning = assessmentArtifactPublicationWarning(artifactsAvailable);
|
|
107418
|
-
return { ...plan, ...warning ? { warning } : {} };
|
|
107617
|
+
return { ...plan, artifactsAvailable, ...warning ? { warning } : {} };
|
|
107618
|
+
}
|
|
107619
|
+
async managedArtifactsStored(qtiTestIdentifier) {
|
|
107620
|
+
try {
|
|
107621
|
+
return await this.deps.assessmentArtifacts.loadManagedPlayable(qtiTestIdentifier) !== null;
|
|
107622
|
+
} catch (error88) {
|
|
107623
|
+
addEvent("assessment.artifact_lookup_failed", {
|
|
107624
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107625
|
+
"exception.type": errorType(error88),
|
|
107626
|
+
"app.error.message": errorMessage(error88)
|
|
107627
|
+
});
|
|
107628
|
+
return false;
|
|
107629
|
+
}
|
|
107419
107630
|
}
|
|
107420
107631
|
async cleanupQtiAssessmentCopy(client2, testIdentifier, itemIdentifiers) {
|
|
107421
107632
|
let testDeleted = !testIdentifier;
|
|
@@ -107542,6 +107753,7 @@ var init_timeback_assessments_service = __esm(async () => {
|
|
|
107542
107753
|
init_timeback_assessment_diagnostic_util();
|
|
107543
107754
|
init_timeback_assessment_import_util();
|
|
107544
107755
|
init_timeback_assessment_publication_util();
|
|
107756
|
+
init_timeback_assessment_revision_util();
|
|
107545
107757
|
init_timeback_assessment_rules_util();
|
|
107546
107758
|
init_timeback_qti_authoring_util();
|
|
107547
107759
|
init_timeback_qti_hydration_util();
|
|
@@ -167551,7 +167763,7 @@ function parseQtiLibraryParams(searchParams) {
|
|
|
167551
167763
|
limit
|
|
167552
167764
|
};
|
|
167553
167765
|
}
|
|
167554
|
-
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, resumeRuntimeAssessment, saveRuntimeAssessment, recordRuntimeAssessmentProgress, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
167766
|
+
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, resumeRuntimeAssessment, saveRuntimeAssessment, recordRuntimeAssessmentProgress, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, reviseAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
167555
167767
|
var init_timeback_controller = __esm(() => {
|
|
167556
167768
|
init_esm();
|
|
167557
167769
|
init_src();
|
|
@@ -168296,6 +168508,20 @@ var init_timeback_controller = __esm(() => {
|
|
|
168296
168508
|
const body2 = await parseRequestBody(ctx.request, UpdateAssessmentRequestSchema);
|
|
168297
168509
|
return ctx.services.timebackAssessments.updateAssessment(integrationId, testIdentifier, body2);
|
|
168298
168510
|
});
|
|
168511
|
+
reviseAssessment = requireDeveloper(async (ctx) => {
|
|
168512
|
+
const { gameId, courseId } = ctx.params;
|
|
168513
|
+
if (!gameId || !courseId) {
|
|
168514
|
+
throw ApiError.badRequest("Missing gameId or courseId parameter");
|
|
168515
|
+
}
|
|
168516
|
+
const body2 = await parseRequestBody(ctx.request, ReviseAssessmentRequestSchema);
|
|
168517
|
+
const integrationId = await ctx.services.timebackAssessments.resolveIntegrationId(gameId, courseId, ctx.user);
|
|
168518
|
+
return ctx.services.timebackAssessments.reviseAssessment(integrationId, {
|
|
168519
|
+
currentQtiTestIdentifier: body2.currentQtiTestIdentifier,
|
|
168520
|
+
sourceTestIdentifier: newPlaycademyTestIdentifier(),
|
|
168521
|
+
title: body2.title,
|
|
168522
|
+
questions: body2.questions
|
|
168523
|
+
});
|
|
168524
|
+
});
|
|
168299
168525
|
synchronizeReviewBank = requireDeveloper(async (ctx) => {
|
|
168300
168526
|
const { gameId, courseId } = ctx.params;
|
|
168301
168527
|
if (!gameId || !courseId) {
|
|
@@ -168446,6 +168672,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
168446
168672
|
createAssessment,
|
|
168447
168673
|
attachExistingAssessments,
|
|
168448
168674
|
updateAssessment,
|
|
168675
|
+
reviseAssessment,
|
|
168449
168676
|
synchronizeReviewBank,
|
|
168450
168677
|
removeAssessment,
|
|
168451
168678
|
reorderQuestions,
|
package/dist/server.js
CHANGED
|
@@ -1127,7 +1127,7 @@ var package_default;
|
|
|
1127
1127
|
var init_package = __esm(() => {
|
|
1128
1128
|
package_default = {
|
|
1129
1129
|
name: "@playcademy/sandbox",
|
|
1130
|
-
version: "0.10.1-beta.
|
|
1130
|
+
version: "0.10.1-beta.5",
|
|
1131
1131
|
description: "Local development server for Playcademy game development",
|
|
1132
1132
|
type: "module",
|
|
1133
1133
|
exports: {
|
|
@@ -47988,7 +47988,7 @@ function rejectSystemManagedReview(input, context2) {
|
|
|
47988
47988
|
});
|
|
47989
47989
|
}
|
|
47990
47990
|
}
|
|
47991
|
-
var TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, CreateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, VerifyTimebackMetricDiscrepancyRequestSchema, AssessmentPurposeSchema, AssessmentStatusSchema, AssessmentKeySchema, DiagnosticKeySchema, DiagnosticRoutingManifestSchema, DiagnosticAssessmentDefinitionInputSchema, AssessmentStandardRefSchema3, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
47991
|
+
var TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, CreateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, VerifyTimebackMetricDiscrepancyRequestSchema, AssessmentPurposeSchema, AssessmentStatusSchema, AssessmentKeySchema, DiagnosticKeySchema, DiagnosticRoutingManifestSchema, DiagnosticAssessmentDefinitionInputSchema, AssessmentStandardRefSchema3, CreateAssessmentRequestSchema, UpdateAssessmentRequestSchema, CopyAssessmentRequestSchema, ReviseAssessmentRequestSchema, AssessmentAssociationImportEntrySchema, AttachExistingAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
47992
47992
|
var init_schemas4 = __esm(() => {
|
|
47993
47993
|
init_esm();
|
|
47994
47994
|
init_src();
|
|
@@ -48271,6 +48271,11 @@ var init_schemas4 = __esm(() => {
|
|
|
48271
48271
|
requireMasteryStandard(input, context2);
|
|
48272
48272
|
rejectSystemManagedReview(input, context2);
|
|
48273
48273
|
});
|
|
48274
|
+
ReviseAssessmentRequestSchema = exports_external.object({
|
|
48275
|
+
currentQtiTestIdentifier: exports_external.string().trim().min(1, "The current assessment identifier is required"),
|
|
48276
|
+
title: exports_external.string().trim().min(1, "Assessment title is required"),
|
|
48277
|
+
questions: exports_external.array(exports_external.record(exports_external.string(), exports_external.unknown())).min(1, "A revision must contain at least one question").max(500, "A revision cannot contain more than 500 questions")
|
|
48278
|
+
});
|
|
48274
48279
|
AssessmentAssociationImportEntrySchema = exports_external.object({
|
|
48275
48280
|
assessmentKey: AssessmentKeySchema,
|
|
48276
48281
|
qtiTestIdentifier: exports_external.string().trim().min(1, "Assessment identifier is required"),
|
|
@@ -106221,6 +106226,69 @@ var init_timeback_assessment_import_util = __esm(() => {
|
|
|
106221
106226
|
init_timeback_qti_authoring_util();
|
|
106222
106227
|
});
|
|
106223
106228
|
|
|
106229
|
+
// ../api-core/src/utils/timeback-assessment-revision.util.ts
|
|
106230
|
+
function assertRevisableAssessment(row, reviewBankIdentifier) {
|
|
106231
|
+
if (row.purpose === "review" || row.qtiTestIdentifier === reviewBankIdentifier) {
|
|
106232
|
+
throw new ValidationError("The review bank is system-managed. Revise its mastery sources instead.");
|
|
106233
|
+
}
|
|
106234
|
+
if (row.purpose === "diagnostic") {
|
|
106235
|
+
throw new ValidationError("Diagnostics are revised by importing a new revision and archiving the current one.");
|
|
106236
|
+
}
|
|
106237
|
+
if (row.status !== "live") {
|
|
106238
|
+
throw new ValidationError(row.status === "draft" ? "Draft assessments are edited directly. Only live assessments can be revised in place." : "Archived assessments cannot be revised. Restore it to live first.");
|
|
106239
|
+
}
|
|
106240
|
+
if (!row.assessmentKey) {
|
|
106241
|
+
throw new ValidationError("This unmanaged assessment cannot be revised. Republish it with an explicit assessment key.");
|
|
106242
|
+
}
|
|
106243
|
+
}
|
|
106244
|
+
function assertRevisableTestStructure(current) {
|
|
106245
|
+
const parts2 = qtiTestParts(current);
|
|
106246
|
+
const sectionCount = parts2.reduce((count, part) => count + part["qti-assessment-section"].length, 0);
|
|
106247
|
+
if (parts2.length !== 1 || sectionCount !== 1) {
|
|
106248
|
+
throw new ValidationError(`Assessment ${current.identifier} has ${parts2.length} test part(s) and ${sectionCount} section(s). Revisions can only replace content in single-section assessments.`);
|
|
106249
|
+
}
|
|
106250
|
+
}
|
|
106251
|
+
function buildRevisionSourceTestInput(input) {
|
|
106252
|
+
assertRevisableTestStructure(input.current);
|
|
106253
|
+
const { "qti-outcome-declaration": outcomeDeclarations, ...structure } = buildQtiTestStructureInput(input.current, input.sourceIdentifier);
|
|
106254
|
+
return {
|
|
106255
|
+
identifier: input.sourceIdentifier,
|
|
106256
|
+
title: input.title,
|
|
106257
|
+
...qtiTestOptionalAttributes(input.current),
|
|
106258
|
+
metadata: {
|
|
106259
|
+
...sourceMetadata(input.current.metadata),
|
|
106260
|
+
ownerSystem: PLAYCADEMY_QTI_OWNER_SYSTEM,
|
|
106261
|
+
ownerGameSlug: input.gameSlug,
|
|
106262
|
+
assessmentKey: input.assessmentKey,
|
|
106263
|
+
integrationId: input.integrationId,
|
|
106264
|
+
subject: input.subject,
|
|
106265
|
+
grade: String(input.grade),
|
|
106266
|
+
[PLAYCADEMY_REVIEW_BANK_MANIFEST_METADATA_KEY]: emptyReviewBankManifest(input.sourceIdentifier)
|
|
106267
|
+
},
|
|
106268
|
+
...outcomeDeclarations && outcomeDeclarations.length > 0 ? { "qti-outcome-declaration": outcomeDeclarations } : {},
|
|
106269
|
+
"qti-test-part": structure["qti-test-part"].map((part) => ({
|
|
106270
|
+
...part,
|
|
106271
|
+
"qti-assessment-section": part["qti-assessment-section"].map((section) => ({
|
|
106272
|
+
...section,
|
|
106273
|
+
"qti-assessment-item-ref": []
|
|
106274
|
+
}))
|
|
106275
|
+
}))
|
|
106276
|
+
};
|
|
106277
|
+
}
|
|
106278
|
+
function revisionConflict(requestedIdentifier, currentIdentifier) {
|
|
106279
|
+
return new ConflictError("This assessment was revised by someone else. Reload to see the current version before revising again.", {
|
|
106280
|
+
requestedQtiTestIdentifier: requestedIdentifier,
|
|
106281
|
+
currentQtiTestIdentifier: currentIdentifier
|
|
106282
|
+
});
|
|
106283
|
+
}
|
|
106284
|
+
var init_timeback_assessment_revision_util = __esm(() => {
|
|
106285
|
+
init_assessment_runtime2();
|
|
106286
|
+
init_timeback3();
|
|
106287
|
+
init_errors2();
|
|
106288
|
+
init_timeback_assessment_publication_util();
|
|
106289
|
+
init_timeback_qti_authoring_util();
|
|
106290
|
+
});
|
|
106291
|
+
|
|
106224
106292
|
// ../api-core/src/utils/timeback-qti-library.util.ts
|
|
106225
106293
|
function newPlaycademyTestIdentifier() {
|
|
106226
106294
|
return `${PLAYCADEMY_QTI_SOURCE_PREFIX}${crypto.randomUUID()}`;
|
|
@@ -106646,6 +106714,109 @@ class TimebackAssessmentsService {
|
|
|
106646
106714
|
throw error88;
|
|
106647
106715
|
}
|
|
106648
106716
|
}
|
|
106717
|
+
async reviseAssessment(integrationId, input) {
|
|
106718
|
+
const client2 = this.requireClient();
|
|
106719
|
+
const reviewBankIdentifier = reviewBankSinkIdentifier(integrationId);
|
|
106720
|
+
const current = await this.requireCurrentRevisionRow(integrationId, input.currentQtiTestIdentifier);
|
|
106721
|
+
assertRevisableAssessment(current, reviewBankIdentifier);
|
|
106722
|
+
const ownership = await this.requireQtiTestOwnershipContext(integrationId);
|
|
106723
|
+
const { integration } = ownership;
|
|
106724
|
+
const creations = input.questions.map((question) => {
|
|
106725
|
+
const creation = parseQtiQuestionCreationInput(question);
|
|
106726
|
+
if (creation.kind === "authoring") {
|
|
106727
|
+
assertSupportedQtiQuestionInteraction(creation.input);
|
|
106728
|
+
}
|
|
106729
|
+
return creation;
|
|
106730
|
+
});
|
|
106731
|
+
assertAssessmentHasQuestions(creations);
|
|
106732
|
+
const currentTest = await client2.qtiApi.assessmentTests.get(current.qtiTestIdentifier);
|
|
106733
|
+
const sourceIdentifier = input.sourceTestIdentifier;
|
|
106734
|
+
const sourceInput = buildRevisionSourceTestInput({
|
|
106735
|
+
sourceIdentifier,
|
|
106736
|
+
title: input.title,
|
|
106737
|
+
current: currentTest,
|
|
106738
|
+
assessmentKey: current.assessmentKey,
|
|
106739
|
+
integrationId,
|
|
106740
|
+
gameSlug: ownership.gameSlug,
|
|
106741
|
+
subject: integration.subject,
|
|
106742
|
+
grade: integration.grade
|
|
106743
|
+
});
|
|
106744
|
+
const createdItemIdentifiers = [];
|
|
106745
|
+
let sourceCreateAttempted = false;
|
|
106746
|
+
try {
|
|
106747
|
+
sourceCreateAttempted = true;
|
|
106748
|
+
await client2.qtiApi.assessmentTests.create(sourceInput);
|
|
106749
|
+
const sourceTest = await client2.qtiApi.assessmentTests.get(sourceIdentifier);
|
|
106750
|
+
for (const creation of creations) {
|
|
106751
|
+
const created = await this.createQuestionInOwnedTest({
|
|
106752
|
+
client: client2,
|
|
106753
|
+
integrationId,
|
|
106754
|
+
qtiTestIdentifier: sourceIdentifier,
|
|
106755
|
+
test: sourceTest,
|
|
106756
|
+
gameSlug: ownership.gameSlug,
|
|
106757
|
+
creation
|
|
106758
|
+
});
|
|
106759
|
+
createdItemIdentifiers.push(created.reference.identifier);
|
|
106760
|
+
}
|
|
106761
|
+
const loaded = await loadHydratedQtiTest(client2, sourceIdentifier);
|
|
106762
|
+
await this.validateAssessmentHasQuestions(loaded);
|
|
106763
|
+
const publication = await this.publishManagedAssessment(current, current.purpose, null, loaded);
|
|
106764
|
+
if (!publication.artifactsAvailable) {
|
|
106765
|
+
throw new ValidationError("The revised assessment could not be compiled for the runtime. The current version stays live; try again.");
|
|
106766
|
+
}
|
|
106767
|
+
const result = await this.deps.db.transaction(async (tx) => {
|
|
106768
|
+
const rows = await tx.select().from(gameTimebackAssessmentTests).where(and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.assessmentKey, current.assessmentKey))).for("update");
|
|
106769
|
+
const row = rows[0];
|
|
106770
|
+
if (!row) {
|
|
106771
|
+
throw new NotFoundError(`Assessment not found: ${input.currentQtiTestIdentifier}`);
|
|
106772
|
+
}
|
|
106773
|
+
if (row.qtiTestIdentifier !== input.currentQtiTestIdentifier) {
|
|
106774
|
+
throw revisionConflict(input.currentQtiTestIdentifier, row.qtiTestIdentifier);
|
|
106775
|
+
}
|
|
106776
|
+
assertRevisableAssessment(row, reviewBankIdentifier);
|
|
106777
|
+
const previousQtiTestIdentifier = row.qtiTestIdentifier;
|
|
106778
|
+
const unchanged = publication.qtiTestIdentifier === previousQtiTestIdentifier;
|
|
106779
|
+
if (unchanged) {
|
|
106780
|
+
setAttribute("app.assessment.operation", "revise_unchanged");
|
|
106781
|
+
return { row, previousQtiTestIdentifier, unchanged };
|
|
106782
|
+
}
|
|
106783
|
+
const [updatedRow] = await tx.update(gameTimebackAssessmentTests).set({
|
|
106784
|
+
qtiTestIdentifier: publication.qtiTestIdentifier,
|
|
106785
|
+
updatedAt: new Date
|
|
106786
|
+
}).where(eq(gameTimebackAssessmentTests.id, row.id)).returning();
|
|
106787
|
+
setAttribute("app.assessment.operation", "revise");
|
|
106788
|
+
addEvent("assessment.revised", {
|
|
106789
|
+
"app.assessment.assessment_key": row.assessmentKey,
|
|
106790
|
+
"app.assessment.previous_qti_test_identifier": previousQtiTestIdentifier,
|
|
106791
|
+
"app.assessment.qti_test_identifier": publication.qtiTestIdentifier
|
|
106792
|
+
});
|
|
106793
|
+
return { row: updatedRow ?? row, previousQtiTestIdentifier, unchanged };
|
|
106794
|
+
});
|
|
106795
|
+
let warning;
|
|
106796
|
+
if (!result.unchanged && result.row.purpose === "mastery") {
|
|
106797
|
+
try {
|
|
106798
|
+
const sync = await this.synchronizeReviewBank(integrationId);
|
|
106799
|
+
if (sync.warnings.length > 0) {
|
|
106800
|
+
warning = `Review bank: ${sync.warnings.map((entry) => entry.message).join(" ")}`;
|
|
106801
|
+
}
|
|
106802
|
+
} catch (error88) {
|
|
106803
|
+
addEvent("assessment.revision_review_bank_sync_failed", {
|
|
106804
|
+
"app.assessment.qti_test_identifier": result.row.qtiTestIdentifier,
|
|
106805
|
+
"exception.type": errorType(error88),
|
|
106806
|
+
"app.error.message": errorMessage(error88)
|
|
106807
|
+
});
|
|
106808
|
+
warning = `The review bank could not be synchronized: ${errorMessage(error88)}. Use Sync Review Bank to retry.`;
|
|
106809
|
+
}
|
|
106810
|
+
}
|
|
106811
|
+
return {
|
|
106812
|
+
...this.associationSummary(result.row, warning),
|
|
106813
|
+
previousQtiTestIdentifier: result.previousQtiTestIdentifier,
|
|
106814
|
+
unchanged: result.unchanged
|
|
106815
|
+
};
|
|
106816
|
+
} finally {
|
|
106817
|
+
await this.cleanupQtiAssessmentCopy(client2, sourceCreateAttempted ? sourceIdentifier : undefined, createdItemIdentifiers);
|
|
106818
|
+
}
|
|
106819
|
+
}
|
|
106649
106820
|
async removeAssessment(integrationId, qtiTestIdentifier) {
|
|
106650
106821
|
return this.withLockedAssessmentAssociations(integrationId, qtiTestIdentifier, async (row, tx) => {
|
|
106651
106822
|
if (qtiTestIdentifier === reviewBankSinkIdentifier(integrationId)) {
|
|
@@ -106918,106 +107089,118 @@ class TimebackAssessmentsService {
|
|
|
106918
107089
|
assertAllAssessmentAssociationsDraft(associations);
|
|
106919
107090
|
const ownership = await this.requireOwnedDraftAssessment(integrationId, qtiTestIdentifier, row, tx);
|
|
106920
107091
|
const creation = parseQtiQuestionCreationInput(input);
|
|
106921
|
-
const
|
|
106922
|
-
|
|
106923
|
-
|
|
106924
|
-
|
|
106925
|
-
|
|
106926
|
-
|
|
106927
|
-
|
|
106928
|
-
|
|
106929
|
-
const copy = buildQtiQuestionCopyInput({
|
|
106930
|
-
source,
|
|
106931
|
-
targetIdentifier: itemIdentifier,
|
|
106932
|
-
targetTestIdentifier: qtiTestIdentifier,
|
|
106933
|
-
gameSlug: ownership.gameSlug,
|
|
106934
|
-
integrationId
|
|
106935
|
-
});
|
|
106936
|
-
title = copy.title;
|
|
106937
|
-
metadata2 = copy.metadata;
|
|
106938
|
-
xml = copy.xml;
|
|
106939
|
-
fallbackItem = {
|
|
106940
|
-
...source,
|
|
106941
|
-
identifier: itemIdentifier,
|
|
106942
|
-
title,
|
|
106943
|
-
rawXml: xml,
|
|
106944
|
-
metadata: metadata2
|
|
106945
|
-
};
|
|
106946
|
-
} else {
|
|
106947
|
-
structuredInput = creation.input;
|
|
106948
|
-
assertSupportedQtiQuestionInteraction(structuredInput);
|
|
106949
|
-
title = typeof structuredInput.title === "string" ? structuredInput.title.trim() : "";
|
|
106950
|
-
metadata2 = buildOwnedQtiQuestionMetadata(structuredInput, {
|
|
106951
|
-
gameSlug: ownership.gameSlug,
|
|
106952
|
-
testIdentifier: qtiTestIdentifier
|
|
106953
|
-
});
|
|
106954
|
-
xml = buildQuestionXml(structuredInput, itemIdentifier, title);
|
|
106955
|
-
fallbackItem = {
|
|
106956
|
-
...structuredInput,
|
|
106957
|
-
identifier: itemIdentifier,
|
|
106958
|
-
title,
|
|
106959
|
-
...xml ? { rawXml: xml } : {}
|
|
106960
|
-
};
|
|
106961
|
-
}
|
|
106962
|
-
const section = await this.qtiSectionItems(client2, qtiTestIdentifier, undefined, undefined, ownership.test);
|
|
106963
|
-
const href = qtiItemHref(client2, itemIdentifier);
|
|
106964
|
-
let item;
|
|
106965
|
-
let itemCreationAttempted = false;
|
|
106966
|
-
let referenceCreationAttempted = false;
|
|
106967
|
-
try {
|
|
106968
|
-
itemCreationAttempted = true;
|
|
106969
|
-
item = xml ? await client2.course.createAssessmentItemXml({ xml, metadata: metadata2 }) : await client2.qtiApi.assessmentItems.create({
|
|
106970
|
-
...structuredInput,
|
|
106971
|
-
identifier: itemIdentifier,
|
|
106972
|
-
metadata: metadata2
|
|
106973
|
-
});
|
|
106974
|
-
referenceCreationAttempted = true;
|
|
106975
|
-
await section.items.add({
|
|
106976
|
-
identifier: itemIdentifier,
|
|
106977
|
-
href
|
|
106978
|
-
});
|
|
106979
|
-
} catch (error88) {
|
|
106980
|
-
let referenceRemoved = !referenceCreationAttempted;
|
|
106981
|
-
const creationStatus = isApiError(error88) ? error88.statusCode : undefined;
|
|
106982
|
-
const itemCreationDefinitelyRejected = !referenceCreationAttempted && typeof creationStatus === "number" && creationStatus >= 400 && creationStatus < 500 && creationStatus !== 408;
|
|
106983
|
-
if (referenceCreationAttempted) {
|
|
106984
|
-
try {
|
|
106985
|
-
await section.items.remove(itemIdentifier);
|
|
106986
|
-
referenceRemoved = true;
|
|
106987
|
-
} catch (cleanupError) {
|
|
106988
|
-
referenceRemoved = isApiError(cleanupError) && cleanupError.statusCode === 404;
|
|
106989
|
-
addEvent("assessment.qti_question_reference_cleanup_failed", {
|
|
106990
|
-
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
106991
|
-
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
106992
|
-
"exception.type": errorType(cleanupError),
|
|
106993
|
-
"app.error.message": errorMessage(cleanupError)
|
|
106994
|
-
});
|
|
106995
|
-
}
|
|
106996
|
-
}
|
|
106997
|
-
if (itemCreationAttempted && !itemCreationDefinitelyRejected && referenceRemoved) {
|
|
106998
|
-
try {
|
|
106999
|
-
await client2.qtiApi.assessmentItems.delete(itemIdentifier);
|
|
107000
|
-
} catch (cleanupError) {
|
|
107001
|
-
addEvent("assessment.qti_question_cleanup_failed", {
|
|
107002
|
-
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107003
|
-
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
107004
|
-
"exception.type": errorType(cleanupError),
|
|
107005
|
-
"app.error.message": errorMessage(cleanupError)
|
|
107006
|
-
});
|
|
107007
|
-
}
|
|
107008
|
-
}
|
|
107009
|
-
throw error88;
|
|
107010
|
-
}
|
|
107092
|
+
const created = await this.createQuestionInOwnedTest({
|
|
107093
|
+
client: client2,
|
|
107094
|
+
integrationId,
|
|
107095
|
+
qtiTestIdentifier,
|
|
107096
|
+
test: ownership.test,
|
|
107097
|
+
gameSlug: ownership.gameSlug,
|
|
107098
|
+
creation
|
|
107099
|
+
});
|
|
107011
107100
|
setAttribute("app.assessment.operation", creation.kind === "copy" ? "copy_question" : "create_question");
|
|
107012
|
-
return
|
|
107013
|
-
|
|
107014
|
-
|
|
107015
|
-
|
|
107016
|
-
|
|
107017
|
-
|
|
107018
|
-
|
|
107101
|
+
return created;
|
|
107102
|
+
});
|
|
107103
|
+
}
|
|
107104
|
+
async createQuestionInOwnedTest(input) {
|
|
107105
|
+
const { client: client2, integrationId, qtiTestIdentifier, creation } = input;
|
|
107106
|
+
const itemIdentifier = newPlaycademyQuestionIdentifier(qtiTestIdentifier);
|
|
107107
|
+
let title;
|
|
107108
|
+
let metadata2;
|
|
107109
|
+
let xml;
|
|
107110
|
+
let structuredInput;
|
|
107111
|
+
let fallbackItem;
|
|
107112
|
+
if (creation.kind === "copy") {
|
|
107113
|
+
const source = await client2.qtiApi.assessmentItems.get(creation.sourceItemIdentifier);
|
|
107114
|
+
const copy = buildQtiQuestionCopyInput({
|
|
107115
|
+
source,
|
|
107116
|
+
targetIdentifier: itemIdentifier,
|
|
107117
|
+
targetTestIdentifier: qtiTestIdentifier,
|
|
107118
|
+
gameSlug: input.gameSlug,
|
|
107119
|
+
integrationId
|
|
107120
|
+
});
|
|
107121
|
+
title = copy.title;
|
|
107122
|
+
metadata2 = copy.metadata;
|
|
107123
|
+
xml = copy.xml;
|
|
107124
|
+
fallbackItem = {
|
|
107125
|
+
...source,
|
|
107126
|
+
identifier: itemIdentifier,
|
|
107127
|
+
title,
|
|
107128
|
+
rawXml: xml,
|
|
107129
|
+
metadata: metadata2
|
|
107130
|
+
};
|
|
107131
|
+
} else {
|
|
107132
|
+
structuredInput = creation.input;
|
|
107133
|
+
assertSupportedQtiQuestionInteraction(structuredInput);
|
|
107134
|
+
title = typeof structuredInput.title === "string" ? structuredInput.title.trim() : "";
|
|
107135
|
+
metadata2 = buildOwnedQtiQuestionMetadata(structuredInput, {
|
|
107136
|
+
gameSlug: input.gameSlug,
|
|
107137
|
+
testIdentifier: qtiTestIdentifier
|
|
107138
|
+
});
|
|
107139
|
+
xml = buildQuestionXml(structuredInput, itemIdentifier, title);
|
|
107140
|
+
fallbackItem = {
|
|
107141
|
+
...structuredInput,
|
|
107142
|
+
identifier: itemIdentifier,
|
|
107143
|
+
title,
|
|
107144
|
+
...xml ? { rawXml: xml } : {}
|
|
107145
|
+
};
|
|
107146
|
+
}
|
|
107147
|
+
const section = await this.qtiSectionItems(client2, qtiTestIdentifier, undefined, undefined, input.test);
|
|
107148
|
+
const href = qtiItemHref(client2, itemIdentifier);
|
|
107149
|
+
let item;
|
|
107150
|
+
let itemCreationAttempted = false;
|
|
107151
|
+
let referenceCreationAttempted = false;
|
|
107152
|
+
try {
|
|
107153
|
+
itemCreationAttempted = true;
|
|
107154
|
+
item = xml ? await client2.course.createAssessmentItemXml({ xml, metadata: metadata2 }) : await client2.qtiApi.assessmentItems.create({
|
|
107155
|
+
...structuredInput,
|
|
107156
|
+
identifier: itemIdentifier,
|
|
107019
107157
|
metadata: metadata2
|
|
107020
107158
|
});
|
|
107159
|
+
referenceCreationAttempted = true;
|
|
107160
|
+
await section.items.add({
|
|
107161
|
+
identifier: itemIdentifier,
|
|
107162
|
+
href
|
|
107163
|
+
});
|
|
107164
|
+
} catch (error88) {
|
|
107165
|
+
let referenceRemoved = !referenceCreationAttempted;
|
|
107166
|
+
const creationStatus = isApiError(error88) ? error88.statusCode : undefined;
|
|
107167
|
+
const itemCreationDefinitelyRejected = !referenceCreationAttempted && typeof creationStatus === "number" && creationStatus >= 400 && creationStatus < 500 && creationStatus !== 408;
|
|
107168
|
+
if (referenceCreationAttempted) {
|
|
107169
|
+
try {
|
|
107170
|
+
await section.items.remove(itemIdentifier);
|
|
107171
|
+
referenceRemoved = true;
|
|
107172
|
+
} catch (cleanupError) {
|
|
107173
|
+
referenceRemoved = isApiError(cleanupError) && cleanupError.statusCode === 404;
|
|
107174
|
+
addEvent("assessment.qti_question_reference_cleanup_failed", {
|
|
107175
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107176
|
+
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
107177
|
+
"exception.type": errorType(cleanupError),
|
|
107178
|
+
"app.error.message": errorMessage(cleanupError)
|
|
107179
|
+
});
|
|
107180
|
+
}
|
|
107181
|
+
}
|
|
107182
|
+
if (itemCreationAttempted && !itemCreationDefinitelyRejected && referenceRemoved) {
|
|
107183
|
+
try {
|
|
107184
|
+
await client2.qtiApi.assessmentItems.delete(itemIdentifier);
|
|
107185
|
+
} catch (cleanupError) {
|
|
107186
|
+
addEvent("assessment.qti_question_cleanup_failed", {
|
|
107187
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107188
|
+
"app.assessment.qti_item_identifier": itemIdentifier,
|
|
107189
|
+
"exception.type": errorType(cleanupError),
|
|
107190
|
+
"app.error.message": errorMessage(cleanupError)
|
|
107191
|
+
});
|
|
107192
|
+
}
|
|
107193
|
+
}
|
|
107194
|
+
throw error88;
|
|
107195
|
+
}
|
|
107196
|
+
return buildCreatedQtiQuestionReference({
|
|
107197
|
+
item,
|
|
107198
|
+
fallbackItem,
|
|
107199
|
+
itemIdentifier,
|
|
107200
|
+
href,
|
|
107201
|
+
partIdentifier: section.partIdentifier,
|
|
107202
|
+
sectionIdentifier: section.sectionIdentifier,
|
|
107203
|
+
metadata: metadata2
|
|
107021
107204
|
});
|
|
107022
107205
|
}
|
|
107023
107206
|
async updateQuestion(integrationId, qtiTestIdentifier, itemIdentifier, input) {
|
|
@@ -107265,6 +107448,22 @@ class TimebackAssessmentsService {
|
|
|
107265
107448
|
}
|
|
107266
107449
|
return row;
|
|
107267
107450
|
}
|
|
107451
|
+
async requireCurrentRevisionRow(integrationId, qtiTestIdentifier) {
|
|
107452
|
+
const row = await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
107453
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.qtiTestIdentifier, qtiTestIdentifier))
|
|
107454
|
+
});
|
|
107455
|
+
if (row) {
|
|
107456
|
+
return row;
|
|
107457
|
+
}
|
|
107458
|
+
const assessmentKey = assessmentKeyFromManagedQtiIdentifier(qtiTestIdentifier);
|
|
107459
|
+
const successor = assessmentKey ? await this.deps.db.query.gameTimebackAssessmentTests.findFirst({
|
|
107460
|
+
where: and(eq(gameTimebackAssessmentTests.integrationId, integrationId), eq(gameTimebackAssessmentTests.assessmentKey, assessmentKey))
|
|
107461
|
+
}) : undefined;
|
|
107462
|
+
if (successor) {
|
|
107463
|
+
throw revisionConflict(qtiTestIdentifier, successor.qtiTestIdentifier);
|
|
107464
|
+
}
|
|
107465
|
+
throw new NotFoundError(`Assessment not found: ${qtiTestIdentifier}`);
|
|
107466
|
+
}
|
|
107268
107467
|
async requireDraftAssessmentRow(integrationId, qtiTestIdentifier, lockedRow) {
|
|
107269
107468
|
const row = lockedRow ?? await this.requireAssessmentRow(integrationId, qtiTestIdentifier);
|
|
107270
107469
|
assertDraftAssessment(row);
|
|
@@ -107412,9 +107611,21 @@ class TimebackAssessmentsService {
|
|
|
107412
107611
|
itemHref: (identifier) => qtiItemHref(client2, identifier)
|
|
107413
107612
|
});
|
|
107414
107613
|
const artifactSource = await this.ensureManagedQtiPublication(client2, plan);
|
|
107415
|
-
const artifactsAvailable = await this.storeManagedArtifacts(artifactSource, managedAssessmentDiagnosticRoutingManifest(artifactSource.test, plan.assessmentKey));
|
|
107614
|
+
const artifactsAvailable = await this.managedArtifactsStored(plan.qtiTestIdentifier) || await this.storeManagedArtifacts(artifactSource, managedAssessmentDiagnosticRoutingManifest(artifactSource.test, plan.assessmentKey));
|
|
107416
107615
|
const warning = assessmentArtifactPublicationWarning(artifactsAvailable);
|
|
107417
|
-
return { ...plan, ...warning ? { warning } : {} };
|
|
107616
|
+
return { ...plan, artifactsAvailable, ...warning ? { warning } : {} };
|
|
107617
|
+
}
|
|
107618
|
+
async managedArtifactsStored(qtiTestIdentifier) {
|
|
107619
|
+
try {
|
|
107620
|
+
return await this.deps.assessmentArtifacts.loadManagedPlayable(qtiTestIdentifier) !== null;
|
|
107621
|
+
} catch (error88) {
|
|
107622
|
+
addEvent("assessment.artifact_lookup_failed", {
|
|
107623
|
+
"app.assessment.qti_test_identifier": qtiTestIdentifier,
|
|
107624
|
+
"exception.type": errorType(error88),
|
|
107625
|
+
"app.error.message": errorMessage(error88)
|
|
107626
|
+
});
|
|
107627
|
+
return false;
|
|
107628
|
+
}
|
|
107418
107629
|
}
|
|
107419
107630
|
async cleanupQtiAssessmentCopy(client2, testIdentifier, itemIdentifiers) {
|
|
107420
107631
|
let testDeleted = !testIdentifier;
|
|
@@ -107541,6 +107752,7 @@ var init_timeback_assessments_service = __esm(async () => {
|
|
|
107541
107752
|
init_timeback_assessment_diagnostic_util();
|
|
107542
107753
|
init_timeback_assessment_import_util();
|
|
107543
107754
|
init_timeback_assessment_publication_util();
|
|
107755
|
+
init_timeback_assessment_revision_util();
|
|
107544
107756
|
init_timeback_assessment_rules_util();
|
|
107545
107757
|
init_timeback_qti_authoring_util();
|
|
107546
107758
|
init_timeback_qti_hydration_util();
|
|
@@ -167550,7 +167762,7 @@ function parseQtiLibraryParams(searchParams) {
|
|
|
167550
167762
|
limit
|
|
167551
167763
|
};
|
|
167552
167764
|
}
|
|
167553
|
-
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, resumeRuntimeAssessment, saveRuntimeAssessment, recordRuntimeAssessmentProgress, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
167765
|
+
var populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, getRemovedIntegrations, createIntegration, updateIntegration, deactivateCourse, reactivateCourse, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, prepareRuntimeAssessment, startRuntimeAssessment, getRuntimeAssessment, getLatestRuntimeAssessment, resumeRuntimeAssessment, saveRuntimeAssessment, recordRuntimeAssessmentProgress, submitRuntimeAssessmentItem, submitRuntimeAssessment, finalizeRuntimeAssessment, exportRuntimeAssessmentFixture, getStudentXp, getStudentMastery, getStudentHighestGradeMastered, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getGradeLevelTestResults, getGradeLevelTestReview, getActivityDetail, listMetricDiscrepancies, verifyMetricDiscrepancy, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, attachExistingAssessments, updateAssessment, reviseAssessment, synchronizeReviewBank, reorderQuestions, removeAssessment, listQuestions, listQuestionLibrary, listTestLibrary, copyAssessment, createQuestion, updateQuestion, removeQuestion, timeback2;
|
|
167554
167766
|
var init_timeback_controller = __esm(() => {
|
|
167555
167767
|
init_esm();
|
|
167556
167768
|
init_src();
|
|
@@ -168295,6 +168507,20 @@ var init_timeback_controller = __esm(() => {
|
|
|
168295
168507
|
const body2 = await parseRequestBody(ctx.request, UpdateAssessmentRequestSchema);
|
|
168296
168508
|
return ctx.services.timebackAssessments.updateAssessment(integrationId, testIdentifier, body2);
|
|
168297
168509
|
});
|
|
168510
|
+
reviseAssessment = requireDeveloper(async (ctx) => {
|
|
168511
|
+
const { gameId, courseId } = ctx.params;
|
|
168512
|
+
if (!gameId || !courseId) {
|
|
168513
|
+
throw ApiError.badRequest("Missing gameId or courseId parameter");
|
|
168514
|
+
}
|
|
168515
|
+
const body2 = await parseRequestBody(ctx.request, ReviseAssessmentRequestSchema);
|
|
168516
|
+
const integrationId = await ctx.services.timebackAssessments.resolveIntegrationId(gameId, courseId, ctx.user);
|
|
168517
|
+
return ctx.services.timebackAssessments.reviseAssessment(integrationId, {
|
|
168518
|
+
currentQtiTestIdentifier: body2.currentQtiTestIdentifier,
|
|
168519
|
+
sourceTestIdentifier: newPlaycademyTestIdentifier(),
|
|
168520
|
+
title: body2.title,
|
|
168521
|
+
questions: body2.questions
|
|
168522
|
+
});
|
|
168523
|
+
});
|
|
168298
168524
|
synchronizeReviewBank = requireDeveloper(async (ctx) => {
|
|
168299
168525
|
const { gameId, courseId } = ctx.params;
|
|
168300
168526
|
if (!gameId || !courseId) {
|
|
@@ -168445,6 +168671,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
168445
168671
|
createAssessment,
|
|
168446
168672
|
attachExistingAssessments,
|
|
168447
168673
|
updateAssessment,
|
|
168674
|
+
reviseAssessment,
|
|
168448
168675
|
synchronizeReviewBank,
|
|
168449
168676
|
removeAssessment,
|
|
168450
168677
|
reorderQuestions,
|