Skip to content

Commit

Permalink
add zendesk as a mentions and items provider
Browse files Browse the repository at this point in the history
  • Loading branch information
enriquegh committed Aug 7, 2024
1 parent 779a4b2 commit 9d8d6da
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 0 deletions.
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

122 changes: 122 additions & 0 deletions provider/zendesk/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import type { Settings } from './index.ts'

export interface TicketPickerItem {
id: number
subject: string
url: string
}

export interface Ticket {
id: number
url: string
subject: string
description: string
tags: string[]
status: string
priority: string
created_at: string
updated_at: string
comments: TicketComment[]
}

export interface TicketComment {
id: number
type: string
author_id: number
body: string
html_body: string
plain_body: string
public: boolean
created_at: string
}

const authHeaders = (settings: Settings) => ({
Authorization: `Basic ${Buffer.from(`${settings.email}/token:${settings.apiToken}`).toString('base64')}`,
})

const buildUrl = (settings: Settings, path: string, searchParams: Record<string, string> = {}) => {
const url = new URL(`https://${settings.subdomain}.zendesk.com/api/v2${path}`)
url.search = new URLSearchParams(searchParams).toString()
return url
}

export const searchTickets = async (
query: string | undefined,
settings: Settings,
): Promise<TicketPickerItem[]> => {
const searchResponse = await fetch(
buildUrl(settings, '/search.json', {
query: `type:ticket ${query || ''}`,
}),
{
method: 'GET',
headers: authHeaders(settings),
},
)
if (!searchResponse.ok) {
throw new Error(
`Error searching Zendesk tickets (${searchResponse.status} ${
searchResponse.statusText
}): ${await searchResponse.text()}`,
)
}

const searchJSON = (await searchResponse.json()) as {
results: {
id: number
subject: string
url: string
}[]
}

return searchJSON.results.map(ticket => ({
id: ticket.id,
subject: ticket.subject,
url: ticket.url,
}))
}

export const fetchTicket = async (ticketId: number, settings: Settings): Promise<Ticket | null> => {
const ticketResponse = await fetch(
buildUrl(settings, `/tickets/${ticketId}.json`),
{
method: 'GET',
headers: authHeaders(settings),
}
)
if (!ticketResponse.ok) {
throw new Error(
`Error fetching Zendesk ticket (${ticketResponse.status} ${
ticketResponse.statusText
}): ${await ticketResponse.text()}`
)
}

const responseJSON = (await ticketResponse.json()) as { ticket: Ticket }
const ticket = responseJSON.ticket

if (!ticket) {
return null
}

// Fetch comments for the ticket
const commentsResponse = await fetch(
buildUrl(settings, `/tickets/${ticketId}/comments.json`),
{
method: 'GET',
headers: authHeaders(settings),
}
)
if (!commentsResponse.ok) {
throw new Error(
`Error fetching Zendesk ticket comments (${commentsResponse.status} ${
commentsResponse.statusText
}): ${await commentsResponse.text()}`
)
}

const commentsJSON = (await commentsResponse.json()) as { comments: TicketComment[] }
ticket.comments = commentsJSON.comments

return ticket
}
96 changes: 96 additions & 0 deletions provider/zendesk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import type {
Item,
ItemsParams,
ItemsResult,
MentionsParams,
MentionsResult,
MetaParams,
MetaResult,
Provider,
} from '@openctx/provider'
import { type Ticket, fetchTicket, searchTickets } from './api.js'

export type Settings = {
subdomain: string
email: string
apiToken: string
}

const checkSettings = (settings: Settings) => {
const missingKeys = ['subdomain', 'email', 'apiToken'].filter(key => !(key in settings))
if (missingKeys.length > 0) {
throw new Error(`Missing settings: ${JSON.stringify(missingKeys)}`)
}
}

const ticketToItem = (ticket: Ticket): Item => ({
url: ticket.url,
title: ticket.subject,
ui: {
hover: {
markdown: ticket.description,
text: ticket.description || ticket.subject,
},
},
ai: {
content:
`The following represents contents of the Zendesk ticket ${ticket.id}: ` +
JSON.stringify({
ticket: {
id: ticket.id,
subject: ticket.subject,
url: ticket.url,
description: ticket.description,
tags: ticket.tags,
status: ticket.status,
priority: ticket.priority,
created_at: ticket.created_at,
updated_at: ticket.updated_at,
comments: ticket.comments.map(comment => ({
id: comment.id,
type: comment.type,
author_id: comment.author_id,
body: comment.body,
html_body: comment.html_body,
plain_body: comment.plain_body,
public: comment.public,
created_at: comment.created_at,
}))
},
}),
},
})

const zendeskProvider: Provider = {
meta(params: MetaParams, settings: Settings): MetaResult {
return { name: 'Zendesk', mentions: { label: 'Search by subject, id, or paste url...' } }
},
async mentions(params: MentionsParams, settings: Settings): Promise<MentionsResult> {
checkSettings(settings)

return searchTickets(params.query, settings).then(items =>
items.map(item => ({
title: `#${item.id}`,
uri: item.url,
description: item.subject,
data: { id: item.id },
})),
)
},

async items(params: ItemsParams, settings: Settings): Promise<ItemsResult> {
checkSettings(settings)

const id = (params.mention?.data as { id: number }).id

const ticket = await fetchTicket(id, settings)

if (!ticket) {
return []
}

return [ticketToItem(ticket)]
},
}

export default zendeskProvider
26 changes: 26 additions & 0 deletions provider/zendesk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "@openctx/zendesk",
"version": "0.0.1",
"description": "Use information from Zendesk (OpenCtx provider)",
"license": "Apache-2.0",
"homepage": "https://openctx.org/docs/providers/zendesk",
"repository": {
"type": "git",
"url": "https://github.com/sourcegraph/openctx",
"directory": "provider/zendesk"
},
"type": "module",
"main": "dist/bundle.js",
"types": "dist/index.d.ts",
"files": ["dist/bundle.js", "dist/index.d.ts"],
"sideEffects": false,
"scripts": {
"bundle": "tsc --build && esbuild --log-level=error --bundle --format=esm --outfile=dist/bundle.js index.ts",
"prepublishOnly": "tsc --build --clean && npm run --silent bundle",
"test": "vitest"
},
"dependencies": {
"@openctx/provider": "workspace:*"
}
}

12 changes: 12 additions & 0 deletions provider/zendesk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"extends": "../../.config/tsconfig.base.json",
"compilerOptions": {
"rootDir": ".",
"outDir": "dist",
"lib": ["ESNext"]
},
"include": ["*.ts"],
"exclude": ["dist", "vitest.config.ts"],
"references": [{ "path": "../../lib/provider" }]
}

0 comments on commit 9d8d6da

Please sign in to comment.