Skip to content

Commit 8ee342b

Browse files
committed
Add migration script to/from legacy history to generic history
1 parent c1613c6 commit 8ee342b

File tree

4 files changed

+180
-2
lines changed

4 files changed

+180
-2
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* eslint-disable @typescript-eslint/no-require-imports */
2+
const convertObsoleteHistoryToGenericHistory =
3+
require("../dist/datasets/utils/history.util").convertObsoleteHistoryToGenericHistory;
4+
5+
const convertGenericHistoriesToObsoleteHistories =
6+
require("../dist/datasets/utils/history.util").convertGenericHistoriesToObsoleteHistories;
7+
8+
module.exports = {
9+
/**
10+
* @param db {import('mongodb').Db}
11+
* @param client {import('mongodb').MongoClient}
12+
* @returns {Promise<void>}
13+
*/
14+
async up(db, client) {
15+
for await (const dataset of db
16+
.collection("Dataset")
17+
.find({ history: { $exists: true, $type: "array", $ne: [] } })) {
18+
console.log(
19+
`Migrating history for dataset ${dataset._id}. Entries: ${dataset.history.length}`,
20+
);
21+
dataset.history.forEach(async (entry) => {
22+
const genericHistory = convertObsoleteHistoryToGenericHistory(
23+
entry,
24+
dataset._id,
25+
);
26+
console.log(`Inserting history entry for dataset ${dataset._id}`);
27+
result = await db.collection("History").insertOne(genericHistory);
28+
});
29+
}
30+
await db.collection("Dataset").updateMany({}, { $unset: { history: "" } });
31+
},
32+
33+
/**
34+
* @param db {import('mongodb').Db}
35+
* @param client {import('mongodb').MongoClient}
36+
* @returns {Promise<void>}
37+
*/
38+
async down(db, client) {
39+
for await (const dataset of db.collection("Dataset").find({})) {
40+
console.log(`Rolling back history for dataset ${dataset._id}`);
41+
const genericHistories = await db
42+
.collection("History")
43+
.find(
44+
{ documentId: dataset._id, subsystem: "Dataset" },
45+
{ sort: { timestamp: "desc" } },
46+
)
47+
.toArray();
48+
console.log(
49+
`Found ${genericHistories.length} history entries for dataset ${dataset._id}`,
50+
);
51+
dataset.$clone = () => dataset; // Mock the $clone method, as this is not a mongoose document
52+
const obsoleteHistories = convertGenericHistoriesToObsoleteHistories(
53+
genericHistories,
54+
dataset,
55+
);
56+
console.log(
57+
`Inserting obsolete history entries for dataset ${dataset._id}`,
58+
);
59+
await db
60+
.collection("Dataset")
61+
.updateOne(
62+
{ _id: dataset._id },
63+
{ $set: { history: obsoleteHistories } },
64+
);
65+
}
66+
},
67+
};

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
"build": "nest build",
1212
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
1313
"format:check": "prettier --check \"src/**/*.ts\" \"test/**/*.ts\"",
14-
"migrate:db:up": "node ./node_modules/migrate-mongo/bin/migrate-mongo.js up",
15-
"migrate:db:down": "node ./node_modules/migrate-mongo/bin/migrate-mongo.js down",
14+
"migrate:db:up": "npm run build && node ./node_modules/migrate-mongo/bin/migrate-mongo.js up",
15+
"migrate:db:down": "npm run build && node ./node_modules/migrate-mongo/bin/migrate-mongo.js down",
1616
"migrate:db:status": "node ./node_modules/migrate-mongo/bin/migrate-mongo.js status",
1717
"start": "nest start",
1818
"start:dev": "nest start --watch",

src/datasets/utils/history.util.spec.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { HistoryClass } from "src/datasets/schemas/history.schema";
12
import {
23
convertGenericHistoriesToObsoleteHistories,
34
convertGenericHistoryToObsoleteHistory,
5+
convertObsoleteHistoryToGenericHistory,
46
} from "./history.util";
57
import {
68
DatasetClass,
@@ -9,6 +11,63 @@ import {
911
import { GenericHistory } from "../../common/schemas/generic-history.schema";
1012

1113
describe("History Utility Functions", () => {
14+
it("should convert obsolete history to generic history", () => {
15+
const obsoleteHistory: HistoryClass = {
16+
updatedAt: new Date("2023-10-01T12:00:00Z"),
17+
updatedBy: "user123",
18+
isPublished: {
19+
currentValue: true,
20+
previousValue: false,
21+
},
22+
datasetlifecycle: {
23+
currentValue: {
24+
publishedOn: new Date("2023-10-01T12:00:00Z"),
25+
archivable: true,
26+
retrievable: true,
27+
},
28+
previousValue: {
29+
archivable: false,
30+
retrievable: true,
31+
publishable: false,
32+
archiveRetentionTime: new Date("2031-10-01T12:00:00Z"),
33+
dateOfPublishing: new Date("2024-10-01T12:00:00Z"),
34+
isOnCentralDisk: true,
35+
archiveStatusMessage: "datasetOnArchiveDisk",
36+
retrieveStatusMessage: "",
37+
retrieveIntegrityCheck: false,
38+
},
39+
},
40+
_id: "",
41+
};
42+
const documentId = "pid123";
43+
const genericHistory = convertObsoleteHistoryToGenericHistory(
44+
obsoleteHistory,
45+
documentId,
46+
);
47+
48+
expect(genericHistory).toEqual({
49+
subsystem: "Dataset",
50+
documentId: "pid123",
51+
user: "user123",
52+
operation: "update",
53+
timestamp: new Date("2023-10-01T12:00:00Z"),
54+
before: {
55+
isPublished: false,
56+
datasetlifecycle: {
57+
publishedOn: undefined,
58+
archivable: false,
59+
},
60+
},
61+
after: {
62+
datasetlifecycle: {
63+
publishedOn: new Date("2023-10-01T12:00:00Z"),
64+
archivable: true,
65+
},
66+
isPublished: true,
67+
},
68+
});
69+
});
70+
1271
it("should convert generic history to obsolete history", () => {
1372
const genericHistory: GenericHistory = {
1473
subsystem: "Dataset",

src/datasets/utils/history.util.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,58 @@ import {
77

88
const IGNORE_FIELDS = ["updatedAt", "updatedBy", "_id"];
99

10+
export function convertObsoleteHistoryToGenericHistory(
11+
history: HistoryClass,
12+
documentId: string,
13+
): GenericHistory {
14+
const result: GenericHistory = {
15+
subsystem: "Dataset",
16+
documentId: documentId,
17+
user: history.updatedBy,
18+
operation: "update",
19+
timestamp: history.updatedAt,
20+
before: {},
21+
after: {},
22+
};
23+
const changeList = Object.keys(history).filter(
24+
(key) => !IGNORE_FIELDS.includes(key),
25+
);
26+
for (const key of changeList) {
27+
if (
28+
!history[key] ||
29+
!history[key].hasOwnProperty("previousValue") ||
30+
!history[key].hasOwnProperty("currentValue")
31+
) {
32+
continue;
33+
}
34+
const fieldChange = history[key] as {
35+
previousValue: unknown;
36+
currentValue: unknown;
37+
};
38+
if (key === "datasetlifecycle") {
39+
const currentValue = fieldChange.currentValue as Record<string, unknown>;
40+
const previousValue = fieldChange.previousValue as Record<
41+
string,
42+
unknown
43+
>;
44+
// only retain the intersection of keys in currentValue and previousValue and whose value has changed. drop all others
45+
const prunedPreviousValue: Record<string, unknown> = {};
46+
const prunedCurrentValue: Record<string, unknown> = {};
47+
for (const subKey of Object.keys(currentValue)) {
48+
if (currentValue[subKey] !== previousValue[subKey]) {
49+
prunedPreviousValue[subKey] = previousValue[subKey];
50+
prunedCurrentValue[subKey] = currentValue[subKey];
51+
}
52+
}
53+
fieldChange.previousValue = prunedPreviousValue;
54+
fieldChange.currentValue = prunedCurrentValue;
55+
}
56+
result.before![key] = fieldChange.previousValue;
57+
result.after![key] = fieldChange.currentValue;
58+
}
59+
return result;
60+
}
61+
1062
// Given a dataset snapshot and a history entry, reconstruct the obsolete history entry
1163
export function convertGenericHistoryToObsoleteHistory(
1264
history: GenericHistory,

0 commit comments

Comments
 (0)