diff --git a/package.json b/package.json index 175b7bc56..f2e1e154d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "hawk.workers", "private": true, - "version": "0.1.3", + "version": "0.1.4", "description": "Hawk workers", "repository": "git@github.com:codex-team/hawk.workers.git", "license": "BUSL-1.1", diff --git a/workers/loop/src/templates/event.ts b/workers/loop/src/templates/event.ts index bd1f9ced0..afc6622ee 100644 --- a/workers/loop/src/templates/event.ts +++ b/workers/loop/src/templates/event.ts @@ -39,7 +39,8 @@ function renderBacktrace(event: GroupedEventDBScheme): string { export default function render(tplData: EventsTemplateVariables): string { const eventInfo = tplData.events[0] as TemplateEventData; const event = eventInfo.event; - const eventURL = tplData.host + '/project/' + tplData.project._id + '/event/' + event._id + '/'; + const repetitionId = eventInfo.repetitionId; + const eventURL = tplData.host + '/project/' + tplData.project._id + '/event/' + event._id + '/' + (repetitionId ? repetitionId + '/overview' : ''); let location = 'Неизвестное место'; if (event.payload.backtrace && event.payload.backtrace.length > 0) { diff --git a/workers/loop/tests/provider.test.ts b/workers/loop/tests/provider.test.ts index b8185928e..955ad4911 100644 --- a/workers/loop/tests/provider.test.ts +++ b/workers/loop/tests/provider.test.ts @@ -137,6 +137,54 @@ describe('LoopProvider', () => { expect(message).toBeDefined(); }); + /** + * Event URL should include repetitionId when provided + */ + describe('event URL contains correct repetitionId', () => { + const eventId = new ObjectId('5d206f7f9aaf7c0071d64597'); + const projectId = new ObjectId('5d206f7f9aaf7c0071d64596'); + const host = 'https://garage.hawk.so'; + + const basePayload = { + events: [ { + event: { + _id: eventId, + totalCount: 1, + timestamp: Date.now(), + payload: { title: 'Err', backtrace: [] }, + } as DecodedGroupedEvent, + daysRepeated: 1, + newCount: 1, + } ], + period: 60, + host, + hostOfStatic: '', + project: { + _id: projectId, + token: 'tok', + name: 'P', + workspaceId: projectId, + uidAdded: projectId, + notifications: [], + } as ProjectDBScheme, + }; + + it('should include repetitionId and /overview in URL when repetitionId is set', () => { + const repetitionId = '5d206f7f9aaf7c0071d64599'; + const payload = { ...basePayload, events: [ { ...basePayload.events[0], repetitionId } ] }; + const message = templates.EventTpl(payload); + + expect(message).toContain(`/event/${eventId}/${repetitionId}/overview`); + }); + + it('should omit repetitionId from URL when repetitionId is not set', () => { + const message = templates.EventTpl(basePayload); + + expect(message).toContain(`/event/${eventId}/`); + expect(message).not.toContain('/overview'); + }); + }); + /** * Check that rendering of a several events message works without errors */ diff --git a/workers/slack/src/templates/event.ts b/workers/slack/src/templates/event.ts index d11fa66fc..00833bcec 100644 --- a/workers/slack/src/templates/event.ts +++ b/workers/slack/src/templates/event.ts @@ -45,7 +45,7 @@ function renderBacktrace(event: GroupedEventDBScheme): string { export default function render(tplData: EventsTemplateVariables): IncomingWebhookSendArguments { const eventInfo = tplData.events[0] as TemplateEventData; const event = eventInfo.event; - const eventURL = getEventUrl(tplData.host, tplData.project, event); + const eventURL = getEventUrl(tplData.host, tplData.project, event, eventInfo.repetitionId); const location = getEventLocation(event); const blocks = [ diff --git a/workers/slack/src/templates/utils.ts b/workers/slack/src/templates/utils.ts index cc0af00d2..9b14e586b 100644 --- a/workers/slack/src/templates/utils.ts +++ b/workers/slack/src/templates/utils.ts @@ -32,9 +32,12 @@ export function getEventLocation(event: DecodedGroupedEvent): string { * @param host - garage host. Also, can be accessed from process.env.GARAGE_URL * @param project - parent project * @param event - event to compose its URL + * @param repetitionId - id of the specific repetition that triggered the notification */ -export function getEventUrl(host: string, project: ProjectDBScheme, event: GroupedEventDBScheme): string { - return host + '/project/' + project._id + '/event/' + event._id + '/'; +export function getEventUrl(host: string, project: ProjectDBScheme, event: GroupedEventDBScheme, repetitionId?: string | null): string { + const base = host + '/project/' + project._id + '/event/' + event._id + '/'; + + return repetitionId ? base + repetitionId + '/overview' : base; } /** diff --git a/workers/slack/tests/utils.test.ts b/workers/slack/tests/utils.test.ts new file mode 100644 index 000000000..7c67f75da --- /dev/null +++ b/workers/slack/tests/utils.test.ts @@ -0,0 +1,40 @@ +import { ObjectId } from 'mongodb'; +import { ProjectDBScheme, GroupedEventDBScheme } from '@hawk.so/types'; +import { getEventUrl } from '../src/templates/utils'; + +const project = { + _id: new ObjectId('5d206f7f9aaf7c0071d64596'), + token: 'project-token', + name: 'Project', + workspaceId: new ObjectId('5d206f7f9aaf7c0071d64596'), + uidAdded: new ObjectId('5d206f7f9aaf7c0071d64596'), + notifications: [], +} as ProjectDBScheme; + +const event = { + _id: new ObjectId('5d206f7f9aaf7c0071d64597'), + payload: { title: 'Error' }, +} as unknown as GroupedEventDBScheme; + +const host = 'https://garage.hawk.so'; + +describe('getEventUrl', () => { + it('should return base URL with trailing slash when no repetitionId', () => { + const url = getEventUrl(host, project, event); + + expect(url).toBe(`${host}/project/${project._id}/event/${event._id}/`); + }); + + it('should return base URL with trailing slash when repetitionId is null', () => { + const url = getEventUrl(host, project, event, null); + + expect(url).toBe(`${host}/project/${project._id}/event/${event._id}/`); + }); + + it('should append repetitionId and /overview when repetitionId is provided', () => { + const repetitionId = '5d206f7f9aaf7c0071d64599'; + const url = getEventUrl(host, project, event, repetitionId); + + expect(url).toBe(`${host}/project/${project._id}/event/${event._id}/${repetitionId}/overview`); + }); +}); diff --git a/workers/telegram/src/templates/event.ts b/workers/telegram/src/templates/event.ts index c8676c00a..46da0cba2 100644 --- a/workers/telegram/src/templates/event.ts +++ b/workers/telegram/src/templates/event.ts @@ -8,7 +8,8 @@ import type { EventsTemplateVariables, TemplateEventData } from 'hawk-worker-sen export default function render(tplData: EventsTemplateVariables): string { const eventInfo = tplData.events[0] as TemplateEventData; const event = eventInfo.event; - const eventURL = tplData.host + '/project/' + tplData.project._id + '/event/' + event._id + '/'; + const repetitionId = eventInfo.repetitionId; + const eventURL = tplData.host + '/project/' + tplData.project._id + '/event/' + event._id + '/' + (repetitionId ? repetitionId + '/overview' : ''); let location = ''; if (event.payload.backtrace && event.payload.backtrace.length > 0) { diff --git a/workers/telegram/tests/provider.test.ts b/workers/telegram/tests/provider.test.ts index 7cb4ca4b2..51ba45c4a 100644 --- a/workers/telegram/tests/provider.test.ts +++ b/workers/telegram/tests/provider.test.ts @@ -2,6 +2,7 @@ import { EventNotification, SeveralEventsNotification } from 'hawk-worker-sender import { DecodedGroupedEvent, ProjectDBScheme } from '@hawk.so/types'; import TelegramProvider from 'hawk-worker-telegram/src/provider'; import templates from '../src/templates'; +import EventTpl from '../src/templates/event'; import { ObjectId } from 'mongodb'; /** @@ -64,6 +65,54 @@ describe('TelegramProvider', () => { expect(message).toBeDefined(); }); + /** + * Event URL should include repetitionId when provided + */ + describe('event URL contains correct repetitionId', () => { + const eventId = new ObjectId('5d206f7f9aaf7c0071d64597'); + const projectId = new ObjectId('5d206f7f9aaf7c0071d64596'); + const host = 'https://garage.hawk.so'; + + const basePayload = { + events: [ { + event: { + _id: eventId, + totalCount: 1, + timestamp: Date.now(), + payload: { title: 'Err', backtrace: [] }, + } as DecodedGroupedEvent, + daysRepeated: 1, + newCount: 1, + } ], + period: 60, + host, + hostOfStatic: '', + project: { + _id: projectId, + token: 'tok', + name: 'P', + workspaceId: projectId, + uidAdded: projectId, + notifications: [], + } as ProjectDBScheme, + }; + + it('should include repetitionId and /overview in URL when repetitionId is set', () => { + const repetitionId = '5d206f7f9aaf7c0071d64599'; + const payload = { ...basePayload, events: [ { ...basePayload.events[0], repetitionId } ] }; + const message = EventTpl(payload); + + expect(message).toContain(`/event/${eventId}/${repetitionId}/overview`); + }); + + it('should omit repetitionId from URL when repetitionId is not set', () => { + const message = EventTpl(basePayload); + + expect(message).toContain(`/event/${eventId}/`); + expect(message).not.toContain('/overview'); + }); + }); + /** * Check that rendering of a several events message works without errors */