chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import axios from 'axios'
|
||||
import http from 'http'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
type AxiosSummaryErrorProps = {
|
||||
request: {
|
||||
method: string
|
||||
url: string
|
||||
} | null
|
||||
response: {
|
||||
status: number
|
||||
statusText: string
|
||||
data: unknown
|
||||
} | null
|
||||
}
|
||||
|
||||
class AxiosSummaryError extends Error {
|
||||
public constructor(props: AxiosSummaryErrorProps) {
|
||||
const { request, response } = props
|
||||
|
||||
const messageLines = ['Zendesk API error']
|
||||
if (request) {
|
||||
messageLines.push(`Request: ${request.method} ${request.url}`)
|
||||
}
|
||||
if (response) {
|
||||
messageLines.push(`Response: ${response.status} (${response.statusText}) ${JSON.stringify(response.data)}`)
|
||||
}
|
||||
|
||||
const message = messageLines.map((l) => `\n ${l}`).join('')
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Axios Requests are too verbose and can pollute logs.
|
||||
* This function summarizes the error for better readability.
|
||||
*/
|
||||
export const summarizeAxiosError = (thrown: unknown, logger: bp.Logger): never => {
|
||||
if (!axios.isAxiosError(thrown)) {
|
||||
throw thrown
|
||||
}
|
||||
|
||||
const { request, response } = thrown
|
||||
|
||||
let parsedRequest: AxiosSummaryErrorProps['request'] = null
|
||||
try {
|
||||
if (request) {
|
||||
// for some reason request is not properly typed in axios error
|
||||
const req = request as http.ClientRequest
|
||||
const method = req.method
|
||||
const url = `${req.protocol}//${req.host}${req.path}`
|
||||
parsedRequest = {
|
||||
method,
|
||||
url,
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
let parsedResponse: AxiosSummaryErrorProps['response'] = null
|
||||
if (response) {
|
||||
const status = response.status
|
||||
const statusText = response.statusText
|
||||
const data = response.data
|
||||
parsedResponse = {
|
||||
status,
|
||||
statusText,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
logger.forBotOnly().error('Zendesk API request failed', { request: parsedRequest, response: parsedResponse })
|
||||
throw new AxiosSummaryError({
|
||||
request: parsedRequest,
|
||||
response: parsedResponse,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { getZendeskClient } from 'src/client'
|
||||
import { ZendeskArticle } from 'src/definitions/schemas'
|
||||
import { getUploadArticlePayload } from 'src/misc/utils'
|
||||
import { Client, Context, Logger } from '.botpress'
|
||||
|
||||
export const uploadArticlesToKb = async (props: { ctx: Context; client: Client; logger: Logger; kbId: string }) => {
|
||||
const { ctx, client: bpClient, logger, kbId } = props
|
||||
|
||||
logger.forBot().info('Attempting to sync Zendesk KB to BP KB')
|
||||
|
||||
const fetchedArticles: ZendeskArticle[] = []
|
||||
|
||||
try {
|
||||
const fetchArticles = async (url: string) => {
|
||||
const zendeskClient = await getZendeskClient(bpClient, ctx, logger)
|
||||
const response: { data: { articles: ZendeskArticle[]; next_page?: string } } = await zendeskClient.makeRequest({
|
||||
method: 'get',
|
||||
url,
|
||||
})
|
||||
|
||||
const { articles, next_page } = response.data
|
||||
|
||||
fetchedArticles.push(...articles)
|
||||
|
||||
if (next_page) {
|
||||
await fetchArticles(next_page)
|
||||
}
|
||||
}
|
||||
await fetchArticles('/api/v2/help_center/articles')
|
||||
} catch (error) {
|
||||
logger
|
||||
.forBot()
|
||||
.error(`Error fetching Zendesk articles: ${error instanceof Error ? error.message : 'An unknown error occurred'}`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
for (const article of fetchedArticles) {
|
||||
if (article.draft || !article.body) {
|
||||
logger.forBot().info(`Article "${article.title}" is either unpublished or empty. Skipping...`)
|
||||
continue
|
||||
}
|
||||
|
||||
const payload = getUploadArticlePayload({ kbId, article })
|
||||
|
||||
await bpClient.uploadFile(payload)
|
||||
|
||||
logger.forBot().info(`Successfully uploaded article ${article.title} to BP KB`)
|
||||
}
|
||||
} catch (error) {
|
||||
logger
|
||||
.forBot()
|
||||
.error(
|
||||
`Error uploading article to BP KB: ${error instanceof Error ? error.message : 'An unknown error occurred'}`
|
||||
)
|
||||
logger.forBot().error(JSON.stringify(error))
|
||||
return
|
||||
}
|
||||
|
||||
logger.forBot().info('Successfully synced Zendesk KB to BP KB')
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ZendeskArticle } from 'src/definitions/schemas'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const stringifyProperties = (obj: Record<string, any>) => {
|
||||
const result: Record<string, string> = {}
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
result[key] = JSON.stringify(value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
export const getUploadArticlePayload = ({ kbId, article }: { kbId: string; article: ZendeskArticle }) => {
|
||||
const { body, id, title, locale, label_names } = article
|
||||
|
||||
return {
|
||||
key: `${kbId}/${id}.html`,
|
||||
accessPolicies: [],
|
||||
content: body,
|
||||
index: true,
|
||||
tags: {
|
||||
source: 'knowledge-base',
|
||||
kbId,
|
||||
title,
|
||||
locale,
|
||||
labels: label_names.join(' '),
|
||||
zendeskId: `${id}`,
|
||||
},
|
||||
}
|
||||
}
|
||||
export const deleteKbArticles = async (kbId: string, client: bp.Client): Promise<void> => {
|
||||
const { files } = await client.listFiles({
|
||||
tags: {
|
||||
kbId,
|
||||
},
|
||||
})
|
||||
|
||||
for (const file of files) {
|
||||
await client.deleteFile({ id: file.id })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user