chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
import * as sdk from '@botpress/sdk'
|
||||
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
|
||||
import axiosRetry from 'axios-retry'
|
||||
import type { ZendeskUser, ZendeskTicket, ZendeskWebhook } from './definitions/schemas'
|
||||
import { summarizeAxiosError } from './misc/axios-utils'
|
||||
import { ConditionsData, getTriggerTemplate, type TriggerNames } from './triggers'
|
||||
import type { ZendeskEventType } from './webhookEvents'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export type TicketRequester =
|
||||
| {
|
||||
name?: string
|
||||
email?: string
|
||||
}
|
||||
| {
|
||||
id: string
|
||||
}
|
||||
|
||||
export type Trigger = {
|
||||
url: string
|
||||
id: string
|
||||
}
|
||||
|
||||
const _makeBaseUrl = (organizationDomain: string) => {
|
||||
return organizationDomain.startsWith('https') ? organizationDomain : `https://${organizationDomain}.zendesk.com`
|
||||
}
|
||||
|
||||
type AxiosRetryClient = Parameters<typeof axiosRetry>[0]
|
||||
type ZendeskConfig = {
|
||||
type: 'OAuth'
|
||||
accessToken: string
|
||||
subdomain: string
|
||||
}
|
||||
|
||||
class ZendeskApi {
|
||||
private _client: AxiosInstance
|
||||
private _logger: bp.Logger
|
||||
public constructor(config: ZendeskConfig, logger: bp.Logger) {
|
||||
this._logger = logger
|
||||
this._client = axios.create({
|
||||
baseURL: _makeBaseUrl(config.subdomain),
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.accessToken}`,
|
||||
},
|
||||
})
|
||||
|
||||
axiosRetry(this._client as AxiosRetryClient, {
|
||||
retries: 3,
|
||||
retryDelay: axiosRetry.exponentialDelay,
|
||||
retryCondition: (error) => {
|
||||
const rateLimitReached = error.response?.status === 429
|
||||
return axiosRetry.isNetworkOrIdempotentRequestError(error) || rateLimitReached
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
public async findCustomers(query: string): Promise<ZendeskUser[]> {
|
||||
const { data } = await this._client
|
||||
.get<{ users: ZendeskUser[] }>(`/api/v2/users/search.json?query=${query}`)
|
||||
.catch(this._summarizeAxiosError)
|
||||
return data.users
|
||||
}
|
||||
|
||||
public async getTicket(ticketId: string) {
|
||||
const { data } = await this._client
|
||||
.get<{ ticket: ZendeskTicket }>(`/api/v2/tickets/${ticketId}.json`)
|
||||
.catch(this._summarizeAxiosError)
|
||||
return data.ticket
|
||||
}
|
||||
|
||||
public async createTicket(
|
||||
subject: string,
|
||||
comment: string,
|
||||
requester: TicketRequester,
|
||||
extraFields: Partial<ZendeskTicket> = {}
|
||||
): Promise<ZendeskTicket> {
|
||||
const requesterPayload = 'id' in requester ? { requester_id: requester.id } : { requester }
|
||||
|
||||
const { data } = await this._client
|
||||
.post<{ ticket: ZendeskTicket }>('/api/v2/tickets.json', {
|
||||
ticket: {
|
||||
subject,
|
||||
comment: { body: comment },
|
||||
...requesterPayload,
|
||||
...extraFields,
|
||||
},
|
||||
})
|
||||
.catch(this._summarizeAxiosError)
|
||||
|
||||
return data.ticket
|
||||
}
|
||||
|
||||
public async subscribeWebhook(webhookUrl: string): Promise<string> {
|
||||
const { data } = await this._client
|
||||
.post<{ webhook: { id: string } }>('/api/v2/webhooks', {
|
||||
webhook: {
|
||||
name: 'bpc_integration_webhook',
|
||||
status: 'active',
|
||||
endpoint: webhookUrl,
|
||||
http_method: 'POST',
|
||||
request_format: 'json',
|
||||
subscriptions: ['conditional_ticket_events'],
|
||||
},
|
||||
})
|
||||
.catch(this._summarizeAxiosError)
|
||||
|
||||
return data.webhook?.id
|
||||
}
|
||||
|
||||
public async createTrigger(name: TriggerNames, subscriptionId: string, conditions: ConditionsData): Promise<string> {
|
||||
const { data } = await this._client
|
||||
.post<{ trigger: Trigger }>('/api/v2/triggers.json', {
|
||||
trigger: {
|
||||
actions: [
|
||||
{
|
||||
field: 'notification_webhook',
|
||||
value: [subscriptionId, JSON.stringify(getTriggerTemplate(name), null, 2)],
|
||||
},
|
||||
],
|
||||
conditions,
|
||||
title: `bpc_${name}`,
|
||||
},
|
||||
})
|
||||
.catch(this._summarizeAxiosError)
|
||||
|
||||
return `${data.trigger.id}`
|
||||
}
|
||||
|
||||
public async deleteTrigger(triggerId: string): Promise<void> {
|
||||
await this._client.delete(`/api/v2/triggers/${triggerId}.json`).catch(this._summarizeAxiosError)
|
||||
}
|
||||
|
||||
public async unsubscribeWebhook(subscriptionId: string): Promise<void> {
|
||||
await this._client.delete(`/api/v2/webhooks/${subscriptionId}`).catch(this._summarizeAxiosError)
|
||||
}
|
||||
|
||||
public async createPlaintextComment(ticketId: string, authorId: string, content: string) {
|
||||
const response = await this.updateTicket(ticketId, {
|
||||
comment: {
|
||||
body: content,
|
||||
author_id: authorId,
|
||||
},
|
||||
}).catch(this._summarizeAxiosError)
|
||||
const zendeskCommentId = this._extractCommentId(response)
|
||||
return { zendeskCommentId }
|
||||
}
|
||||
|
||||
private _extractCommentId(response: Awaited<ReturnType<typeof this.updateTicket>>) {
|
||||
const commentId = response.audit.events.find((event) => event.type === 'Comment' && 'id' in event)?.id
|
||||
|
||||
if (!commentId) {
|
||||
throw new sdk.RuntimeError('Failed to retrieve comment ID')
|
||||
}
|
||||
|
||||
return commentId
|
||||
}
|
||||
|
||||
public async updateTicket(ticketId: string | number, updateFields: Partial<ZendeskTicket>) {
|
||||
const response = await this._client
|
||||
.put<{
|
||||
ticket: ZendeskTicket
|
||||
audit: {
|
||||
events: (
|
||||
| {
|
||||
id: number
|
||||
type: 'Comment'
|
||||
}
|
||||
| { type: string }
|
||||
)[]
|
||||
}
|
||||
}>(`/api/v2/tickets/${ticketId}.json`, {
|
||||
ticket: updateFields,
|
||||
})
|
||||
.catch(this._summarizeAxiosError)
|
||||
return response.data
|
||||
}
|
||||
|
||||
public async getAgents(online?: boolean): Promise<ZendeskUser[]> {
|
||||
const { data } = await this._client
|
||||
.get<{ users: ZendeskUser[] }>('/api/v2/users.json?role[]=agent&role[]=admin')
|
||||
.catch(this._summarizeAxiosError)
|
||||
return online ? data.users.filter((user) => user.user_fields?.availability === 'online') : data.users
|
||||
}
|
||||
|
||||
public async createOrUpdateUser(fields: Partial<ZendeskUser>): Promise<ZendeskUser> {
|
||||
const { data } = await this._client
|
||||
.post<{ user: ZendeskUser }>('/api/v2/users/create_or_update', {
|
||||
user: fields,
|
||||
})
|
||||
.catch(this._summarizeAxiosError)
|
||||
return data.user
|
||||
}
|
||||
|
||||
public async updateUser(userId: number | string, fields: Partial<ZendeskUser>): Promise<ZendeskUser> {
|
||||
const { data } = await this._client
|
||||
.put<{ user: ZendeskUser }>(`/api/v2/users/${userId}.json`, {
|
||||
user: fields,
|
||||
})
|
||||
.catch(this._summarizeAxiosError)
|
||||
return data.user
|
||||
}
|
||||
|
||||
public async getUser(userId: number | string): Promise<ZendeskUser> {
|
||||
const { data } = await this._client
|
||||
.get<{ user: ZendeskUser }>(`/api/v2/users/${userId}.json`)
|
||||
.catch(this._summarizeAxiosError)
|
||||
return data.user
|
||||
}
|
||||
|
||||
public async createArticleWebhook(webhookUrl: string, webhookId: string): Promise<void> {
|
||||
const subscriptions: ZendeskEventType[] = ['zen:event-type:article.published', 'zen:event-type:article.unpublished']
|
||||
|
||||
await this._client
|
||||
.post('/api/v2/webhooks', {
|
||||
webhook: {
|
||||
endpoint: `${webhookUrl}/article-event`,
|
||||
http_method: 'POST',
|
||||
name: `bpc_article_event_${webhookId}`,
|
||||
request_format: 'json',
|
||||
status: 'active',
|
||||
subscriptions,
|
||||
},
|
||||
})
|
||||
.catch(this._summarizeAxiosError)
|
||||
}
|
||||
|
||||
public async deleteWebhook(webhookId: string): Promise<void> {
|
||||
await this._client.delete(`/api/v2/webhooks/${webhookId}`).catch(this._summarizeAxiosError)
|
||||
}
|
||||
|
||||
public async findWebhooks(params?: Record<string, string>): Promise<ZendeskWebhook[]> {
|
||||
const { data } = await this._client.get('/api/v2/webhooks', { params }).catch(this._summarizeAxiosError)
|
||||
|
||||
return data.webhooks
|
||||
}
|
||||
|
||||
public async makeRequest(requestConfig: AxiosRequestConfig) {
|
||||
const { data, headers, status } = await this._client.request(requestConfig).catch(this._summarizeAxiosError)
|
||||
|
||||
return {
|
||||
data,
|
||||
headers: headers as Record<string, string>,
|
||||
status,
|
||||
}
|
||||
}
|
||||
|
||||
private _summarizeAxiosError = (thrown: unknown) => summarizeAxiosError(thrown, this._logger)
|
||||
}
|
||||
|
||||
export type ZendeskClient = InstanceType<typeof ZendeskApi>
|
||||
|
||||
export const getZendeskClient = async (client: bp.Client, ctx: bp.Context, logger: bp.Logger): Promise<ZendeskApi> => {
|
||||
const { accessToken, subdomain } = await client
|
||||
.getState({ type: 'integration', name: 'credentials', id: ctx.integrationId })
|
||||
.then((result) => result.state.payload)
|
||||
if (accessToken === undefined) {
|
||||
throw new sdk.RuntimeError('Failed to get the OAuth accessToken')
|
||||
}
|
||||
if (subdomain === undefined) {
|
||||
throw new sdk.RuntimeError('Failed to get the subdomain')
|
||||
}
|
||||
|
||||
return new ZendeskApi({ type: 'OAuth', accessToken, subdomain }, logger)
|
||||
}
|
||||
Reference in New Issue
Block a user