adapt-authoring-content 3.0.4 → 3.0.6
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/lib/ContentModule.js +0 -14
- package/migrations/3.0.5.js +40 -0
- package/package.json +1 -1
- package/tests/ContentModule.spec.js +12 -15
package/lib/ContentModule.js
CHANGED
|
@@ -460,19 +460,6 @@ class ContentModule extends AbstractApiModule {
|
|
|
460
460
|
friendlyIds.set(_type, { ids: await this.generateFriendlyIds(_type, newCourseId, count), next: 0 })
|
|
461
461
|
}))
|
|
462
462
|
|
|
463
|
-
// Pre-allocate sequential _trackingId for cloned blocks. Bulk insertMany
|
|
464
|
-
// defeats SpoorTrackingModule's preInsertHook (which reads the current max
|
|
465
|
-
// from the DB per-block), so without this every cloned block would get the
|
|
466
|
-
// same id.
|
|
467
|
-
const blockCount = typeCounts.get('block') ?? 0
|
|
468
|
-
let nextTrackingId
|
|
469
|
-
if (blockCount > 0) {
|
|
470
|
-
const [{ _trackingId: maxTrackingId = 0 } = {}] = await this.find(
|
|
471
|
-
{ _courseId: newCourseId }, {}, { limit: 1, sort: [['_trackingId', -1]] }
|
|
472
|
-
)
|
|
473
|
-
nextTrackingId = maxTrackingId + 1
|
|
474
|
-
}
|
|
475
|
-
|
|
476
463
|
// Build all insert payloads with pre-mapped IDs and parent references
|
|
477
464
|
const rootId = _id.toString()
|
|
478
465
|
const payloads = allItems.map(item => {
|
|
@@ -497,7 +484,6 @@ class ContentModule extends AbstractApiModule {
|
|
|
497
484
|
return stringifyValues({
|
|
498
485
|
...item,
|
|
499
486
|
_id: newId,
|
|
500
|
-
_trackingId: item._type === 'block' ? nextTrackingId++ : undefined,
|
|
501
487
|
_friendlyId: friendlyId,
|
|
502
488
|
_courseId: isCourse ? newId.toString() : newCourseId,
|
|
503
489
|
_parentId: newParentId,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ObjectId } from 'mongodb'
|
|
2
|
+
|
|
3
|
+
export default function (migration) {
|
|
4
|
+
migration.describe('Normalise content._shareWithUsers entries to ObjectId form')
|
|
5
|
+
migration.runCommand(normaliseShareWithUsers)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async function normaliseShareWithUsers (db, log) {
|
|
9
|
+
const content = db.collection('content')
|
|
10
|
+
const docs = await content
|
|
11
|
+
.find({ _shareWithUsers: { $elemMatch: { $type: 'string' } } }, { projection: { _id: 1, _shareWithUsers: 1 } })
|
|
12
|
+
.toArray()
|
|
13
|
+
if (!docs.length) {
|
|
14
|
+
log('info', 'migrations', 'No content documents with string _shareWithUsers entries, skipping')
|
|
15
|
+
return
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ops = []
|
|
19
|
+
let unconvertible = 0
|
|
20
|
+
for (const doc of docs) {
|
|
21
|
+
const next = []
|
|
22
|
+
for (const u of doc._shareWithUsers) {
|
|
23
|
+
if (u instanceof ObjectId) {
|
|
24
|
+
next.push(u)
|
|
25
|
+
} else if (typeof u === 'string' && ObjectId.isValid(u)) {
|
|
26
|
+
next.push(new ObjectId(u))
|
|
27
|
+
} else {
|
|
28
|
+
next.push(u)
|
|
29
|
+
unconvertible++
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
ops.push({ updateOne: { filter: { _id: doc._id }, update: { $set: { _shareWithUsers: next } } } })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (let i = 0; i < ops.length; i += 500) {
|
|
36
|
+
await content.bulkWrite(ops.slice(i, i + 500), { ordered: false })
|
|
37
|
+
}
|
|
38
|
+
log('info', 'migrations', `normalised _shareWithUsers on ${docs.length} content document(s)`)
|
|
39
|
+
if (unconvertible) log('warn', 'migrations', `${unconvertible} _shareWithUsers entr(ies) could not be coerced to ObjectId and were left as-is`)
|
|
40
|
+
}
|
package/package.json
CHANGED
|
@@ -517,15 +517,14 @@ describe('ContentModule', () => {
|
|
|
517
517
|
assert.ok(inst.postInsertHook.invoke.mock.callCount() > 0)
|
|
518
518
|
})
|
|
519
519
|
|
|
520
|
-
it('should
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
//
|
|
520
|
+
it('should delegate _trackingId assignment to preInsertHook (clone no longer allocates them)', async () => {
|
|
521
|
+
// Tracking IDs are owned by the spoortracking module, which taps preInsertHook. clone must
|
|
522
|
+
// fire that hook once per payload (so each cloned block can be assigned an id) and must not
|
|
523
|
+
// assign _trackingId itself. With no observer attached here, payloads pass through untouched.
|
|
524
524
|
const BLOCK2_OID = '507f1f77bcf86cd79943a001'
|
|
525
525
|
const BLOCK3_OID = '507f1f77bcf86cd79943a002'
|
|
526
526
|
|
|
527
527
|
const { inst, mongodb } = createCloneInstance()
|
|
528
|
-
inst.find = mock.fn(async () => [{ _trackingId: 7 }]) // existing max in course
|
|
529
528
|
|
|
530
529
|
const items = [
|
|
531
530
|
{ _id: COURSE_OID, _type: 'course', _courseId: COURSE_OID },
|
|
@@ -539,18 +538,16 @@ describe('ContentModule', () => {
|
|
|
539
538
|
const parent = { _id: COURSE_OID, _type: 'course', _courseId: COURSE_OID }
|
|
540
539
|
await ContentModule.prototype.clone.call(inst, USER_OID, PAGE_OID, COURSE_OID, {}, { tree, parent })
|
|
541
540
|
|
|
541
|
+
// preInsertHook fired once per cloned payload — the seam the spoortracking observer uses.
|
|
542
|
+
// Cloning the page clones page + article + 3 blocks (5 items); the course is the source.
|
|
543
|
+
assert.equal(inst.preInsertHook.invoke.mock.callCount(), 5)
|
|
544
|
+
// every payload passed to the hook is a block/page/etc, and a block payload is present
|
|
545
|
+
const hookedTypes = inst.preInsertHook.invoke.mock.calls.map(c => c.arguments[0]._type)
|
|
546
|
+
assert.ok(hookedTypes.includes('block'))
|
|
547
|
+
// clone itself assigned nothing — block payloads still carry the (now irrelevant) source ids
|
|
542
548
|
const inserted = mongodb.collection.insertMany.mock.calls[0].arguments[0]
|
|
543
549
|
const blockTrackingIds = inserted.filter(d => d._type === 'block').map(d => d._trackingId)
|
|
544
|
-
assert.
|
|
545
|
-
for (const id of blockTrackingIds) {
|
|
546
|
-
assert.equal(typeof id, 'number', `block cloned without numeric _trackingId (got ${id})`)
|
|
547
|
-
}
|
|
548
|
-
// all distinct
|
|
549
|
-
assert.equal(new Set(blockTrackingIds).size, blockTrackingIds.length, 'duplicate _trackingId among cloned blocks')
|
|
550
|
-
// and continuing past the existing max
|
|
551
|
-
for (const id of blockTrackingIds) {
|
|
552
|
-
assert.ok(id > 7, `expected _trackingId > existing max (7), got ${id}`)
|
|
553
|
-
}
|
|
550
|
+
assert.deepEqual(blockTrackingIds.sort(), [5, 6, 7])
|
|
554
551
|
})
|
|
555
552
|
})
|
|
556
553
|
|