chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import rootConfig from '../../eslint.config.mjs'
|
||||
|
||||
export default [
|
||||
...rootConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,37 @@
|
||||
Streamline your project management workflows and enhance productivity by integrating Botpress with ClickUp. This integration empowers your chatbot to interact seamlessly with your ClickUp account, automating task creation, retrieving updates, and managing your workspaces. Elevate your team's efficiency by allowing your chatbot to access and manipulate your ClickUp data, enabling faster decision-making and better organization. With this integration, your chatbot can help manage projects, assign tasks, and keep your team informed.
|
||||
|
||||
## Configuration
|
||||
|
||||
To use the ClickUp integration, you need to set up authentication and provide essential details from your ClickUp account.
|
||||
|
||||
### Obtaining Your API Key and Team ID
|
||||
|
||||
1. **Generate an API Key:**
|
||||
|
||||
- Log in to your ClickUp account.
|
||||
- Go to your profile settings and select the **Apps** section.
|
||||
- Generate or retrieve your API key.
|
||||
|
||||
2. **Find Your Team ID:**
|
||||
- Navigate to the left menu bar of your ClickUp workspace.
|
||||
- Copy the URL of the workspace (e.g., `https://app.clickup.com/9011669285/v/s/90112461548`).
|
||||
- The **last ID in the URL** is your team ID (in this case, `90112461548`).
|
||||
|
||||
### Configuring the Integration in Botpress
|
||||
|
||||
1. Navigate to the **Integrations** section in your Botpress workspace.
|
||||
2. Select the **ClickUp Integration** and open the configuration panel.
|
||||
3. Enter the following details:
|
||||
- Your ClickUp API key.
|
||||
- Your team ID.
|
||||
4. Save the configuration and enable the integration.
|
||||
5. Once configured, the chatbot will be able to interact with your ClickUp workspace.
|
||||
|
||||
## Limitations
|
||||
|
||||
The ClickUp integration is subject to the platform's API limitations, including rate limits and payload size restrictions. Key constraints include:
|
||||
|
||||
- **Rate Limits:** Each workspace is limited to 100 requests per minute and 10 requests per second.
|
||||
- **Payload Size:** API calls may have size restrictions, so ensure data sent in requests adheres to ClickUp's specifications.
|
||||
|
||||
Refer to the [ClickUp API documentation on rate limits](https://clickup.com/api/developer-portal/rate-limits/) for more details and best practices to avoid exceeding these limits.
|
||||
@@ -0,0 +1,33 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 54.8 65.8" style="enable-background:new 0 0 54.8 65.8;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_1_);}
|
||||
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:url(#SVGID_2_);}
|
||||
</style>
|
||||
<metadata>
|
||||
<sfw xmlns="ns_sfw;">
|
||||
<slices>
|
||||
</slices>
|
||||
<sliceSourceBounds bottomLeftOrigin="true" height="65.8" width="54.8" x="0.8" y="2.9">
|
||||
</sliceSourceBounds>
|
||||
</sfw>
|
||||
</metadata>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="0" y1="15.0492" x2="54.8446" y2="15.0492" gradientTransform="matrix(1 0 0 -1 0 69.3604)">
|
||||
<stop offset="0" style="stop-color:#8930FD">
|
||||
</stop>
|
||||
<stop offset="1" style="stop-color:#49CCF9">
|
||||
</stop>
|
||||
</linearGradient>
|
||||
<path class="st0" d="M0,50.6l10.1-7.8c5.4,7,11.1,10.3,17.4,10.3c6.3,0,11.9-3.2,17-10.2l10.3,7.6c-7.4,10-16.6,15.3-27.3,15.3
|
||||
C16.9,65.8,7.6,60.5,0,50.6z">
|
||||
</path>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="1.1953" y1="53.166" x2="53.7447" y2="53.166" gradientTransform="matrix(1 0 0 -1 0 69.3604)">
|
||||
<stop offset="0" style="stop-color:#FF02F0">
|
||||
</stop>
|
||||
<stop offset="1" style="stop-color:#FFC800">
|
||||
</stop>
|
||||
</linearGradient>
|
||||
<path class="st1" d="M27.5,16.9l-18,15.5l-8.3-9.7L27.6,0l26.2,22.7l-8.4,9.6L27.5,16.9z">
|
||||
</path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,165 @@
|
||||
import { IntegrationDefinition, z } from '@botpress/sdk'
|
||||
|
||||
export default new IntegrationDefinition({
|
||||
name: 'clickup',
|
||||
title: 'ClickUp',
|
||||
version: '0.0.3',
|
||||
description: 'Create and update tasks, and add comments from your chatbot.',
|
||||
readme: 'hub.md',
|
||||
icon: 'icon.svg',
|
||||
configuration: {
|
||||
schema: z.object({
|
||||
apiKey: z.string().title('API Key').describe('API Key for Click Up'),
|
||||
teamId: z.string().title('Team ID').describe('Team ID to use for Click Up'),
|
||||
}),
|
||||
},
|
||||
events: {
|
||||
taskCreated: {
|
||||
title: 'Task Created',
|
||||
description: 'Triggered when a task is created',
|
||||
schema: z.object({ id: z.string().title('Task ID').describe('ID of the created task') }),
|
||||
},
|
||||
taskUpdated: {
|
||||
title: 'Task Updated',
|
||||
description: 'Triggered when a task is updated',
|
||||
schema: z.object({ id: z.string().title('Task ID').describe('ID of the updated task') }),
|
||||
},
|
||||
taskDeleted: {
|
||||
title: 'Task Deleted',
|
||||
description: 'Triggered when a task is deleted',
|
||||
schema: z.object({ id: z.string().title('Task ID').describe('ID of the deleted task') }),
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
createTask: {
|
||||
title: 'Create a task',
|
||||
description: 'Create a new task in a list',
|
||||
input: {
|
||||
schema: z.object({
|
||||
listId: z.string().title('List ID').describe('ID of the list to create the task in'),
|
||||
name: z.string().title('Name').describe('Name of the task to be created'),
|
||||
description: z.string().title('Description').optional().describe('Description of the task to be created'),
|
||||
status: z.string().optional().title('Status').describe('Status of the task to be created'),
|
||||
assignees: z
|
||||
.array(z.number())
|
||||
.optional()
|
||||
.title('Assignees')
|
||||
.describe('IDs of the assignees of the task to be created'),
|
||||
dueDate: z.string().datetime().optional().title('Due date').describe('Due date of the task to be created'),
|
||||
tags: z.array(z.string()).optional().title('Tags').describe('Tags of the task to be created'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
taskId: z.string().title('Task ID').describe('Id of the task created'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
updateTask: {
|
||||
title: 'Update a task',
|
||||
description: 'Update the details of a task',
|
||||
input: {
|
||||
schema: z.object({
|
||||
taskId: z.string().title('Task ID').describe('ID of the task to be updated'),
|
||||
name: z.string().optional().title('Name').describe('New name of the task'),
|
||||
description: z.string().optional().title('Description').describe('New description of the task'),
|
||||
status: z.string().optional().title('Status').describe('New status of the task'),
|
||||
archived: z.boolean().optional().title('Archived').describe('New archived status of the task'),
|
||||
assigneesToAdd: z
|
||||
.array(z.number())
|
||||
.optional()
|
||||
.title('Assignees to add')
|
||||
.describe('Members to add to the task'),
|
||||
assigneesToRemove: z
|
||||
.array(z.number())
|
||||
.optional()
|
||||
.title('Assignees to remove')
|
||||
.describe('Members to remove from the task'),
|
||||
dueDate: z.string().datetime().optional().title('Due date').describe('New due date of the task'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
taskId: z.string().title('Task ID').describe('Id of the task updated'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
deleteTask: {
|
||||
title: 'Delete a task',
|
||||
description: 'Delete a task',
|
||||
input: {
|
||||
schema: z.object({
|
||||
taskId: z.string().title('Task ID').describe('ID of the task to be deleted'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({}),
|
||||
},
|
||||
},
|
||||
getListMembers: {
|
||||
title: 'Get members of a list',
|
||||
description: 'Get all the members of a list',
|
||||
input: {
|
||||
schema: z.object({
|
||||
listId: z.string().title('List ID').describe('ID of the list to get the members of'),
|
||||
}),
|
||||
},
|
||||
output: {
|
||||
schema: z.object({
|
||||
members: z
|
||||
.array(
|
||||
z.object({
|
||||
id: z.number().title('Member ID').describe('ID of the member'),
|
||||
username: z.string().title('Username').describe('Username of the member'),
|
||||
email: z.string().title('Email').describe('Email of the member'),
|
||||
})
|
||||
)
|
||||
.title('Members')
|
||||
.describe('Members of the list'),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
channels: {
|
||||
comment: {
|
||||
title: 'Comment',
|
||||
description: 'Comments on a task',
|
||||
messages: {
|
||||
text: {
|
||||
schema: z.object({ text: z.string().title('Text').describe('Content of the comment') }),
|
||||
},
|
||||
},
|
||||
message: {
|
||||
tags: {
|
||||
id: {
|
||||
title: 'Message ID',
|
||||
description: 'Message ID from ClickUp',
|
||||
},
|
||||
},
|
||||
},
|
||||
conversation: {
|
||||
tags: {
|
||||
taskId: {
|
||||
title: 'Task ID',
|
||||
description: 'Task ID from ClickUp',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
user: {
|
||||
tags: {
|
||||
id: {
|
||||
title: 'User ID',
|
||||
description: 'User ID from ClickUp',
|
||||
},
|
||||
},
|
||||
},
|
||||
attributes: {
|
||||
category: 'Project Management',
|
||||
repo: 'botpress',
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@botpresshub/clickup",
|
||||
"scripts": {
|
||||
"check:type": "tsc --noEmit",
|
||||
"check:bplint": "bp lint",
|
||||
"build": "bp add -y && bp build",
|
||||
"test": "vitest --run"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@botpress/client": "workspace:*",
|
||||
"@botpress/common": "workspace:*",
|
||||
"@botpress/sdk": "workspace:*",
|
||||
"axios": "^1.7.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@botpress/cli": "workspace:*",
|
||||
"@botpress/common": "workspace:*",
|
||||
"@botpress/sdk": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { createActionWrapper, createAsyncFnWrapperWithErrorRedaction } from '@botpress/common'
|
||||
import * as sdk from '@botpress/sdk'
|
||||
import { ClickUpClient } from '../client'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const wrapAction: typeof _wrapAction = (meta, actionImpl) =>
|
||||
_wrapAction(meta, (props) =>
|
||||
_wrapAsyncFnWithTryCatch(() => {
|
||||
props.logger
|
||||
.forBot()
|
||||
.debug(`Running action "${meta.actionName}" [bot id: ${props.ctx.botId}]`, { input: props.input })
|
||||
|
||||
return actionImpl(props as Parameters<typeof actionImpl>[0], props.input)
|
||||
}, `Action Error: ${meta.errorMessage}`)()
|
||||
)
|
||||
|
||||
const _wrapAction = createActionWrapper<bp.IntegrationProps>()({
|
||||
toolFactories: {
|
||||
clickupClient: ({ ctx }) => new ClickUpClient(ctx.configuration.apiKey, ctx.configuration.teamId),
|
||||
},
|
||||
extraMetadata: {} as {
|
||||
errorMessage: string
|
||||
},
|
||||
})
|
||||
|
||||
const _wrapAsyncFnWithTryCatch = createAsyncFnWrapperWithErrorRedaction((error: Error, customMessage: string) => {
|
||||
if (error instanceof sdk.RuntimeError) {
|
||||
return error
|
||||
}
|
||||
|
||||
console.warn(customMessage, error)
|
||||
return new sdk.RuntimeError(customMessage)
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
import { wrapAction } from './action-wrapper'
|
||||
|
||||
export const createTask = wrapAction(
|
||||
{ actionName: 'createTask', errorMessage: 'Failed to create a new task' },
|
||||
async ({ clickupClient }, input) => {
|
||||
const { dueDate, ...rest } = input
|
||||
const task = await clickupClient.createTask({
|
||||
...rest,
|
||||
dueDate: dueDate ? new Date(dueDate).getTime() : undefined,
|
||||
})
|
||||
return { taskId: task.id.toString() }
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,6 @@
|
||||
import { wrapAction } from './action-wrapper'
|
||||
|
||||
export const deleteTask = wrapAction(
|
||||
{ actionName: 'deleteTask', errorMessage: 'Failed to delete the task' },
|
||||
async ({ clickupClient }, input) => await clickupClient.deleteTask(input)
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
import { wrapAction } from './action-wrapper'
|
||||
|
||||
export const getListMembers = wrapAction(
|
||||
{ actionName: 'getListMembers', errorMessage: 'Failed to create a new task' },
|
||||
async ({ clickupClient }, input) => {
|
||||
const members = await clickupClient.getListMembers(input)
|
||||
return { members }
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
import { createTask } from './createTask'
|
||||
import { deleteTask } from './deleteTask'
|
||||
import { getListMembers } from './getListMembers'
|
||||
import { updateTask } from './updateTask'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export default {
|
||||
createTask,
|
||||
updateTask,
|
||||
deleteTask,
|
||||
getListMembers,
|
||||
} as const satisfies bp.IntegrationProps['actions']
|
||||
@@ -0,0 +1,14 @@
|
||||
import { wrapAction } from './action-wrapper'
|
||||
|
||||
export const updateTask = wrapAction(
|
||||
{ actionName: 'updateTask', errorMessage: 'Failed to update the task' },
|
||||
async ({ clickupClient }, input) => {
|
||||
const { dueDate, assigneesToAdd, assigneesToRemove, ...rest } = input
|
||||
const task = await clickupClient.updateTask({
|
||||
...rest,
|
||||
assignees: { add: assigneesToAdd ?? [], rem: assigneesToRemove ?? [] },
|
||||
dueDate: dueDate ? new Date(dueDate).getTime() : undefined,
|
||||
})
|
||||
return { taskId: task.id.toString() }
|
||||
}
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ClickUpClient } from './client'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const channels: bp.IntegrationProps['channels'] = {
|
||||
comment: {
|
||||
messages: {
|
||||
text: async ({ ctx, payload, ack, conversation }) => {
|
||||
const clickup = new ClickUpClient(ctx.configuration.apiKey, ctx.configuration.teamId)
|
||||
const comment = await clickup.createComment({ text: payload.text, taskId: conversation.tags.taskId! })
|
||||
await ack({ tags: { id: comment.id.toString() } })
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import axios, { AxiosInstance } from 'axios'
|
||||
const baseURL = 'https://api.clickup.com/api/v2'
|
||||
|
||||
export class ClickUpClient {
|
||||
private _axios: AxiosInstance
|
||||
|
||||
public constructor(
|
||||
private _token: string,
|
||||
private _teamId: string
|
||||
) {
|
||||
this._axios = axios.create({
|
||||
baseURL,
|
||||
headers: { Authorization: _token },
|
||||
})
|
||||
}
|
||||
|
||||
public async getUser() {
|
||||
const { data } = await this._axios.get('/user')
|
||||
return data.user
|
||||
}
|
||||
|
||||
public async listWebhooks() {
|
||||
const { data } = await this._axios.get(`/team/${this._teamId}/webhook`)
|
||||
return data.webhooks
|
||||
}
|
||||
|
||||
public async createWebhook(body: { endpoint: string; events: string[] }) {
|
||||
const { data } = await this._axios.post(`/team/${this._teamId}/webhook`, body)
|
||||
return data
|
||||
}
|
||||
|
||||
public async updateWebhook({
|
||||
webhookId,
|
||||
...body
|
||||
}: {
|
||||
webhookId: string
|
||||
endpoint: string
|
||||
events: string[]
|
||||
status: 'active'
|
||||
}) {
|
||||
const { data } = await this._axios.put(`/webhook/${webhookId}`, body)
|
||||
return data
|
||||
}
|
||||
|
||||
public async createComment({ taskId, text }: { taskId: string; text: string }) {
|
||||
const user = await this.getUser()
|
||||
const { data } = await this._axios.post(`/task/${taskId}/comment`, {
|
||||
comment_text: text,
|
||||
notify_all: false,
|
||||
assignee: user.id,
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
public async createTask({
|
||||
name,
|
||||
listId,
|
||||
description,
|
||||
status,
|
||||
assignees,
|
||||
dueDate,
|
||||
tags,
|
||||
}: {
|
||||
name: string
|
||||
listId: string
|
||||
description?: string
|
||||
status?: string
|
||||
assignees?: number[]
|
||||
dueDate?: number
|
||||
tags?: string[]
|
||||
}) {
|
||||
const { data } = await this._axios.post(`/list/${listId}/task`, {
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
assignees,
|
||||
due_date: dueDate,
|
||||
tags,
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
public async getTask(taskId: string) {
|
||||
const { data } = await this._axios.get(`/task/${taskId}`)
|
||||
return data
|
||||
}
|
||||
|
||||
public async updateTask({
|
||||
taskId,
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
archived,
|
||||
assignees,
|
||||
dueDate,
|
||||
}: {
|
||||
taskId: string
|
||||
name?: string
|
||||
description?: string
|
||||
status?: string
|
||||
archived?: boolean
|
||||
assignees?: { add: number[]; rem: number[] }
|
||||
dueDate?: number
|
||||
}) {
|
||||
const { data } = await this._axios.put(`/task/${taskId}`, {
|
||||
name,
|
||||
description,
|
||||
status,
|
||||
archived,
|
||||
assignees,
|
||||
due_date: dueDate,
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
public async deleteTask({ taskId }: { taskId: string }) {
|
||||
const { data } = await this._axios.delete(`/task/${taskId}`)
|
||||
return data
|
||||
}
|
||||
|
||||
public async getListMembers({ listId }: { listId: string }) {
|
||||
const { data } = await this._axios.get(`/list/${listId}/member`)
|
||||
return data.members
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { ClickUpClient } from 'src/client'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const executeCommentReceived = async ({
|
||||
clickup,
|
||||
body,
|
||||
client,
|
||||
logger,
|
||||
}: {
|
||||
clickup: ClickUpClient
|
||||
body: any
|
||||
client: bp.Client
|
||||
logger: bp.Logger
|
||||
}) => {
|
||||
const botUser = await clickup.getUser()
|
||||
|
||||
for (const historyItem of body.history_items) {
|
||||
if (botUser.id === historyItem.user.id) {
|
||||
continue
|
||||
}
|
||||
|
||||
const { user } = await client.getOrCreateUser({ tags: { id: historyItem.user.id.toString() } })
|
||||
const { conversation } = await client.getOrCreateConversation({
|
||||
tags: { taskId: body.task_id.toString() },
|
||||
channel: 'comment',
|
||||
})
|
||||
const { message } = await client.getOrCreateMessage({
|
||||
conversationId: conversation.id,
|
||||
userId: user.id,
|
||||
type: 'text',
|
||||
payload: { text: historyItem.comment.text_content },
|
||||
tags: { id: historyItem.comment.id.toString() },
|
||||
})
|
||||
|
||||
logger.forBot().info('Message created', message.payload.text)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ClickUpClient } from './client'
|
||||
import { executeCommentReceived } from './events/executeCommentReceived'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const handler: bp.IntegrationProps['handler'] = async ({ req, ctx, client, logger }) => {
|
||||
if (!req.body) {
|
||||
return
|
||||
}
|
||||
|
||||
const clickup = new ClickUpClient(ctx.configuration.apiKey, ctx.configuration.teamId)
|
||||
|
||||
let parsedBody: any
|
||||
try {
|
||||
parsedBody = JSON.parse(req.body)
|
||||
} catch {
|
||||
return {
|
||||
status: 400,
|
||||
body: JSON.stringify({ error: 'Invalid JSON Body' }),
|
||||
}
|
||||
}
|
||||
|
||||
switch (parsedBody.event) {
|
||||
case 'taskCommentPosted':
|
||||
return executeCommentReceived({ clickup, body: parsedBody, client, logger })
|
||||
case 'taskCreated':
|
||||
case 'taskUpdated':
|
||||
case 'taskDeleted':
|
||||
await client.createEvent({ type: parsedBody.event, payload: { id: parsedBody.task_id.toString() } })
|
||||
return
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import actions from './actions'
|
||||
import { channels } from './channels'
|
||||
import { ClickUpClient } from './client'
|
||||
import { handler } from './handler'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export default new bp.Integration({
|
||||
register: async ({ ctx, logger, webhookUrl }) => {
|
||||
logger.forBot().info('ClickUp integration enabled')
|
||||
const clickup = new ClickUpClient(ctx.configuration.apiKey, ctx.configuration.teamId)
|
||||
|
||||
// Fetching the user to make sure we have access to click up before registering the webhook.
|
||||
await clickup.getUser()
|
||||
await setWebhook(clickup, webhookUrl)
|
||||
},
|
||||
unregister: async () => {},
|
||||
actions,
|
||||
channels,
|
||||
handler,
|
||||
})
|
||||
|
||||
async function setWebhook(clickup: ClickUpClient, webhookUrl: string) {
|
||||
const webhooks = await clickup.listWebhooks()
|
||||
const events = ['taskCommentPosted', 'taskCreated', 'taskUpdated', 'taskDeleted']
|
||||
|
||||
for (const webhook of webhooks) {
|
||||
if (webhook.endpoint === webhookUrl) {
|
||||
await clickup.updateWebhook({
|
||||
endpoint: webhookUrl,
|
||||
status: 'active',
|
||||
webhookId: webhook.id,
|
||||
events,
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
await clickup.createWebhook({
|
||||
endpoint: webhookUrl,
|
||||
events,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"paths": { "*": ["./*"] },
|
||||
"outDir": "dist",
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": [".botpress/**/*", "definitions/**/*", "src/**/*", "*.ts"]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
import config from '../../vitest.config'
|
||||
export default config
|
||||
Reference in New Issue
Block a user