Skip to content

Commit f3366d8

Browse files
authored
Merge pull request #647 from codex-team/feat/events-multiselect-bulk-actions
Feat/events multiselect bulk actions
2 parents 29a4b17 + 0ee4684 commit f3366d8

13 files changed

Lines changed: 270 additions & 345 deletions

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "hawk.api",
3-
"version": "1.5.0",
3+
"version": "1.5.1",
44
"main": "index.ts",
55
"license": "BUSL-1.1",
66
"scripts": {

src/models/eventsFactory.js

Lines changed: 121 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,7 @@ class EventsFactory extends Factory {
887887
*
888888
* @param {string[]} eventIds - original event ids
889889
* @param {string|ObjectId} userId - id of the user who is visiting events
890-
* @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
890+
* @returns {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
891891
*/
892892
async bulkVisitEvents(eventIds, userId) {
893893
const {
@@ -902,25 +902,53 @@ class EventsFactory extends Factory {
902902

903903
return !visitedBy.some((visitedUserId) => String(visitedUserId) === userIdStr);
904904
});
905-
const updatedEventIds = docsToUpdate.map(doc => doc._id.toString());
906905

907906
if (docsToUpdate.length === 0) {
908907
return {
909-
updatedCount: 0,
910908
updatedEventIds: [],
911909
failedEventIds,
912910
};
913911
}
914912

915-
const updateManyResult = await collection.updateMany(
916-
{ _id: { $in: docsToUpdate.map(doc => doc._id) } },
917-
{ $addToSet: { visitedBy: new ObjectId(userId) } }
913+
const userObjectId = new ObjectId(userId);
914+
const settled = await Promise.allSettled(
915+
docsToUpdate.map(async (doc) => {
916+
const eventId = doc._id.toString();
917+
const updateResult = await collection.updateOne(
918+
{
919+
_id: doc._id,
920+
visitedBy: { $ne: userObjectId },
921+
},
922+
{ $addToSet: { visitedBy: userObjectId } }
923+
);
924+
925+
return {
926+
eventId,
927+
updated: updateResult.modifiedCount > 0,
928+
};
929+
})
918930
);
919931

932+
const updatedEventIds = [];
933+
const failedByUpdate = [];
934+
935+
settled.forEach((result, index) => {
936+
const fallbackEventId = docsToUpdate[index]._id.toString();
937+
938+
if (result.status === 'fulfilled') {
939+
if (result.value.updated) {
940+
updatedEventIds.push(result.value.eventId);
941+
} else {
942+
failedByUpdate.push(result.value.eventId);
943+
}
944+
} else {
945+
failedByUpdate.push(fallbackEventId);
946+
}
947+
});
948+
920949
return {
921-
updatedCount: updateManyResult.modifiedCount,
922950
updatedEventIds,
923-
failedEventIds,
951+
failedEventIds: this._mergeFailedEventIds(failedEventIds, failedByUpdate),
924952
};
925953
}
926954

@@ -965,7 +993,7 @@ class EventsFactory extends Factory {
965993
*
966994
* @param {string[]} eventIds - original event ids
967995
* @param {string} mark - 'resolved' | 'ignored' | 'starred'
968-
* @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
996+
* @returns {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
969997
*/
970998
async bulkToggleEventMark(eventIds, mark) {
971999
const {
@@ -978,7 +1006,6 @@ class EventsFactory extends Factory {
9781006
const markKey = `marks.${mark}`;
9791007
const allHaveMark = found.length > 0 && found.every(doc => doc.marks && doc.marks[mark]);
9801008
const ops = [];
981-
const updatedEventIds = [];
9821009

9831010
for (const doc of found) {
9841011
const hasMark = doc.marks && doc.marks[mark];
@@ -998,23 +1025,50 @@ class EventsFactory extends Factory {
9981025
update,
9991026
},
10001027
});
1001-
updatedEventIds.push(doc._id.toString());
10021028
}
10031029

10041030
if (ops.length === 0) {
10051031
return {
1006-
updatedCount: 0,
10071032
updatedEventIds: [],
10081033
failedEventIds,
10091034
};
10101035
}
10111036

1012-
const bulkResult = await collection.bulkWrite(ops, { ordered: false });
1037+
const settled = await Promise.allSettled(
1038+
ops.map(async ({ updateOne }) => {
1039+
const eventId = updateOne.filter._id.toString();
1040+
const updateResult = await collection.updateOne(
1041+
updateOne.filter,
1042+
updateOne.update
1043+
);
1044+
1045+
return {
1046+
eventId,
1047+
updated: updateResult.modifiedCount > 0,
1048+
};
1049+
})
1050+
);
1051+
1052+
const updatedEventIds = [];
1053+
const failedByUpdate = [];
1054+
1055+
settled.forEach((result, index) => {
1056+
const fallbackEventId = ops[index].updateOne.filter._id.toString();
1057+
1058+
if (result.status === 'fulfilled') {
1059+
if (result.value.updated) {
1060+
updatedEventIds.push(result.value.eventId);
1061+
} else {
1062+
failedByUpdate.push(result.value.eventId);
1063+
}
1064+
} else {
1065+
failedByUpdate.push(fallbackEventId);
1066+
}
1067+
});
10131068

10141069
return {
1015-
updatedCount: bulkResult.modifiedCount + bulkResult.upsertedCount,
10161070
updatedEventIds,
1017-
failedEventIds,
1071+
failedEventIds: this._mergeFailedEventIds(failedEventIds, failedByUpdate),
10181072
};
10191073
}
10201074

@@ -1023,7 +1077,7 @@ class EventsFactory extends Factory {
10231077
*
10241078
* @param {string[]} eventIds - original event ids
10251079
* @param {string|null|undefined} assignee - target assignee id, null/undefined to clear
1026-
* @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
1080+
* @returns {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
10271081
*/
10281082
async bulkUpdateAssignee(eventIds, assignee) {
10291083
const {
@@ -1034,25 +1088,52 @@ class EventsFactory extends Factory {
10341088

10351089
const normalizedAssignee = assignee ? String(assignee) : '';
10361090
const docsToUpdate = found.filter(doc => String(doc.assignee || '') !== normalizedAssignee);
1037-
const updatedEventIds = docsToUpdate.map(doc => doc._id.toString());
10381091

10391092
if (docsToUpdate.length === 0) {
10401093
return {
1041-
updatedCount: 0,
10421094
updatedEventIds: [],
10431095
failedEventIds,
10441096
};
10451097
}
10461098

1047-
const updateManyResult = await collection.updateMany(
1048-
{ _id: { $in: docsToUpdate.map(doc => doc._id) } },
1049-
{ $set: { assignee: normalizedAssignee } }
1099+
const settled = await Promise.allSettled(
1100+
docsToUpdate.map(async (doc) => {
1101+
const eventId = doc._id.toString();
1102+
const updateResult = await collection.updateOne(
1103+
{
1104+
_id: doc._id,
1105+
assignee: { $ne: normalizedAssignee },
1106+
},
1107+
{ $set: { assignee: normalizedAssignee } }
1108+
);
1109+
1110+
return {
1111+
eventId,
1112+
updated: updateResult.modifiedCount > 0,
1113+
};
1114+
})
10501115
);
10511116

1117+
const updatedEventIds = [];
1118+
const failedByUpdate = [];
1119+
1120+
settled.forEach((result, index) => {
1121+
const fallbackEventId = docsToUpdate[index]._id.toString();
1122+
1123+
if (result.status === 'fulfilled') {
1124+
if (result.value.updated) {
1125+
updatedEventIds.push(result.value.eventId);
1126+
} else {
1127+
failedByUpdate.push(result.value.eventId);
1128+
}
1129+
} else {
1130+
failedByUpdate.push(fallbackEventId);
1131+
}
1132+
});
1133+
10521134
return {
1053-
updatedCount: updateManyResult.modifiedCount,
10541135
updatedEventIds,
1055-
failedEventIds,
1136+
failedEventIds: this._mergeFailedEventIds(failedEventIds, failedByUpdate),
10561137
};
10571138
}
10581139

@@ -1127,6 +1208,23 @@ class EventsFactory extends Factory {
11271208
};
11281209
}
11291210

1211+
/**
1212+
* Merge two failed ids collections preserving uniqueness.
1213+
*
1214+
* @param {string[]} baseFailedEventIds - existing failed ids
1215+
* @param {string[]} extraFailedEventIds - failed ids collected from update results
1216+
* @returns {string[]}
1217+
*/
1218+
_mergeFailedEventIds(baseFailedEventIds, extraFailedEventIds) {
1219+
const mergedFailedEventIds = new Set(baseFailedEventIds);
1220+
1221+
extraFailedEventIds.forEach((eventId) => {
1222+
mergedFailedEventIds.add(eventId);
1223+
});
1224+
1225+
return Array.from(mergedFailedEventIds);
1226+
}
1227+
11301228
/**
11311229
* Compose event with repetition
11321230
*

src/resolvers/event.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
const getEventsFactory = require('./helpers/eventsFactory').default;
22
const {
3-
fireAndForgetAssigneeNotifications,
43
parseBulkEventIds,
5-
mergeFailedEventIds,
6-
} = require('./helpers/bulkEvents');
4+
enqueueAssigneeNotification,
5+
} = require('./helpers/bulkEventUtils');
76
const { aiService } = require('../services/ai');
87
const { UserInputError } = require('apollo-server-express');
98
const { ObjectId } = require('mongodb');
@@ -148,25 +147,25 @@ module.exports = {
148147
* @param {string} projectId - project id
149148
* @param {string[]} eventIds - original event ids
150149
* @param {UserInContext} user - user context
151-
* @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
150+
* @returns {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
152151
*/
153152
async bulkVisitEvents(_obj, { projectId, eventIds }, { user, ...context }) {
154153
const { validEventIds, invalidEventIds } = parseBulkEventIds(eventIds);
155154

156155
if (validEventIds.length === 0) {
157156
return {
158-
updatedCount: 0,
159157
updatedEventIds: [],
160158
failedEventIds: invalidEventIds,
161159
};
162160
}
163161

164162
const factory = getEventsFactory(context, projectId);
165163
const result = await factory.bulkVisitEvents(validEventIds, user.id);
164+
const failedEventIds = Array.from(new Set([...(result.failedEventIds || []), ...invalidEventIds]));
166165

167166
return {
168167
...result,
169-
failedEventIds: mergeFailedEventIds(result, invalidEventIds),
168+
failedEventIds,
170169
};
171170
},
172171

@@ -196,25 +195,25 @@ module.exports = {
196195
* @param {string[]} eventIds - original event ids
197196
* @param {string} mark - EventMark enum value
198197
* @param {object} context - gql context
199-
* @return {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
198+
* @return {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
200199
*/
201200
async bulkToggleEventMarks(_obj, { projectId, eventIds, mark }, context) {
202201
const { validEventIds, invalidEventIds } = parseBulkEventIds(eventIds);
203202

204203
if (validEventIds.length === 0) {
205204
return {
206-
updatedCount: 0,
207205
updatedEventIds: [],
208206
failedEventIds: invalidEventIds,
209207
};
210208
}
211209

212210
const factory = getEventsFactory(context, projectId);
213211
const result = await factory.bulkToggleEventMark(validEventIds, mark);
212+
const failedEventIds = Array.from(new Set([...(result.failedEventIds || []), ...invalidEventIds]));
214213

215214
return {
216215
...result,
217-
failedEventIds: mergeFailedEventIds(result, invalidEventIds),
216+
failedEventIds,
218217
};
219218
},
220219

@@ -261,12 +260,12 @@ module.exports = {
261260

262261
const assigneeData = await factories.usersFactory.dataLoaders.userById.load(assignee);
263262

264-
fireAndForgetAssigneeNotifications({
263+
enqueueAssigneeNotification({
265264
assigneeData,
266-
eventIds: [ eventId ],
267-
projectId,
268265
assigneeId: assignee,
266+
projectId,
269267
whoAssignedId: user.id,
268+
eventId,
270269
});
271270

272271
return {
@@ -300,7 +299,7 @@ module.exports = {
300299
* @param {ResolverObj} _obj - resolver context
301300
* @param {BulkUpdateAssigneeInput} input - object of arguments
302301
* @param factories - factories for working with models
303-
* @return {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
302+
* @return {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
304303
*/
305304
async bulkUpdateAssignee(_obj, { input }, { factories, user, ...context }) {
306305
const { projectId, eventIds, assignee } = input;
@@ -309,7 +308,6 @@ module.exports = {
309308

310309
if (validEventIds.length === 0) {
311310
return {
312-
updatedCount: 0,
313311
updatedEventIds: [],
314312
failedEventIds: invalidEventIds,
315313
};
@@ -342,16 +340,18 @@ module.exports = {
342340
const result = await factory.bulkUpdateAssignee(validEventIds, assignee);
343341
const resultWithInvalid = {
344342
...result,
345-
failedEventIds: mergeFailedEventIds(result, invalidEventIds),
343+
failedEventIds: Array.from(new Set([...(result.failedEventIds || []), ...invalidEventIds])),
346344
};
347345

348346
if (assignee && resultWithInvalid.updatedEventIds.length > 0) {
349-
fireAndForgetAssigneeNotifications({
350-
assigneeData,
351-
eventIds: resultWithInvalid.updatedEventIds,
352-
projectId,
353-
assigneeId: assignee,
354-
whoAssignedId: user.id,
347+
resultWithInvalid.updatedEventIds.forEach((eventId) => {
348+
enqueueAssigneeNotification({
349+
assigneeData,
350+
assigneeId: assignee,
351+
projectId,
352+
whoAssignedId: user.id,
353+
eventId,
354+
});
355355
});
356356
}
357357

0 commit comments

Comments
 (0)