Skip to content

Commit 370cd76

Browse files
authored
Merge pull request #636 from codex-team/feat/events-assignee-filter
feat(events): enhance assignee filtering with sentinel values for una…
2 parents 31dea62 + 7dcbc3e commit 370cd76

File tree

4 files changed

+72
-6
lines changed

4 files changed

+72
-6
lines changed

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.4.10",
3+
"version": "1.4.11",
44
"main": "index.ts",
55
"license": "BUSL-1.1",
66
"scripts": {

src/models/eventsFactory.js

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ class EventsFactory extends Factory {
340340
? Object.fromEntries(
341341
Object
342342
.entries(filters)
343-
.filter(([mark]) => markFilters.includes(mark))
343+
.filter(([ mark ]) => markFilters.includes(mark))
344344
.map(([mark, exists]) => [`event.marks.${mark}`, { $exists: exists } ])
345345
)
346346
: {};
@@ -364,9 +364,43 @@ class EventsFactory extends Factory {
364364
}
365365
: {};
366366

367-
const assigneeFilter = assignee
368-
? { 'event.assignee': String(assignee) }
369-
: {};
367+
/**
368+
* Sentinel values from garage assignee filter (not user ids)
369+
*/
370+
const FILTER_UNASSIGNED = '__filter_unassigned__';
371+
const FILTER_ANY_ASSIGNEE = '__filter_any_assignee__';
372+
373+
const assigneeFilter = (() => {
374+
if (!assignee) {
375+
return {};
376+
}
377+
if (assignee === FILTER_UNASSIGNED) {
378+
/**
379+
* Use $and so this does not collide with searchFilter’s top-level $or in $match spread
380+
*/
381+
return {
382+
$and: [
383+
{
384+
$or: [
385+
{ 'event.assignee': { $exists: false } },
386+
{ 'event.assignee': null },
387+
{ 'event.assignee': '' },
388+
],
389+
},
390+
],
391+
};
392+
}
393+
if (assignee === FILTER_ANY_ASSIGNEE) {
394+
return {
395+
'event.assignee': {
396+
$exists: true,
397+
$nin: [null, ''],
398+
},
399+
};
400+
}
401+
402+
return { 'event.assignee': String(assignee) };
403+
})();
370404

371405
pipeline.push(
372406
/**

src/typeDefs/project.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ type Project {
353353
release: String
354354
355355
"""
356-
User id to filter events by assignee
356+
Filter by assignee: workspace user id, or sentinels __filter_unassigned__ (no assignee) / __filter_any_assignee__ (has assignee)
357357
"""
358358
assignee: ID
359359
): DailyEventsPortion

test/resolvers/project-daily-events-portion.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,38 @@ describe('Project resolver dailyEventsPortion', () => {
5555
);
5656
});
5757

58+
it('should pass assignee sentinel for unassigned filter to factory', async () => {
59+
const findDailyEventsPortion = jest.fn().mockResolvedValue({
60+
nextCursor: null,
61+
dailyEvents: [],
62+
});
63+
(getEventsFactory as unknown as jest.Mock).mockReturnValue({
64+
findDailyEventsPortion,
65+
});
66+
67+
const project = { _id: 'project-1' };
68+
const args = {
69+
limit: 50,
70+
nextCursor: null,
71+
sort: 'BY_DATE',
72+
filters: {},
73+
search: '',
74+
assignee: '__filter_unassigned__',
75+
};
76+
77+
await projectResolver.Project.dailyEventsPortion(project, args, {});
78+
79+
expect(findDailyEventsPortion).toHaveBeenCalledWith(
80+
50,
81+
null,
82+
'BY_DATE',
83+
{},
84+
'',
85+
undefined,
86+
'__filter_unassigned__'
87+
);
88+
});
89+
5890
it('should keep old call shape when assignee is not provided', async () => {
5991
const findDailyEventsPortion = jest.fn().mockResolvedValue({
6092
nextCursor: null,

0 commit comments

Comments
 (0)