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
19 changes: 13 additions & 6 deletions bots/bugbuster/bot.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,21 @@ export default new sdk.BotDefinition({
),
}),
},
notificationChannelName: {
notificationChannels: {
type: 'bot',
schema: sdk.z.object({
name: sdk.z
.string()
.optional()
.title('Notification Channel Name')
.describe('The Slack channel where notifications will be posted'),
channels: sdk.z
.array(
sdk.z.object({
name: sdk.z.string().title('Name').describe('The channel name'),
teams: sdk.z
.array(sdk.z.string())
.title('Teams')
.describe('The teams for which notifications will be sent to the channel'),
})
)
.title('Channel')
.describe('The Slack channel where notifications will be sent'),
}),
},
},
Expand Down
46 changes: 39 additions & 7 deletions bots/bugbuster/src/handlers/lint-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@ import * as boot from '../bootstrap'
import * as bp from '.botpress'

export const handleLintAll: bp.WorkflowHandlers['lintAll'] = async (props) => {
const { client, workflow, conversation } = props

const conversationId = conversation?.id
const { client, workflow, ctx, conversation } = props

const { botpress, issueProcessor } = boot.bootstrap(props)

const _handleError = (context: string) => (thrown: unknown) =>
botpress.handleError({ context, conversationId }, thrown)
const _handleError = (context: string) => (thrown: unknown) => botpress.handleError({ context }, thrown)

const {
state: {
Expand Down Expand Up @@ -75,8 +72,33 @@ export const handleLintAll: bp.WorkflowHandlers['lintAll'] = async (props) => {
endCursor = pagedIssues.pagination?.endCursor
} while (hasNextPage)

if (conversationId) {
await botpress.respondText(conversationId, _buildResultMessage(lintResults)).catch(() => {})
if (conversation?.id) {
await botpress.respondText(conversation.id, _buildResultMessage(lintResults)).catch(() => {})
await workflow.setCompleted()
return
}

const {
state: {
payload: { channels },
},
} = await client.getOrSetState({
id: ctx.botId,
name: 'notificationChannels',
type: 'bot',
payload: { channels: [] },
})

for (const channel of channels) {
const conversationId = await _getConversationId(client, channel.name).catch(
_handleError(`trying to get the conversation ID of Slack channel '${channel.name}'`)
)

const relevantIssues = lintResults.filter((result) =>
channel.teams.some((team) => result.identifier.includes(team))
)

await botpress.respondText(conversationId, _buildResultMessage(relevantIssues)).catch(() => {})
}

await workflow.setCompleted()
Expand All @@ -103,3 +125,13 @@ const _buildResultMessage = (results: types.LintResult[]) => {

return `Linting complete. ${messageDetail}`
}

const _getConversationId = async (client: bp.Client, channelName: string) => {
const conversation = await client.callAction({
type: 'slack:startChannelConversation',
input: {
channelName,
},
})
return conversation.output.conversationId
}
36 changes: 3 additions & 33 deletions bots/bugbuster/src/handlers/time-to-lint-all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,19 @@ import * as boot from '../bootstrap'
import * as bp from '.botpress'

export const handleTimeToLintAll: bp.EventHandlers['timeToLintAll'] = async (props) => {
const { client, ctx, logger } = props
const { client, logger } = props
logger.info("'timeToLintAll' event received.")

const { botpress } = boot.bootstrap(props)

const _handleError = (context: string, conversationId?: string) => (thrown: unknown) =>
botpress.handleError({ context, conversationId }, thrown)

const conversationId = await _tryGetConversationId(client, ctx.botId).catch(
_handleError('trying to get Slack notification channel')
)
const _handleError = (context: string) => (thrown: unknown) => botpress.handleError({ context }, thrown)

await client
.getOrCreateWorkflow({
name: 'lintAll',
input: {},
discriminateByStatusGroup: 'active',
conversationId,
status: 'pending',
})
.catch(_handleError("trying to start the 'lintAll' workflow", conversationId))
}

const _tryGetConversationId = async (client: bp.Client, botId: string): Promise<string | undefined> => {
const {
state: {
payload: { name },
},
} = await client.getOrSetState({
id: botId,
name: 'notificationChannelName',
payload: {},
type: 'bot',
})

if (name) {
const conversation = await client.callAction({
type: 'slack:startChannelConversation',
input: {
channelName: name,
},
})
return conversation.output.conversationId
}
return undefined
.catch(_handleError("trying to start the 'lintAll' workflow"))
}
163 changes: 148 additions & 15 deletions bots/bugbuster/src/services/command-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,38 +57,151 @@ export class CommandProcessor {
}
}

private _setNotifChannel: types.CommandImplementation = async ([channel]: string[]) => {
if (!channel) {
private _addNotifChannel: types.CommandImplementation = async ([channelToAdd, ...teams]: string[]) => {
if (!channelToAdd || !teams[0]) {
return { success: false, message: MISSING_ARGS_ERROR }
}

const {
state: {
payload: { channels },
},
} = await this._client.getOrSetState({
id: this._botId,
name: 'notificationChannels',
type: 'bot',
payload: { channels: [] },
})

const watchedTeams = await this._teamsManager.listWatchedTeams()
if (!teams.every((team) => watchedTeams.includes(team))) {
return {
success: false,
message: 'make sure every team you want to add is being watched.',
}
}

const existingChannel = channels.find((channel) => channel.name === channelToAdd)
if (!existingChannel) {
channels.push({ name: channelToAdd, teams })
} else {
teams.forEach((team) => {
if (!existingChannel.teams.includes(team)) {
existingChannel.teams.push(team)
}
})
}

await this._client.setState({
id: this._botId,
name: 'notificationChannels',
type: 'bot',
payload: { channels },
})

return {
success: true,
message: `Notifications for team(s) ${teams.join(', ')} will be posted in channel ${channelToAdd}.`,
}
}

private _removeNotifChannel: types.CommandImplementation = async ([channelToRemove]: string[]) => {
if (!channelToRemove) {
return { success: false, message: MISSING_ARGS_ERROR }
}

const {
state: {
payload: { channels },
},
} = await this._client.getOrSetState({
id: this._botId,
name: 'notificationChannels',
type: 'bot',
payload: { channels: [] },
})

if (!channels.find((channel) => channel.name === channelToRemove)) {
return {
success: false,
message: `channel '${channelToRemove}' is not part of the notification channels.`,
}
}

await this._client.setState({
id: this._botId,
name: 'notificationChannels',
type: 'bot',
payload: { channels: channels.filter((channel) => channel.name !== channelToRemove) },
})

return {
success: true,
message: `Notification channel ${channelToRemove} has been removed.`,
}
}

private _removeNotifChannelTeam: types.CommandImplementation = async ([channelToRemove, teamToRemove]: string[]) => {
if (!channelToRemove || !teamToRemove) {
return { success: false, message: MISSING_ARGS_ERROR }
}

const {
state: {
payload: { channels },
},
} = await this._client.getOrSetState({
id: this._botId,
name: 'notificationChannels',
type: 'bot',
payload: { channels: [] },
})

const channel = channels.find((channel) => channel.name === channelToRemove)
if (!channel) {
return {
success: false,
message: `channel '${channelToRemove}' is not part of the notification channels.`,
}
}

if (!channel.teams.find((team) => team === teamToRemove)) {
return {
success: false,
message: `channel ${channel.name} does not receive notifications from team '${teamToRemove}'.`,
}
}

channel.teams = channel.teams.filter((team) => team !== teamToRemove)

await this._client.setState({
id: this._botId,
name: 'notificationChannelName',
name: 'notificationChannels',
type: 'bot',
payload: { name: channel },
payload: { channels },
})

return {
success: true,
message: `Success. Notification channel is now set to ${channel}.`,
message: `Team ${teamToRemove} has been removed from channel ${channelToRemove}.`,
}
}

private _getNotifChannel: types.CommandImplementation = async () => {
private _listNotifChannels: types.CommandImplementation = async () => {
const {
state: {
payload: { name },
payload: { channels },
},
} = await this._client.getOrSetState({
id: this._botId,
name: 'notificationChannelName',
name: 'notificationChannels',
type: 'bot',
payload: {},
payload: { channels: [] },
})

let message = 'There is no set Slack notification channel.'
if (name) {
message = `The Slack notification channel is ${name}.`
if (channels.length > 0) {
message = this._buildNotifChannelsMessage(channels)
}

return {
Expand All @@ -97,6 +210,15 @@ export class CommandProcessor {
}
}

private _buildNotifChannelsMessage(channels: { name: string; teams: string[] }[]) {
return `The Slack notification channels are:\n${channels.map(this._getMessageForChannel).join('\n')}`
}

private _getMessageForChannel(channel: { name: string; teams: string[] }) {
const { name, teams } = channel
return `- channel ${name} for team(s) ${teams.join(', ')}`
}

public commandDefinitions: types.CommandDefinition[] = [
{
name: '#listTeams',
Expand All @@ -117,13 +239,24 @@ export class CommandProcessor {
implementation: this._lintAll,
},
{
name: '#setNotifChannel',
implementation: this._setNotifChannel,
name: '#addNotifChannel',
implementation: this._addNotifChannel,
requiredArgs: ['channelName', 'teamName1'],
optionalArgs: ['teamName2 ...'],
},
{
name: '#removeNotifChannel',
implementation: this._removeNotifChannel,
requiredArgs: ['channelName'],
},
{
name: '#getNotifChannel',
implementation: this._getNotifChannel,
name: '#removeNotifChannelTeam',
implementation: this._removeNotifChannelTeam,
requiredArgs: ['channelName', 'teamName'],
},
{
name: '#listNotifChannels',
implementation: this._listNotifChannels,
},
]
}
1 change: 1 addition & 0 deletions bots/bugbuster/src/services/teams-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export class TeamsManager {
if (!teamKeys.includes(key)) {
throw new Error(`The team with the key '${key}' is not currently being watched.`)
}
await this._setWatchedTeams(teamKeys.filter((team) => team !== key))
}

public async listWatchedTeams(): Promise<string[]> {
Expand Down
Loading