diff --git a/package.json b/package.json index 68211463..126150a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hawk.api", - "version": "1.4.9", + "version": "1.4.10", "main": "index.ts", "license": "BUSL-1.1", "scripts": { diff --git a/src/models/eventsFactory.js b/src/models/eventsFactory.js index bf3efb13..1a3df725 100644 --- a/src/models/eventsFactory.js +++ b/src/models/eventsFactory.js @@ -216,7 +216,8 @@ class EventsFactory extends Factory { sort = 'BY_DATE', filters = {}, search = '', - release + release, + assignee ) { if (typeof search !== 'string') { throw new Error('Search parameter must be a string'); @@ -334,10 +335,12 @@ class EventsFactory extends Factory { } : {}; + const markFilters = ['resolved', 'starred', 'ignored']; const matchFilter = filters ? Object.fromEntries( Object .entries(filters) + .filter(([mark]) => markFilters.includes(mark)) .map(([mark, exists]) => [`event.marks.${mark}`, { $exists: exists } ]) ) : {}; @@ -361,6 +364,10 @@ class EventsFactory extends Factory { } : {}; + const assigneeFilter = assignee + ? { 'event.assignee': String(assignee) } + : {}; + pipeline.push( /** * Left outer join original event on groupHash field @@ -398,6 +405,7 @@ class EventsFactory extends Factory { ...matchFilter, ...searchFilter, ...releaseFilter, + ...assigneeFilter, }, }, { $limit: limit + 1 }, diff --git a/src/resolvers/project.js b/src/resolvers/project.js index c1a2767c..63b00cff 100644 --- a/src/resolvers/project.js +++ b/src/resolvers/project.js @@ -583,7 +583,7 @@ module.exports = { * * @return {Promise} */ - async dailyEventsPortion(project, { limit, nextCursor, sort, filters, search, release }, context) { + async dailyEventsPortion(project, { limit, nextCursor, sort, filters, search, release, assignee }, context) { if (search) { if (search.length > MAX_SEARCH_QUERY_LENGTH) { search = search.slice(0, MAX_SEARCH_QUERY_LENGTH); @@ -592,7 +592,15 @@ module.exports = { const factory = getEventsFactory(context, project._id); - const dailyEventsPortion = await factory.findDailyEventsPortion(limit, nextCursor, sort, filters, search, release); + const dailyEventsPortion = await factory.findDailyEventsPortion( + limit, + nextCursor, + sort, + filters, + search, + release, + assignee + ); return dailyEventsPortion; }, diff --git a/src/typeDefs/project.ts b/src/typeDefs/project.ts index 296beb41..494cca39 100644 --- a/src/typeDefs/project.ts +++ b/src/typeDefs/project.ts @@ -123,6 +123,10 @@ input EventsFiltersInput { If True, includes events with ignored mark to the output """ ignored: Boolean + """ + Includes only events assigned to passed user id + """ + assignee: ID } """ @@ -347,6 +351,11 @@ type Project { Release label to filter events by payload.release """ release: String + + """ + User id to filter events by assignee + """ + assignee: ID ): DailyEventsPortion """ diff --git a/test/resolvers/project-daily-events-portion.test.ts b/test/resolvers/project-daily-events-portion.test.ts new file mode 100644 index 00000000..93c2a96b --- /dev/null +++ b/test/resolvers/project-daily-events-portion.test.ts @@ -0,0 +1,89 @@ +import '../../src/env-test'; + +jest.mock('../../src/integrations/github/service', () => require('../__mocks__/github-service')); + +jest.mock('../../src/resolvers/helpers/eventsFactory', () => ({ + __esModule: true, + default: jest.fn(), +})); + +// @ts-expect-error - CommonJS module, TypeScript can't infer types properly +import projectResolverModule from '../../src/resolvers/project'; +import getEventsFactory from '../../src/resolvers/helpers/eventsFactory'; + +const projectResolver = projectResolverModule as { + Project: { + dailyEventsPortion: (...args: unknown[]) => Promise; + }; +}; + +describe('Project resolver dailyEventsPortion', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should pass assignee filter to events factory', async () => { + const findDailyEventsPortion = jest.fn().mockResolvedValue({ + nextCursor: null, + dailyEvents: [], + }); + (getEventsFactory as unknown as jest.Mock).mockReturnValue({ + findDailyEventsPortion, + }); + + const project = { _id: 'project-1' }; + const args = { + limit: 50, + nextCursor: null, + sort: 'BY_DATE', + filters: { ignored: true }, + search: 'TypeError', + release: '1.0.0', + assignee: 'user-123', + }; + + await projectResolver.Project.dailyEventsPortion(project, args, {}); + + expect(findDailyEventsPortion).toHaveBeenCalledWith( + 50, + null, + 'BY_DATE', + { ignored: true }, + 'TypeError', + '1.0.0', + 'user-123' + ); + }); + + it('should keep old call shape when assignee is not provided', async () => { + const findDailyEventsPortion = jest.fn().mockResolvedValue({ + nextCursor: null, + dailyEvents: [], + }); + (getEventsFactory as unknown as jest.Mock).mockReturnValue({ + findDailyEventsPortion, + }); + + const project = { _id: 'project-1' }; + const args = { + limit: 10, + nextCursor: null, + sort: 'BY_DATE', + filters: {}, + search: '', + release: undefined, + }; + + await projectResolver.Project.dailyEventsPortion(project, args, {}); + + expect(findDailyEventsPortion).toHaveBeenCalledWith( + 10, + null, + 'BY_DATE', + {}, + '', + undefined, + undefined + ); + }); +});