Skip to content

Commit 869a2d6

Browse files
committed
imp(): add convertor script
1 parent e837f20 commit 869a2d6

4 files changed

Lines changed: 184 additions & 165 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
require('dotenv').config();
2+
const { MongoClient } = require('mongodb');
3+
4+
async function movePayloadTimestampToEventLevel(db, collectionName) {
5+
const collection = db.collection(collectionName);
6+
const cursor = collection.find({ 'payload.timestamp': { $exists: true } }).batchSize(500);
7+
8+
let processed = 0;
9+
let updated = 0;
10+
let bulkOps = [];
11+
12+
for await (const doc of cursor) {
13+
const timestamp = Number(doc.payload?.timestamp);
14+
if (isNaN(timestamp)) continue;
15+
16+
bulkOps.push({
17+
updateOne: {
18+
filter: { _id: doc._id },
19+
update: {
20+
$set: { timestamp: timestamp },
21+
$unset: { 'payload.timestamp': '' },
22+
},
23+
},
24+
});
25+
26+
if (bulkOps.length === 1000) {
27+
const result = await collection.bulkWrite(bulkOps);
28+
updated += result.modifiedCount;
29+
processed += bulkOps.length;
30+
console.log(` Flushed 1000 updates (${processed} processed, ${updated} updated)`);
31+
bulkOps = [];
32+
}
33+
}
34+
35+
if (bulkOps.length > 0) {
36+
const result = await collection.bulkWrite(bulkOps);
37+
updated += result.modifiedCount;
38+
processed += bulkOps.length;
39+
console.log(` Flushed final ${bulkOps.length} updates (${processed} processed, ${updated} updated)`);
40+
}
41+
42+
console.log(` Done with ${collectionName}: ${updated} documents updated`);
43+
}
44+
45+
async function backfillTimestampsFromEvents(db, repetitionCollectionName, projectId) {
46+
const collection = db.collection(repetitionCollectionName);
47+
48+
const pipeline = [
49+
{
50+
$match: {
51+
$or: [
52+
{ 'payload.timestamp': { $exists: false } },
53+
{ payload: { $exists: false } },
54+
],
55+
timestamp: { $exists: false },
56+
groupHash: { $exists: true },
57+
},
58+
},
59+
{
60+
$lookup: {
61+
from: `events:${projectId}`,
62+
localField: 'groupHash',
63+
foreignField: 'groupHash',
64+
as: 'eventData',
65+
},
66+
},
67+
{ $unwind: { path: '$eventData', preserveNullAndEmptyArrays: true } },
68+
{ $match: { 'eventData.timestamp': { $exists: true } } },
69+
{
70+
$project: {
71+
_id: 1,
72+
eventTimestamp: { $toDouble: '$eventData.timestamp' },
73+
},
74+
},
75+
];
76+
77+
const cursor = collection.aggregate(pipeline, { allowDiskUse: true }).batchSize(500);
78+
79+
let bulkOps = [];
80+
let processed = 0;
81+
let flushed = 0;
82+
83+
for await (const doc of cursor) {
84+
bulkOps.push({
85+
updateOne: {
86+
filter: { _id: doc._id },
87+
update: { $set: { timestamp: doc.eventTimestamp } },
88+
},
89+
});
90+
91+
if (bulkOps.length === 1000) {
92+
const res = await collection.bulkWrite(bulkOps);
93+
processed += res.modifiedCount;
94+
flushed++;
95+
bulkOps = [];
96+
}
97+
}
98+
99+
if (bulkOps.length > 0) {
100+
const res = await collection.bulkWrite(bulkOps);
101+
processed += res.modifiedCount;
102+
flushed++;
103+
}
104+
105+
console.log(` Done backfilling ${repetitionCollectionName}: ${processed} updated`);
106+
}
107+
108+
async function run() {
109+
const fullUri = process.env.MONGO_EVENTS_DATABASE_URI;
110+
111+
// Parse the Mongo URL manually
112+
const mongoUrl = new URL(fullUri);
113+
const databaseName = "hawk_events"
114+
115+
// Extract query parameters
116+
const queryParams = Object.fromEntries(mongoUrl.searchParams.entries());
117+
118+
// Compose connection options manually
119+
const options = {
120+
useNewUrlParser: true,
121+
useUnifiedTopology: true,
122+
authSource: queryParams.authSource || 'admin',
123+
replicaSet: queryParams.replicaSet || undefined,
124+
tls: queryParams.tls === 'true',
125+
tlsInsecure: queryParams.tlsInsecure === 'true',
126+
// connectTimeoutMS: 3600000,
127+
// socketTimeoutMS: 3600000,
128+
};
129+
130+
// Remove query string from URI
131+
mongoUrl.search = '';
132+
const cleanUri = mongoUrl.toString();
133+
134+
console.log('Connecting to:', cleanUri);
135+
console.log('With options:', options);
136+
137+
const client = new MongoClient(cleanUri, options);
138+
139+
140+
await client.connect();
141+
const db = client.db(databaseName);
142+
console.log(`Connected to database: ${databaseName}`);
143+
144+
const collections = await db.listCollections({}, {
145+
authorizedCollections: true,
146+
nameOnly: true,
147+
}).toArray();
148+
149+
const eventCollections = collections.filter(col => /^events:/.test(col.name)).map(col => col.name);
150+
const repetitionCollections = collections.filter(col => /^repetitions:/.test(col.name)).map(col => col.name);
151+
152+
console.log(`Found ${eventCollections.length} event collections.`);
153+
console.log(`Found ${repetitionCollections.length} repetition collections.`);
154+
155+
// Convert events
156+
let i = 1;
157+
for (const collectionName of eventCollections) {
158+
console.log(`[${i}/${eventCollections.length}] Processing ${collectionName}`);
159+
await movePayloadTimestampToEventLevel(db, collectionName);
160+
i++;
161+
}
162+
163+
// Convert repetitions + backfill from events
164+
i = 1;
165+
for (const collectionName of repetitionCollections) {
166+
console.log(`[${i}/${repetitionCollections.length}] Processing ${collectionName}`);
167+
const projectId = collectionName.split(':')[1];
168+
169+
await movePayloadTimestampToEventLevel(db, collectionName);
170+
await backfillTimestampsFromEvents(db, collectionName, projectId);
171+
172+
i++;
173+
}
174+
175+
await client.close();
176+
console.log('✅ Conversion complete.');
177+
}
178+
179+
run().catch(err => {
180+
console.error('❌ Script failed:', err);
181+
process.exit(1);
182+
});

migrations/20250707160120-move-timestamp-to-event-level.js

Lines changed: 0 additions & 162 deletions
This file was deleted.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"migration-add": "migrate-mongo create",
1515
"migrate:up": "migrate-mongo up",
1616
"migrate:down": "migrate-mongo down",
17+
"convert": "node ./convertors/move-timestamp-out-of-payload.js",
1718
"lint": "eslint -c ./.eslintrc.js --ext .ts,.js --fix .",
1819
"test": "jest --coverage --runInBand",
1920
"test:archiver": "jest workers/archiver",

workers/release/src/index.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,7 @@ export default class ReleaseWorker extends Worker {
234234
}, { session });
235235
});
236236
} catch (error) {
237-
this.logger.error('Can\'t extract release info:\n', {
238-
error,
239-
});
237+
this.logger.error(`Can\'t extract release info:\n${JSON.stringify(error)}`);
240238

241239
throw new NonCriticalError('Can\'t parse source-map file');
242240
} finally {

0 commit comments

Comments
 (0)