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
28 changes: 18 additions & 10 deletions bots/bugbuster/src/handlers/github-issue-opened.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import * as boot from '../bootstrap'
import * as bp from '.botpress'

const STATE_NAME_FOR_NEW_ISSUES = 'Triage'
const TEAM_KEY_FOR_NEW_ISSUES = 'ENG'

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

Expand All @@ -13,24 +16,29 @@ export const handleGithubIssueOpened: bp.EventHandlers['github:issueOpened'] = a
(thrown: unknown): Promise<never> =>
botpress.handleError({ context, conversationId: undefined }, thrown)

const githubLabel = await linear
.findLabel({ name: 'github', parentName: 'origin' })
.catch(_handleError('trying to find the origin/github label in Linear'))
const teamStates = await linear
.findTeamStates(TEAM_KEY_FOR_NEW_ISSUES)
.catch(_handleError('trying to get Linear team states'))

if (!githubLabel) {
props.logger.error('Label origin/github not found in engineering team')
if (!teamStates) {
props.logger.error(`Error: Linear team '${TEAM_KEY_FOR_NEW_ISSUES}' not found.`)
return
}

const teams = await linear.getTeamRecords().catch(_handleError('trying to get Linear teams'))
const states = await linear.getStateRecords().catch(_handleError('trying to get Linear states'))
const state = teamStates.states.nodes.find((el) => el.name === STATE_NAME_FOR_NEW_ISSUES)

if (!state) {
props.logger.error(`Error: Linear state '${STATE_NAME_FOR_NEW_ISSUES}' not found.`)
return
}

const linearResponse = await linear.client
.createIssue({
teamId: teams.ENG.id,
stateId: states.ENG.TRIAGE.id,
teamId: teamStates.id,
stateId: state.id,
title: githubIssue.issue.name,
description: githubIssue.issue.body,
labelIds: githubLabel ? [githubLabel.id] : [],
labelIds: [],
})
.catch(_handleError('trying to create a Linear issue from the GitHub issue'))

Expand Down
1 change: 0 additions & 1 deletion bots/bugbuster/src/utils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * as string from './string-utils'
export * as promise from './promise-utils'
export * as linear from './linear-utils'
export * as botpress from './botpress-utils'
73 changes: 19 additions & 54 deletions bots/bugbuster/src/utils/linear-utils/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@ import * as genenv from '../../../.genenv'
import * as types from '../../types'
import * as graphql from './graphql-queries'

const TEAM_KEYS = ['SQD', 'FT', 'BE', 'ENG'] as const
type TeamKey = (typeof TEAM_KEYS)[number]

type State = { state: lin.WorkflowState; key: types.StateKey }

const RESULTS_PER_PAGE = 200
Expand Down Expand Up @@ -133,67 +130,35 @@ export class LinearApi {
await Promise.all(promises)
}

public async getTeams(): Promise<lin.Team[]> {
if (!this._teams) {
this._teams = await LinearApi._listAllTeams(this._client)
public async findTeamStates(teamKey: string): Promise<graphql.TeamStates | undefined> {
const queryInput: graphql.GRAPHQL_QUERIES['findTeamStates'][graphql.QUERY_INPUT] = {
filter: { key: { eq: teamKey } },
}
return this._teams

const data = await this._executeGraphqlQuery('findTeamStates', queryInput)

const [team] = data.organization.teams.nodes
if (!team) {
return undefined
}
return team
}

public async getTeamRecords(): Promise<Record<TeamKey, lin.Team>> {
public async getTeams(): Promise<lin.Team[]> {
if (!this._teams) {
this._teams = await LinearApi._listAllTeams(this._client)
this._teams = await this._listAllTeams()
}
const safeTeams = this._teams

return new Proxy({} as Record<TeamKey, lin.Team>, {
get: async (_, key: TeamKey): Promise<lin.Team> => {
const team = safeTeams.find((t) => t.key === key)
if (!team) {
throw new Error(`Team with key "${key}" not found.`)
}
return team
},
})
return this._teams
}

public async getStates(): Promise<State[]> {
if (!this._states) {
const states = await LinearApi._listAllStates(this._client)
const states = await this._listAllStates()
this._states = LinearApi._toStateObjects(states)
}
return this._states
}

public async getStateRecords(): Promise<Record<TeamKey, Record<types.StateKey, lin.WorkflowState>>> {
if (!this._states) {
const states = await LinearApi._listAllStates(this._client)
this._states = LinearApi._toStateObjects(states)
}
const safeStates = this._states
const teams = await this.getTeamRecords()

return new Proxy({} as Record<TeamKey, Record<types.StateKey, lin.WorkflowState>>, {
get: (_, teamKey: TeamKey) => {
const teamId = teams[teamKey].id
if (!teamId) {
throw new Error(`Team with key "${teamKey}" not found.`)
}

return new Proxy({} as Record<types.StateKey, lin.WorkflowState>, {
get: (_, stateKey: types.StateKey) => {
const state = safeStates.find((s) => s.key === stateKey && s.state.teamId === teamId)

if (!state) {
throw new Error(`State with key "${stateKey}" not found.`)
}
return state
},
})
},
})
}

private async _stateKeysToStates(keys: types.StateKey[]) {
const states = await this.getStates()
return keys?.map((key) => {
Expand All @@ -205,22 +170,22 @@ export class LinearApi {
})
}

private static _listAllTeams = async (client: lin.LinearClient): Promise<lin.Team[]> => {
private _listAllTeams = async (): Promise<lin.Team[]> => {
let teams: lin.Team[] = []
let cursor: string | undefined = undefined
do {
const response = await client.teams({ after: cursor, first: 100 })
const response = await this._client.teams({ after: cursor, first: 100 })
teams = teams.concat(response.nodes)
cursor = response.pageInfo.endCursor
} while (cursor)
return teams
}

private static _listAllStates = async (client: lin.LinearClient): Promise<lin.WorkflowState[]> => {
private _listAllStates = async (): Promise<lin.WorkflowState[]> => {
let states: lin.WorkflowState[] = []
let cursor: string | undefined = undefined
do {
const response = await client.workflowStates({ after: cursor, first: 100 })
const response = await this._client.workflowStates({ after: cursor, first: 100 })
states = states.concat(response.nodes)
cursor = response.pageInfo.endCursor
} while (cursor)
Expand Down
49 changes: 49 additions & 0 deletions bots/bugbuster/src/utils/linear-utils/graphql-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export type Issue = {
}
}

export type TeamStates = {
id: string
states: {
nodes: {
id: string
name: string
}[]
}
}

export type Pagination = {
hasNextPage: boolean
endCursor: string
Expand Down Expand Up @@ -130,6 +140,45 @@ export const GRAPHQL_QUERIES = {
pageInfo: Pagination
},
},
findTeamStates: {
query: `
query GetAllTeams($filter: TeamFilter) {
organization {
teams(filter: $filter) {
nodes {
id
key
states {
nodes {
id
name
}
}
}
}
}
}`,
[QUERY_INPUT]: {} as {
filter: {
key?: { eq: string }
}
},
[QUERY_RESPONSE]: {} as {
organization: {
teams: {
nodes: {
id: string
states: {
nodes: {
id: string
name: string
}[]
}
}[]
}
}
},
},
} as const satisfies Record<string, GraphQLQuery<object, object>>

export type GRAPHQL_QUERIES = typeof GRAPHQL_QUERIES
Expand Down
8 changes: 0 additions & 8 deletions bots/bugbuster/src/utils/promise-utils.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/llmz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "llmz",
"type": "module",
"description": "LLMz – An LLM-native Typescript VM built on top of Zui",
"version": "0.0.35",
"version": "0.0.36",
"types": "./dist/index.d.ts",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
Expand Down
Loading
Loading