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
6 changes: 3 additions & 3 deletions integrations/hubspot/definitions/actions/company.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const companySchema = z.object({

const searchCompany: ActionDefinition = {
title: 'Search Company',
description: 'Search for a company in Hubspot',
description: 'Search for a company in HubSpot',
input: {
schema: z.object({
name: z.string().optional().title('Name').describe('The name of the company to search for'),
Expand All @@ -27,7 +27,7 @@ const searchCompany: ActionDefinition = {

const getCompany: ActionDefinition = {
title: 'Get Company',
description: 'Get a company from Hubspot by ID',
description: 'Get a company from HubSpot by ID',
input: {
schema: z.object({
companyId: z.string().title('Company ID').describe('The ID of the company to get'),
Expand All @@ -49,7 +49,7 @@ const getCompany: ActionDefinition = {

const updateCompany: ActionDefinition = {
title: 'Update Company',
description: 'Update a company in Hubspot',
description: 'Update a company in HubSpot',
input: {
schema: z.object({
companyId: z.string().title('Company ID').describe('The ID of the company to update'),
Expand Down
10 changes: 5 additions & 5 deletions integrations/hubspot/definitions/actions/deal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const dealSchema = z.object({

const searchDeal: ActionDefinition = {
title: 'Search Deal',
description: 'Search for a deal in Hubspot',
description: 'Search for a deal in HubSpot',
input: {
schema: z.object({
name: z.string().optional().title('Name').describe('The name of the deal to search for'),
Expand All @@ -25,7 +25,7 @@ const searchDeal: ActionDefinition = {

const createDeal: ActionDefinition = {
title: 'Create Deal',
description: 'Create a deal in Hubspot',
description: 'Create a deal in HubSpot',
input: {
schema: z.object({
name: z.string().title('Name').describe('The name of the deal'),
Expand All @@ -50,7 +50,7 @@ const createDeal: ActionDefinition = {

const getDeal: ActionDefinition = {
title: 'Get Deal',
description: 'Get a deal from Hubspot',
description: 'Get a deal from HubSpot',
input: {
schema: z.object({
dealId: z.string().title('Deal ID').describe('The ID of the deal to get'),
Expand All @@ -65,7 +65,7 @@ const getDeal: ActionDefinition = {

const updateDeal: ActionDefinition = {
title: 'Update Deal',
description: 'Update a deal in Hubspot',
description: 'Update a deal in HubSpot',
input: {
schema: z.object({
dealId: z.string().title('Deal ID').describe('The ID of the deal to update'),
Expand Down Expand Up @@ -97,7 +97,7 @@ const updateDeal: ActionDefinition = {

const deleteDeal: ActionDefinition = {
title: 'Delete Deal',
description: 'Delete a deal in Hubspot',
description: 'Delete a deal in HubSpot',
input: {
schema: z.object({
dealId: z.string().title('Deal ID').describe('The ID of the deal to delete'),
Expand Down
10 changes: 5 additions & 5 deletions integrations/hubspot/definitions/actions/lead.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const leadSchema = z.object({

const searchLead: ActionDefinition = {
title: 'Search Lead',
description: 'Search for a lead in Hubspot',
description: 'Search for a lead in HubSpot',
input: {
schema: z.object({
name: z.string().optional().title('Name').describe('The name of the lead to search for'),
Expand All @@ -24,7 +24,7 @@ const searchLead: ActionDefinition = {
}
const createLead: ActionDefinition = {
title: 'Create Lead',
description: 'Create a lead in Hubspot',
description: 'Create a lead in HubSpot',
input: {
schema: z.object({
name: z.string().title('Name').describe('The name of the lead'),
Expand Down Expand Up @@ -53,7 +53,7 @@ const createLead: ActionDefinition = {

const getLead: ActionDefinition = {
title: 'Get Lead',
description: 'Get a lead from Hubspot',
description: 'Get a lead from HubSpot',
input: {
schema: z.object({
leadId: z.string().title('Lead ID').describe('The ID of the lead to get'),
Expand All @@ -68,7 +68,7 @@ const getLead: ActionDefinition = {

const updateLead: ActionDefinition = {
title: 'Update Lead',
description: 'Update a lead in Hubspot',
description: 'Update a lead in HubSpot',
input: {
schema: z.object({
leadId: z.string().title('Lead ID').describe('The ID of the lead to update'),
Expand Down Expand Up @@ -100,7 +100,7 @@ const updateLead: ActionDefinition = {

const deleteLead: ActionDefinition = {
title: 'Delete Lead',
description: 'Delete a lead in Hubspot',
description: 'Delete a lead in HubSpot',
input: {
schema: z.object({
leadId: z.string().title('Lead ID').describe('The ID of the lead to delete'),
Expand Down
12 changes: 6 additions & 6 deletions integrations/hubspot/definitions/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z, EventDefinition } from '@botpress/sdk'

const contactCreated = {
title: 'Contact Created',
description: 'A new contact has been created in Hubspot.',
description: 'A new contact has been created in HubSpot.',
schema: z.object({
contactId: z.string().title('Contact ID').describe('The ID of the created contact'),
name: z.string().optional().title('Name').describe('The name of the created contact'),
Expand All @@ -13,15 +13,15 @@ const contactCreated = {

const contactDeleted = {
title: 'Contact Deleted',
description: 'A contact has been deleted in Hubspot.',
description: 'A contact has been deleted in HubSpot.',
schema: z.object({
contactId: z.string().title('Contact ID').describe('The ID of the deleted contact'),
}),
}

const companyCreated = {
title: 'Company Created',
description: 'A new company has been created in Hubspot.',
description: 'A new company has been created in HubSpot.',
schema: z.object({
companyId: z.string().title('Company ID').describe('The ID of the created company'),
name: z.string().optional().title('Name').describe('The name of the created company'),
Expand All @@ -32,15 +32,15 @@ const companyCreated = {

const companyDeleted = {
title: 'Company Deleted',
description: 'A company has been deleted in Hubspot.',
description: 'A company has been deleted in HubSpot.',
schema: z.object({
companyId: z.string().title('Company ID').describe('The ID of the deleted company'),
}),
}

const ticketCreated = {
title: 'Ticket Created',
description: 'A new ticket has been created in Hubspot.',
description: 'A new ticket has been created in HubSpot.',
schema: z.object({
ticketId: z.string().title('Ticket ID').describe('The ID of the created ticket'),
subject: z.string().optional().title('Subject').describe('The subject of the created ticket'),
Expand All @@ -53,7 +53,7 @@ const ticketCreated = {

const ticketDeleted = {
title: 'Ticket Deleted',
description: 'A ticket has been deleted in Hubspot.',
description: 'A ticket has been deleted in HubSpot.',
schema: z.object({
ticketId: z.string().title('Ticket ID').describe('The ID of the deleted ticket'),
}),
Expand Down
6 changes: 3 additions & 3 deletions integrations/hubspot/definitions/states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { z, StateDefinition } from '@botpress/sdk'
const oauthCredentials = {
type: 'integration',
schema: z.object({
accessToken: z.string().title('Access Token').describe('The access token for the Hubspot integration'),
refreshToken: z.string().title('Refresh Token').describe('The refresh token for the Hubspot integration'),
accessToken: z.string().title('Access Token').describe('The access token for the HubSpot integration'),
refreshToken: z.string().title('Refresh Token').describe('The refresh token for the HubSpot integration'),
expiresAtSeconds: z.number().title('Expires At').describe('The timestamp in seconds when the access token expires'),
}),
} satisfies StateDefinition
Expand Down Expand Up @@ -69,7 +69,7 @@ const propertyCacheStateDefinition = {
z.object({
label: z.string().title('Label').describe('The label of the property'),
type: propertyTypeSchema,
hubspotDefined: z.boolean().title('Hubspot Defined').describe('Whether the property is defined by Hubspot'),
hubspotDefined: z.boolean().title('HubSpot Defined').describe('Whether the property is defined by HubSpot'),
options: z
.array(z.string())
.optional()
Expand Down
2 changes: 1 addition & 1 deletion integrations/hubspot/hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Under the **Webhooks** tab, subscribe to:
#### 4. Get Your App ID and Developer API Key

- **App ID**: Open your private App in HubSpot again — the App ID is in the URL (e.g., `https://app.hubspot.com/private-apps/ACCOUNT_ID/36900466`).
- **Developer API Key**: In your Hubspot Dashboard, navigate to _Development_ > _Keys_ > _Developer API Key_ and copy or generate your key.
- **Developer API Key**: In your HubSpot Dashboard, navigate to _Development_ > _Keys_ > _Developer API Key_ and copy or generate your key.

#### 5. Retrieve Your Help Desk or Inbox IDs

Expand Down
8 changes: 4 additions & 4 deletions integrations/hubspot/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export default new IntegrationDefinition({
name: 'hubspot',
title: 'HubSpot',
description: 'Manage contacts, tickets and more from your chatbot.',
version: '6.0.1',
version: '6.0.4',
readme: 'hub.md',
icon: 'icon.svg',
configuration: {
Expand All @@ -18,7 +18,7 @@ export default new IntegrationDefinition({
configurations: {
manual: {
title: 'Manual Configuration',
description: 'Manual configuration, use your own Hubspot app',
description: 'Manual configuration, use your own HubSpot app',
schema: z.object({
accessToken: z
.string()
Expand Down Expand Up @@ -72,10 +72,10 @@ export default new IntegrationDefinition({
},
secrets: {
CLIENT_ID: {
description: 'The client ID of the Hubspot app',
description: 'The client ID of the HubSpot app',
},
CLIENT_SECRET: {
description: 'The client secret of the Hubspot app',
description: 'The client secret of the HubSpot app',
},
DISABLE_OAUTH: {
// TODO: Remove once the OAuth app allows for unlimited installs
Expand Down
2 changes: 1 addition & 1 deletion integrations/hubspot/linkTemplate.vrl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
webhookId = to_string!(.webhookId)
webhookUrl = to_string!(.webhookUrl)
source = to_string(.source) ?? ""
source = to_string!(.source)

if source == "desk" {
"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}&wizchoice=without-hitl"
Expand Down
10 changes: 9 additions & 1 deletion integrations/hubspot/src/webhook/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { generateRedirection } from '@botpress/common/src/html-dialogs'
import * as oauthWizard from '@botpress/common/src/oauth-wizard'
import { OAUTH_IDENTIFIER_HEADER } from '@botpress/sdk'
import { Signature } from '@hubspot/api-client'
import { getClientSecret } from '../auth'
import { handleOperatorReplied } from '../hitl/events/operator-replied'
Expand All @@ -25,7 +26,14 @@ export const handler: bp.IntegrationProps['handler'] = async (props) => {
if (req.path.startsWith('/oauth')) {
const modifiedProps = { ...props, req: { ...props.req, path: '/oauth/wizard/oauth-callback' } }
try {
return await buildOAuthWizard(modifiedProps).handleRequest()
const wizardResult = await buildOAuthWizard(modifiedProps).handleRequest()
const identifier = wizardResult.headers?.[OAUTH_IDENTIFIER_HEADER]
return identifier
? {
status: 200,
headers: { [OAUTH_IDENTIFIER_HEADER]: identifier },
}
: wizardResult
} catch (thrown: unknown) {
const errMsg = thrown instanceof Error ? thrown.message : String(thrown)
return generateRedirection(oauthWizard.getInterstitialUrl(false, errMsg))
Expand Down
10 changes: 10 additions & 0 deletions integrations/linear/definitions/states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ export const states = {
expiresAt: z.string().title('Expires At').describe('The time when the access token expires'),
}),
},
environment: {
type: 'integration',
schema: z.object({
env: z
.enum(['preview', 'production'])
.title('Environment')
.describe('The environment where the integration is installed'),
source: z.string().optional().title('Source').describe('The source of the OAuth request, eg: "desk"'),
}),
},

// TODO: delete these 2 states when the backend stop considering state deletion as breaking change
configuration: {
Expand Down
8 changes: 7 additions & 1 deletion integrations/linear/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import listable from './bp_modules/listable'
import { actions, channels, events, configuration, configurations, user, states, entities } from './definitions'

export const INTEGRATION_NAME = 'linear'
export const INTEGRATION_VERSION = '2.4.0'
export const INTEGRATION_VERSION = '2.5.0'

export default new IntegrationDefinition({
name: INTEGRATION_NAME,
Expand Down Expand Up @@ -37,6 +37,12 @@ export default new IntegrationDefinition({
CLIENT_SECRET: {
description: 'The client secret of your Linear OAuth app.',
},
DESK_CLIENT_ID: {
description: 'The client ID of your Linear OAuth app.',
},
DESK_CLIENT_SECRET: {
description: 'The client secret of your Linear OAuth app.',
},
WEBHOOK_SIGNING_SECRET: {
description: 'The signing secret of your Linear webhook.',
},
Expand Down
10 changes: 2 additions & 8 deletions integrations/linear/linkTemplate.vrl
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
webhookId = to_string!(.webhookId)
webhookUrl = to_string!(.webhookUrl)
env = to_string!(.env)
source = to_string!(.source)

clientId = "364cc18b5fd2edc8abc3b7113e6a7908"

if env == "production" {
clientId = "be8aaf51ad3d057ed5870c5729926929"
}

"https://linear.app/oauth/authorize?client_id={{ clientId }}&redirect_uri={{ webhookUrl }}/oauth&response_type=code&prompt=consent&actor=application&state={{ webhookId }}&scope=read,write,issues:create,comments:create,admin"
"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}&source={{ source }}"
36 changes: 30 additions & 6 deletions integrations/linear/src/handler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Request, RuntimeError } from '@botpress/sdk'
import { generateRedirection } from '@botpress/common/src/html-dialogs'
import * as oauthWizard from '@botpress/common/src/oauth-wizard'
import { Request, RuntimeError, OAUTH_IDENTIFIER_HEADER } from '@botpress/sdk'
import { LinearWebhookClient } from '@linear/sdk/webhooks'

import { fireIssueCreated } from './events/issueCreated'
import { fireIssueDeleted } from './events/issueDeleted'
import { fireIssueUpdated } from './events/issueUpdated'
import * as mapping from './files-readonly/mapping'
import { LinearEvent, LinearIssueEvent, handleOauth } from './misc/linear'
import { LinearEvent, LinearIssueEvent } from './misc/linear'
import { Result } from './misc/types'
import { getLinearClient, getUserAndConversation } from './misc/utils'
import { buildOAuthWizard } from './oauth-wizard'
import * as bp from '.botpress'

const LINEAR_WEBHOOK_SIGNATURE_HEADER = 'linear-signature'
Expand All @@ -21,12 +24,33 @@ export const handler: bp.IntegrationProps['handler'] = async (props) => {
`Linear handler invoked (method="${req.method ?? ''}", path="${req.path ?? ''}", hasBody=${Boolean(req.body)})`
)

if (oauthWizard.isOAuthWizardUrl(req.path)) {
try {
return await buildOAuthWizard(props).handleRequest()
} catch (thrown: unknown) {
const errMsg = thrown instanceof Error ? thrown.message : String(thrown)
logger.forBot().error('Error while processing OAuth wizard request', errMsg)
return generateRedirection(oauthWizard.getInterstitialUrl(false, errMsg))
}
}

if (req.path === '/oauth') {
logger.forBot().info('Linear OAuth callback received')
return await handleOauth(props).catch((err) => {
logger.forBot().error('Error while processing OAuth', err.response?.data || err.message)
throw err
})
const modifiedProps = { ...props, req: { ...props.req, path: '/oauth/wizard/oauth-callback' } }
try {
const wizardResult = await buildOAuthWizard(modifiedProps).handleRequest()
const identifier = wizardResult.headers?.[OAUTH_IDENTIFIER_HEADER]
return identifier
? {
status: 200,
headers: { [OAUTH_IDENTIFIER_HEADER]: identifier },
}
: wizardResult
} catch (thrown: unknown) {
const errMsg = thrown instanceof Error ? thrown.message : String(thrown)
logger.forBot().error('Error while processing OAuth callback', errMsg)
return generateRedirection(oauthWizard.getInterstitialUrl(false, errMsg))
}
}

if (!req.body) {
Expand Down
Loading
Loading