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
5 changes: 3 additions & 2 deletions .github/workflows/deploy-bots.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ jobs:
env:
BUGBUSTER_GITHUB_TOKEN: ${{ secrets.BUGBUSTER_GITHUB_TOKEN }}
BUGBUSTER_GITHUB_WEBHOOK_SECRET: ${{ secrets.BUGBUSTER_GITHUB_WEBHOOK_SECRET }}
BUGBUSTER_SLACK_BOT_TOKEN: ${{ secrets.BUGBUSTER_SLACK_BOT_TOKEN }}
BUGBUSTER_SLACK_SIGNING_SECRET: ${{ secrets.BUGBUSTER_SLACK_SIGNING_SECRET }}
BUGBUSTER_LINEAR_API_KEY: ${{ secrets.BUGBUSTER_LINEAR_API_KEY }}
BUGBUSTER_LINEAR_WEBHOOK_SIGNING_SECRET: ${{ secrets.BUGBUSTER_LINEAR_WEBHOOK_SIGNING_SECRET }}
BUGBUSTER_TELEGRAM_BOT_TOKEN: ${{ secrets.BUGBUSTER_TELEGRAM_BOT_TOKEN }}
uses: ./.github/actions/setup
- name: Deploy Bots
run: |
Expand Down
44 changes: 28 additions & 16 deletions bots/bugbuster/bot.definition.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import * as sdk from '@botpress/sdk'
import * as genenv from './.genenv'
import github from './bp_modules/github'
import slack from './bp_modules/slack'
import linear from './bp_modules/linear'
import telegram from './bp_modules/telegram'

export default new sdk.BotDefinition({
states: {
listeners: {
recentlyLinted: {
type: 'bot',
schema: sdk.z.object({
conversationIds: sdk.z.array(sdk.z.string()).title('Conversation IDs').describe('List of conversation IDs'),
issues: sdk.z
.array(
sdk.z.object({
id: sdk.z.string(),
lintedAt: sdk.z.string().datetime(),
})
)
.title('Recently Linted Issues')
.describe('List of recently linted issues'),
}),
},
},
events: {
syncIssuesRequest: {
schema: sdk.z.object({}).title('Sync Issues Request').describe('Request to sync issues'),
},
},
recurringEvents: {
fetchIssues: {
type: 'syncIssuesRequest',
payload: {},
schedule: { cron: '0 0/6 * * *' }, // every 6 hours
},
},
})
.addIntegration(github, {
enabled: true,
Expand All @@ -33,4 +30,19 @@ export default new sdk.BotDefinition({
githubWebhookSecret: genenv.BUGBUSTER_GITHUB_WEBHOOK_SECRET,
},
})
.addIntegration(slack)
// TODO: replace Telegram with Slack when available
.addIntegration(telegram, {
enabled: true,
configurationType: null,
configuration: {
botToken: genenv.BUGBUSTER_TELEGRAM_BOT_TOKEN,
},
})
.addIntegration(linear, {
enabled: true,
configurationType: 'apiKey',
configuration: {
apiKey: genenv.BUGBUSTER_LINEAR_API_KEY,
webhookSigningSecret: genenv.BUGBUSTER_LINEAR_WEBHOOK_SIGNING_SECRET,
},
})
11 changes: 7 additions & 4 deletions bots/bugbuster/package.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
{
"name": "@bp-bots/bugbuster",
"scripts": {
"postinstall": "genenv -o ./.genenv/index.ts -e BUGBUSTER_GITHUB_TOKEN -e BUGBUSTER_GITHUB_WEBHOOK_SECRET -e BUGBUSTER_SLACK_BOT_TOKEN -e BUGBUSTER_SLACK_SIGNING_SECRET",
"postinstall": "genenv -o ./.genenv/index.ts -e BUGBUSTER_GITHUB_TOKEN -e BUGBUSTER_GITHUB_WEBHOOK_SECRET -e BUGBUSTER_LINEAR_API_KEY -e BUGBUSTER_LINEAR_WEBHOOK_SIGNING_SECRET -e BUGBUSTER_TELEGRAM_BOT_TOKEN",
"check:type": "tsc --noEmit",
"check:bplint": "bp lint",
"build": "bp add -y && bp build"
},
"private": true,
"dependencies": {
"@botpress/client": "workspace:*",
"@botpress/sdk": "workspace:*"
"@botpress/sdk": "workspace:*",
"@linear/sdk": "^50.0.0"
},
"devDependencies": {
"@botpress/cli": "workspace:*",
"@botpress/common": "workspace:*",
"@botpresshub/github": "workspace:*",
"@botpresshub/slack": "workspace:*",
"@botpresshub/linear": "workspace:*",
"@botpresshub/telegram": "workspace:*",
"@bpinternal/genenv": "0.0.1"
},
"bpDependencies": {
"github": "../../integrations/github",
"slack": "../../integrations/slack"
"linear": "../../integrations/linear",
"telegram": "../../integrations/telegram"
}
}
2 changes: 0 additions & 2 deletions bots/bugbuster/src/bot.ts

This file was deleted.

34 changes: 34 additions & 0 deletions bots/bugbuster/src/handlers/github-issue-opened.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import * as utils from '../utils'
import * as bp from '.botpress'

export const handleGithubIssueOpened: bp.EventHandlers['github:issueOpened'] = async (props): Promise<void> => {
const githubIssue = props.event.payload

props.logger.info('Received GitHub issue', githubIssue)

const linear = await utils.linear.LinearApi.create()

const githubLabel = await linear.findLabel({ name: 'github', parentName: 'origin' })
if (!githubLabel) {
props.logger.error('Label origin/github not found in engineering team')
}

const linearResponse = await linear.client.createIssue({
teamId: linear.teams.ENG.id,
stateId: linear.states.ENG.TRIAGE.id,
title: githubIssue.issue.name,
description: githubIssue.issue.body,
labelIds: githubLabel ? [githubLabel.id] : [],
})

const comment = [
'This issue was created from GitHub by BugBuster Bot.',
'',
`GitHub Issue: [${githubIssue.issue.name}](${githubIssue.issue.url})`,
].join('\n')

await linear.client.createComment({
issueId: linearResponse.issueId,
body: comment,
})
}
5 changes: 3 additions & 2 deletions bots/bugbuster/src/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './issue-opened'
export * from './sync-issues'
export * from './github-issue-opened'
export * from './linear-issue-updated'
export * from './message-created'
21 changes: 0 additions & 21 deletions bots/bugbuster/src/handlers/issue-opened.ts

This file was deleted.

53 changes: 53 additions & 0 deletions bots/bugbuster/src/handlers/linear-issue-updated.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as linlint from '../linear-lint-issue'
import * as utils from '../utils'
import * as bp from '.botpress'

export const handleLinearIssueUpdated: bp.EventHandlers['linear:issueUpdated'] = async (props) => {
const { number: issueNumber, teamKey } = props.event.payload
if (!issueNumber || !teamKey) {
props.logger.error('Missing issueNumber or teamKey in event payload')
return
}

props.logger.info('Linear issue updated event received', `${teamKey}-${issueNumber}`)

const linear = await utils.linear.LinearApi.create()

if (!linear.isTeam(teamKey) || teamKey !== 'SQD') {
props.logger.error(`Ignoring issue of team "${teamKey}"`)
return
}

const issue = await linear.findIssue({ teamKey, issueNumber })
if (!issue) {
props.logger.error(`Issue with number ${issueNumber} not found in team ${teamKey}`)
return
}

const botpress = await utils.botpress.BotpressApi.create(props)
const recentlyLinted = await botpress.getRecentlyLinted()

if (recentlyLinted.some(({ id: issueId }) => issue.id === issueId)) {
props.logger.info(`Issue ${issue.identifier} has already been linted recently, skipping...`)
return
}

const errors = await linlint.lintIssue(linear, issue)
if (errors.length === 0) {
props.logger.info(`Issue ${issue.identifier} passed all lint checks.`)
return
}

props.logger.warn(`Issue ${issue.identifier} has ${errors.length} lint errors:`)

await linear.client.createComment({
issueId: issue.id,
body: [
`BugBuster Bot found the following problems with ${issue.identifier}:`,
'',
...errors.map((error) => `- ${error.message}`),
].join('\n'),
})

await botpress.setRecentlyLinted([...recentlyLinted, { id: issue.id, lintedAt: new Date().toISOString() }])
}
14 changes: 14 additions & 0 deletions bots/bugbuster/src/handlers/message-created.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as utils from '../utils'
import * as bp from '.botpress'

const MESSAGING_INTEGRATIONS = ['telegram', 'slack']

export const handleMessageCreated: bp.MessageHandlers['*'] = async (props) => {
const { conversation, message } = props
if (!MESSAGING_INTEGRATIONS.includes(conversation.integration)) {
props.logger.info(`Ignoring message from ${conversation.integration}`)
return
}
const botpress = await utils.botpress.BotpressApi.create(props)
await botpress.respondText(message.conversationId, "Hey, I'm BugBuster.")
}
48 changes: 0 additions & 48 deletions bots/bugbuster/src/handlers/sync-issues.ts

This file was deleted.

61 changes: 5 additions & 56 deletions bots/bugbuster/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,61 +1,10 @@
import { bot } from './bot'
import { handleNewIssue, handleSyncIssuesRequest } from './handlers'
import { listIssues } from './list-issues'
import * as listeners from './listeners'
import * as handlers from './handlers'
import * as bp from '.botpress'

bot.on.event('github:issueOpened', handleNewIssue)
bot.on.event('syncIssuesRequest', handleSyncIssuesRequest)
export const bot = new bp.Bot({ actions: {} })

const respond = async (props: bp.MessageHandlerProps, text: string) => {
const { client, ctx, message } = props
await client.createMessage({
type: 'text',
payload: {
text,
},
conversationId: message.conversationId,
userId: ctx.botId,
tags: {},
})
}

bot.on.message('*', async (props) => {
const { conversation, message, client, ctx } = props
if (conversation.integration !== 'slack') {
console.info(`Ignoring message from ${conversation.integration}`)
return
}

if (message.type === 'text' && message.payload.text === '#start_listening') {
const state = await listeners.readListeners(props)
if (!state.conversationIds.includes(message.conversationId)) {
state.conversationIds.push(message.conversationId)
await listeners.writeListeners(props, state)
return await respond(props, 'You will now receive notifications.')
} else {
return await respond(props, 'Already listening.')
}
} else if (message.type === 'text' && message.payload.text === '#stop_listening') {
const state = await listeners.readListeners(props)
state.conversationIds = state.conversationIds.filter((id) => id !== message.conversationId)
await listeners.writeListeners(props, state)
return await respond(props, 'Stopped listening.')
} else if (message.type === 'text' && message.payload.text === '#list') {
const githubIssues = await listIssues(props)
const message = ['Here are the issues in GitHub:', ...githubIssues.map((i) => `\t${i.displayName}`)].join('\n')
return await respond(props, message)
}

await client.createMessage({
type: 'text',
payload: {
text: "Hi, I'm BugBuster. I can't help you.",
},
conversationId: message.conversationId,
userId: ctx.botId,
tags: {},
})
})
bot.on.event('github:issueOpened', handlers.handleGithubIssueOpened)
bot.on.event('linear:issueUpdated', handlers.handleLinearIssueUpdated)
bot.on.message('*', handlers.handleMessageCreated)

export default bot
Loading
Loading