Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.5.0",
"version": "1.5.1",
"main": "index.ts",
"license": "BUSL-1.1",
"scripts": {
Expand Down
144 changes: 121 additions & 23 deletions src/models/eventsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -887,7 +887,7 @@ class EventsFactory extends Factory {
*
* @param {string[]} eventIds - original event ids
* @param {string|ObjectId} userId - id of the user who is visiting events
* @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
* @returns {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
*/
async bulkVisitEvents(eventIds, userId) {
const {
Expand All @@ -902,25 +902,53 @@ class EventsFactory extends Factory {

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

if (docsToUpdate.length === 0) {
return {
updatedCount: 0,
updatedEventIds: [],
failedEventIds,
};
}

const updateManyResult = await collection.updateMany(
{ _id: { $in: docsToUpdate.map(doc => doc._id) } },
{ $addToSet: { visitedBy: new ObjectId(userId) } }
const userObjectId = new ObjectId(userId);
const settled = await Promise.allSettled(
docsToUpdate.map(async (doc) => {
const eventId = doc._id.toString();
const updateResult = await collection.updateOne(
{
_id: doc._id,
visitedBy: { $ne: userObjectId },
},
{ $addToSet: { visitedBy: userObjectId } }
);

return {
eventId,
updated: updateResult.modifiedCount > 0,
};
})
);

const updatedEventIds = [];
const failedByUpdate = [];

settled.forEach((result, index) => {
const fallbackEventId = docsToUpdate[index]._id.toString();

if (result.status === 'fulfilled') {
if (result.value.updated) {
updatedEventIds.push(result.value.eventId);
} else {
failedByUpdate.push(result.value.eventId);
}
} else {
failedByUpdate.push(fallbackEventId);
}
});

return {
updatedCount: updateManyResult.modifiedCount,
updatedEventIds,
failedEventIds,
failedEventIds: this._mergeFailedEventIds(failedEventIds, failedByUpdate),
};
}

Expand Down Expand Up @@ -965,7 +993,7 @@ class EventsFactory extends Factory {
*
* @param {string[]} eventIds - original event ids
* @param {string} mark - 'resolved' | 'ignored' | 'starred'
* @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
* @returns {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
*/
async bulkToggleEventMark(eventIds, mark) {
const {
Expand All @@ -978,7 +1006,6 @@ class EventsFactory extends Factory {
const markKey = `marks.${mark}`;
const allHaveMark = found.length > 0 && found.every(doc => doc.marks && doc.marks[mark]);
const ops = [];
const updatedEventIds = [];

for (const doc of found) {
const hasMark = doc.marks && doc.marks[mark];
Expand All @@ -998,23 +1025,50 @@ class EventsFactory extends Factory {
update,
},
});
updatedEventIds.push(doc._id.toString());
}

if (ops.length === 0) {
return {
updatedCount: 0,
updatedEventIds: [],
failedEventIds,
};
}

const bulkResult = await collection.bulkWrite(ops, { ordered: false });
const settled = await Promise.allSettled(
ops.map(async ({ updateOne }) => {
const eventId = updateOne.filter._id.toString();
const updateResult = await collection.updateOne(
updateOne.filter,
updateOne.update
);

return {
eventId,
updated: updateResult.modifiedCount > 0,
};
})
);

const updatedEventIds = [];
const failedByUpdate = [];

settled.forEach((result, index) => {
const fallbackEventId = ops[index].updateOne.filter._id.toString();

if (result.status === 'fulfilled') {
if (result.value.updated) {
updatedEventIds.push(result.value.eventId);
} else {
failedByUpdate.push(result.value.eventId);
}
} else {
failedByUpdate.push(fallbackEventId);
}
});

return {
updatedCount: bulkResult.modifiedCount + bulkResult.upsertedCount,
updatedEventIds,
failedEventIds,
failedEventIds: this._mergeFailedEventIds(failedEventIds, failedByUpdate),
};
}

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

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

if (docsToUpdate.length === 0) {
return {
updatedCount: 0,
updatedEventIds: [],
failedEventIds,
};
}

const updateManyResult = await collection.updateMany(
{ _id: { $in: docsToUpdate.map(doc => doc._id) } },
{ $set: { assignee: normalizedAssignee } }
const settled = await Promise.allSettled(
docsToUpdate.map(async (doc) => {
const eventId = doc._id.toString();
const updateResult = await collection.updateOne(
{
_id: doc._id,
assignee: { $ne: normalizedAssignee },
},
{ $set: { assignee: normalizedAssignee } }
);

return {
eventId,
updated: updateResult.modifiedCount > 0,
};
})
);

const updatedEventIds = [];
const failedByUpdate = [];

settled.forEach((result, index) => {
const fallbackEventId = docsToUpdate[index]._id.toString();

if (result.status === 'fulfilled') {
if (result.value.updated) {
updatedEventIds.push(result.value.eventId);
} else {
failedByUpdate.push(result.value.eventId);
}
} else {
failedByUpdate.push(fallbackEventId);
}
});

return {
updatedCount: updateManyResult.modifiedCount,
updatedEventIds,
failedEventIds,
failedEventIds: this._mergeFailedEventIds(failedEventIds, failedByUpdate),
};
}

Expand Down Expand Up @@ -1127,6 +1208,23 @@ class EventsFactory extends Factory {
};
}

/**
* Merge two failed ids collections preserving uniqueness.
*
* @param {string[]} baseFailedEventIds - existing failed ids
* @param {string[]} extraFailedEventIds - failed ids collected from update results
* @returns {string[]}
*/
_mergeFailedEventIds(baseFailedEventIds, extraFailedEventIds) {
const mergedFailedEventIds = new Set(baseFailedEventIds);

extraFailedEventIds.forEach((eventId) => {
mergedFailedEventIds.add(eventId);
});

return Array.from(mergedFailedEventIds);
}

/**
* Compose event with repetition
*
Expand Down
42 changes: 21 additions & 21 deletions src/resolvers/event.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
const getEventsFactory = require('./helpers/eventsFactory').default;
const {
fireAndForgetAssigneeNotifications,
parseBulkEventIds,
mergeFailedEventIds,
} = require('./helpers/bulkEvents');
enqueueAssigneeNotification,
} = require('./helpers/bulkEventUtils');
const { aiService } = require('../services/ai');
const { UserInputError } = require('apollo-server-express');
const { ObjectId } = require('mongodb');
Expand Down Expand Up @@ -148,25 +147,25 @@ module.exports = {
* @param {string} projectId - project id
* @param {string[]} eventIds - original event ids
* @param {UserInContext} user - user context
* @returns {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
* @returns {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
*/
async bulkVisitEvents(_obj, { projectId, eventIds }, { user, ...context }) {
const { validEventIds, invalidEventIds } = parseBulkEventIds(eventIds);

if (validEventIds.length === 0) {
return {
updatedCount: 0,
updatedEventIds: [],
failedEventIds: invalidEventIds,
};
}

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

return {
...result,
failedEventIds: mergeFailedEventIds(result, invalidEventIds),
failedEventIds,
};
},

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

if (validEventIds.length === 0) {
return {
updatedCount: 0,
updatedEventIds: [],
failedEventIds: invalidEventIds,
};
}

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

return {
...result,
failedEventIds: mergeFailedEventIds(result, invalidEventIds),
failedEventIds,
};
},

Expand Down Expand Up @@ -261,12 +260,12 @@ module.exports = {

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

fireAndForgetAssigneeNotifications({
enqueueAssigneeNotification({
assigneeData,
eventIds: [ eventId ],
projectId,
assigneeId: assignee,
projectId,
whoAssignedId: user.id,
eventId,
});

return {
Expand Down Expand Up @@ -300,7 +299,7 @@ module.exports = {
* @param {ResolverObj} _obj - resolver context
* @param {BulkUpdateAssigneeInput} input - object of arguments
* @param factories - factories for working with models
* @return {Promise<{ updatedCount: number, updatedEventIds: string[], failedEventIds: string[] }>}
* @return {Promise<{ updatedEventIds: string[], failedEventIds: string[] }>}
*/
async bulkUpdateAssignee(_obj, { input }, { factories, user, ...context }) {
const { projectId, eventIds, assignee } = input;
Expand All @@ -309,7 +308,6 @@ module.exports = {

if (validEventIds.length === 0) {
return {
updatedCount: 0,
updatedEventIds: [],
failedEventIds: invalidEventIds,
};
Expand Down Expand Up @@ -342,16 +340,18 @@ module.exports = {
const result = await factory.bulkUpdateAssignee(validEventIds, assignee);
const resultWithInvalid = {
...result,
failedEventIds: mergeFailedEventIds(result, invalidEventIds),
failedEventIds: Array.from(new Set([...(result.failedEventIds || []), ...invalidEventIds])),
};

if (assignee && resultWithInvalid.updatedEventIds.length > 0) {
fireAndForgetAssigneeNotifications({
assigneeData,
eventIds: resultWithInvalid.updatedEventIds,
projectId,
assigneeId: assignee,
whoAssignedId: user.id,
resultWithInvalid.updatedEventIds.forEach((eventId) => {
enqueueAssigneeNotification({
assigneeData,
assigneeId: assignee,
projectId,
whoAssignedId: user.id,
eventId,
});
});
}

Expand Down
Loading