-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add zendesk as a mentions and items provider
- Loading branch information
Showing
5 changed files
with
262 additions
and
0 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:*" | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" }] | ||
} | ||
|