Skip to content

Commit 82d702e

Browse files
feat: support Telegram forum topics (#1419)
1 parent 8989ec2 commit 82d702e

4 files changed

Lines changed: 124 additions & 2 deletions

File tree

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { mkdtemp, readFile, rm } from 'node:fs/promises'
2+
import { tmpdir } from 'node:os'
3+
import path from 'node:path'
4+
5+
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
6+
7+
import { TelegramAlertChannelCodegen, type TelegramAlertChannelResource } from '../telegram-alert-channel-codegen.js'
8+
import { Context } from '../internal/codegen/index.js'
9+
import { Program } from '../../sourcegen/index.js'
10+
11+
describe('TelegramAlertChannelCodegen', () => {
12+
let rootDirectory: string
13+
14+
beforeEach(async () => {
15+
rootDirectory = await mkdtemp(path.join(tmpdir(), 'telegram-alert-channel-codegen-'))
16+
})
17+
18+
afterEach(async () => {
19+
await rm(rootDirectory, { recursive: true, force: true })
20+
})
21+
22+
it('preserves the message thread ID when exporting a Telegram alert channel', async () => {
23+
const program = new Program({
24+
rootDirectory,
25+
constructFileSuffix: '.check',
26+
specFileSuffix: '.spec',
27+
language: 'typescript',
28+
})
29+
const codegen = new TelegramAlertChannelCodegen(program)
30+
const context = new Context()
31+
const resource: TelegramAlertChannelResource = {
32+
id: 123,
33+
type: 'WEBHOOK',
34+
config: {
35+
name: 'Telegram topic',
36+
webhookType: 'WEBHOOK_TELEGRAM',
37+
url: 'https://api.telegram.org/bot123456:ABC/sendMessage',
38+
method: 'POST',
39+
headers: [],
40+
template: 'chat_id=-701234567&message_thread_id=42&parse_mode=HTML&text=test',
41+
},
42+
sendRecovery: true,
43+
sendFailure: true,
44+
sendDegraded: false,
45+
sslExpiry: false,
46+
sslExpiryThreshold: 30,
47+
}
48+
49+
codegen.prepare('telegram-topic', resource, context)
50+
codegen.gencode('telegram-topic', resource, context)
51+
await program.realize()
52+
53+
const [filePath] = program.paths
54+
if (filePath === undefined) {
55+
throw new Error('Codegen did not register a generated file')
56+
}
57+
const source = await readFile(filePath, 'utf8')
58+
59+
expect(source).toContain(`messageThreadId: '42'`)
60+
})
61+
})
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { beforeEach, describe, expect, it } from 'vitest'
2+
3+
import { Project } from '../project.js'
4+
import { Session } from '../session.js'
5+
import { TelegramAlertChannel } from '../telegram-alert-channel.js'
6+
7+
describe('TelegramAlertChannel', () => {
8+
beforeEach(() => {
9+
Session.project = new Project('project-id', {
10+
name: 'Test Project',
11+
repoUrl: 'https://github.com/checkly/checkly-cli',
12+
})
13+
})
14+
15+
it('includes the message thread ID in the Telegram request template', () => {
16+
const channel = new TelegramAlertChannel('telegram-topic', {
17+
name: 'Telegram topic',
18+
apiKey: '123456:ABC',
19+
chatId: '-701234567',
20+
messageThreadId: '42',
21+
payload: 'test',
22+
})
23+
24+
expect(channel.synthesize()).toMatchObject({
25+
type: 'WEBHOOK',
26+
config: {
27+
webhookType: 'WEBHOOK_TELEGRAM',
28+
template: 'chat_id=-701234567&message_thread_id=42&parse_mode=HTML&text=test',
29+
},
30+
})
31+
})
32+
33+
it('keeps the existing template shape when no message thread ID is configured', () => {
34+
const channel = new TelegramAlertChannel('telegram-chat', {
35+
name: 'Telegram chat',
36+
apiKey: '123456:ABC',
37+
chatId: '-701234567',
38+
payload: 'test',
39+
})
40+
41+
expect(channel.synthesize()).toMatchObject({
42+
config: {
43+
template: 'chat_id=-701234567&parse_mode=HTML&text=test',
44+
},
45+
})
46+
})
47+
})

packages/cli/src/constructs/telegram-alert-channel-codegen.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ function apiKeyFromUrl (url: string): string | undefined {
2121

2222
interface TemplateValues {
2323
chatId?: string
24+
messageThreadId?: string
2425
text?: string
2526
}
2627

@@ -37,6 +38,7 @@ function parseTemplate (template: string): TemplateValues {
3738

3839
return {
3940
chatId: singleValue('chat_id'),
41+
messageThreadId: singleValue('message_thread_id'),
4042
text: singleValue('text'),
4143
}
4244
}
@@ -110,13 +112,17 @@ export class TelegramAlertChannelCodegen extends Codegen<TelegramAlertChannelRes
110112
}
111113

112114
if (config.template) {
113-
const { chatId, text } = parseTemplate(config.template)
115+
const { chatId, messageThreadId, text } = parseTemplate(config.template)
114116
if (chatId) {
115117
builder.string('chatId', chatId)
116118
} else {
117119
throw new Error(`Failed to extract Telegram Chat ID from webhook template: ${config.template}`)
118120
}
119121

122+
if (messageThreadId) {
123+
builder.string('messageThreadId', messageThreadId)
124+
}
125+
120126
if (text) {
121127
if (text !== TelegramAlertChannel.DEFAULT_PAYLOAD) {
122128
builder.string('payload', text)

packages/cli/src/constructs/telegram-alert-channel.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export interface TelegramAlertChannelProps extends AlertChannelProps {
1616
* {@link https://www.checklyhq.com/docs/integrations/alerts/telegram/}
1717
*/
1818
apiKey: string
19+
/**
20+
* The optional message thread ID of a Telegram forum topic.
21+
* {@link https://core.telegram.org/bots/api#sendmessage}
22+
*/
23+
messageThreadId?: string
1924
/**
2025
* An optional custom payload. If not given,
2126
* `TelegramAlertChannel.DEFAULT_PAYLOAD` will be used.
@@ -57,9 +62,12 @@ Tags: {{#each TAGS}} <i><b>{{this}}</b></i> {{#unless @last}},{{/unless}} {{/eac
5762
this.webhookType = 'WEBHOOK_TELEGRAM'
5863
this.method = 'POST'
5964
const payload = props.payload ?? TelegramAlertChannel.DEFAULT_PAYLOAD
65+
const messageThreadPart = props.messageThreadId
66+
? `&message_thread_id=${props.messageThreadId}`
67+
: ''
6068
// For historical reasons the payload is not escaped even though it
6169
// should be.
62-
this.template = `chat_id=${props.chatId}&parse_mode=HTML&text=${payload}`
70+
this.template = `chat_id=${props.chatId}${messageThreadPart}&parse_mode=HTML&text=${payload}`
6371
this.url = `https://api.telegram.org/bot${props.apiKey}/sendMessage`
6472
}
6573

0 commit comments

Comments
 (0)