From 83daae4f615a09cd002bb5f84f3020ed1e9f04ef Mon Sep 17 00:00:00 2001 From: Aubin <60398825+aubin-tchoi@users.noreply.github.com> Date: Tue, 22 Oct 2024 18:17:13 +0200 Subject: [PATCH] Add Zendesk api wrapper functions (#8157) * feat: add a function getZendeskAccessToken * feat: add an API wrapper to handle data fetching * fix: remove the await .json since it's already handled in callZendeskApi * feat: widen the OAuth scope to read to allow getting brands * feat: replace our wrapper with the lib node-zendesk --- connectors/package-lock.json | 45 +++++++- connectors/package.json | 2 + .../zendesk/lib/node-zendesk-types.d.ts | 101 ++++++++++++++++++ .../zendesk/lib/zendesk_access_token.ts | 13 +++ .../src/connectors/zendesk/lib/zendesk_api.ts | 11 ++ core/src/oauth/providers/zendesk.rs | 2 +- front/lib/api/oauth.ts | 2 +- 7 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 connectors/src/connectors/zendesk/lib/node-zendesk-types.d.ts create mode 100644 connectors/src/connectors/zendesk/lib/zendesk_access_token.ts create mode 100644 connectors/src/connectors/zendesk/lib/zendesk_api.ts diff --git a/connectors/package-lock.json b/connectors/package-lock.json index 84a137f5284e..aa87d8401c0d 100644 --- a/connectors/package-lock.json +++ b/connectors/package-lock.json @@ -22,6 +22,7 @@ "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", "@types/minimist": "^1.2.2", + "@types/node-zendesk": "^2.0.15", "@types/remove-markdown": "^0.3.4", "@types/uuid": "^9.0.2", "axios": "^1.5.1", @@ -42,6 +43,7 @@ "micromark-extension-gfm": "^3.0.0", "minimist": "^1.2.8", "morgan": "^1.10.0", + "node-zendesk": "^5.0.13", "octokit": "^3.1.2", "p-queue": "^7.3.4", "pg": "^8.8.0", @@ -4762,6 +4764,15 @@ "node": ">= 6" } }, + "node_modules/@types/node-zendesk": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@types/node-zendesk/-/node-zendesk-2.0.15.tgz", + "integrity": "sha512-8Kk7ceoSUiBst5+jX/121QBD8f69F5j9CqvLA1Ka+24vo+B6sPINnqPwfBJAs4/9jBpCLh7h2SH9hUbABiuZXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -6425,6 +6436,15 @@ } } }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -11080,9 +11100,10 @@ "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" }, "node_modules/node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -11121,6 +11142,24 @@ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" }, + "node_modules/node-zendesk": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/node-zendesk/-/node-zendesk-5.0.13.tgz", + "integrity": "sha512-NT+57ZsfZBcMZ8xxX2krKKpHrroDlL7/NQkaStHI4un7W1HV8YWzm405k9D1etkt7tE2LHjU6Zd9y3Efwre/2g==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/blakmatrix/node-zendesk?sponsor=1" + } + ], + "license": "MIT", + "dependencies": { + "cross-fetch": "^4.0.0" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/normalize-url": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", diff --git a/connectors/package.json b/connectors/package.json index 0f4ae64152ab..1b5a0b8dbec7 100644 --- a/connectors/package.json +++ b/connectors/package.json @@ -28,6 +28,7 @@ "@types/express": "^4.17.17", "@types/fs-extra": "^11.0.1", "@types/minimist": "^1.2.2", + "@types/node-zendesk": "^2.0.15", "@types/remove-markdown": "^0.3.4", "@types/uuid": "^9.0.2", "axios": "^1.5.1", @@ -48,6 +49,7 @@ "micromark-extension-gfm": "^3.0.0", "minimist": "^1.2.8", "morgan": "^1.10.0", + "node-zendesk": "^5.0.13", "octokit": "^3.1.2", "p-queue": "^7.3.4", "pg": "^8.8.0", diff --git a/connectors/src/connectors/zendesk/lib/node-zendesk-types.d.ts b/connectors/src/connectors/zendesk/lib/node-zendesk-types.d.ts new file mode 100644 index 000000000000..d3f8ad262e08 --- /dev/null +++ b/connectors/src/connectors/zendesk/lib/node-zendesk-types.d.ts @@ -0,0 +1,101 @@ +import "node-zendesk"; + +interface Brand { + url: string; + id: number; + name: string; + brand_url: string; + subdomain: string; + host_mapping: string | null; + has_help_center: boolean; + help_center_state: string; + active: boolean; + default: boolean; + is_deleted: boolean; + logo: object | null; + ticket_form_ids: number[]; + signature_template: string; + created_at: string; + updated_at: string; +} + +interface Response { + status: number; + headers: object; + statusText: string; +} + +interface Category { + id: number; + url: string; + html_url: string; + position: number; + created_at: string; + updated_at: string; + name: string; + description: string; + locale: string; + source_locale: string; + outdated: boolean; +} + +interface Article { + id: number; + url: string; + html_url: string; + author_id: number; + comments_disabled: boolean; + draft: boolean; + promoted: boolean; + position: number; + vote_sum: number; + vote_count: number; + section_id: number; + created_at: string; + updated_at: string; + name: string; + title: string; + source_locale: string; + locale: string; + outdated: boolean; + outdated_locales: string[]; + edited_at: string; + user_segment_id: number; + permission_group_id: number; + content_tag_ids: number[]; + label_names: string[]; + body: string; + user_segment_ids: number[]; +} + +declare module "node-zendesk" { + interface Client { + brand: { + list: () => Promise<{ + response: Response; + result: Brand[]; + }>; + show: ( + brandId: number + ) => Promise<{ response: Response; result: { brand: Brand } }>; + }; + helpcenter: { + categories: { + list: () => Promise; + show: ( + categoryId: number + ) => Promise<{ response: Response; result: Category }>; + }; + articles: { + list: () => Promise; + show: ( + articleId: number + ) => Promise<{ response: Response; result: Article }>; + listByCategory: (categoryId: number) => Promise; + listSinceStartTime: (startTime: number) => Promise; + }; + }; + } + + export function createClient(options: object): Client; +} diff --git a/connectors/src/connectors/zendesk/lib/zendesk_access_token.ts b/connectors/src/connectors/zendesk/lib/zendesk_access_token.ts new file mode 100644 index 000000000000..6fc498f0b454 --- /dev/null +++ b/connectors/src/connectors/zendesk/lib/zendesk_access_token.ts @@ -0,0 +1,13 @@ +import { getOAuthConnectionAccessTokenWithThrow } from "@connectors/lib/oauth"; +import logger from "@connectors/logger/logger"; + +export async function getZendeskAccessToken( + connectionId: string +): Promise { + const token = await getOAuthConnectionAccessTokenWithThrow({ + logger, + provider: "zendesk", + connectionId, + }); + return token.access_token; +} diff --git a/connectors/src/connectors/zendesk/lib/zendesk_api.ts b/connectors/src/connectors/zendesk/lib/zendesk_api.ts new file mode 100644 index 000000000000..7a446c47d06a --- /dev/null +++ b/connectors/src/connectors/zendesk/lib/zendesk_api.ts @@ -0,0 +1,11 @@ +import { createClient } from "node-zendesk"; + +export function createZendeskClient({ + token, + subdomain = "d3v-dust", +}: { + token: string; + subdomain?: string; +}) { + return createClient({ oauth: true, token, subdomain }); +} diff --git a/core/src/oauth/providers/zendesk.rs b/core/src/oauth/providers/zendesk.rs index 27532b40c38e..1a6872e7b727 100644 --- a/core/src/oauth/providers/zendesk.rs +++ b/core/src/oauth/providers/zendesk.rs @@ -42,7 +42,7 @@ impl Provider for ZendeskConnectionProvider { "client_id": *OAUTH_ZENDESK_CLIENT_ID, "client_secret": *OAUTH_ZENDESK_CLIENT_SECRET, "redirect_uri": redirect_uri, - "scope": "tickets:read hc:read" + "scope": "read" }); let req = reqwest::Client::new() diff --git a/front/lib/api/oauth.ts b/front/lib/api/oauth.ts index 73626c3af567..fd16fca2b5ba 100644 --- a/front/lib/api/oauth.ts +++ b/front/lib/api/oauth.ts @@ -246,7 +246,7 @@ const PROVIDER_STRATEGIES: Record< }, zendesk: { setupUri: (connection) => { - const scopes = ["tickets:read", "hc:read"]; + const scopes = ["read"]; return ( `https://d3v-dust.zendesk.com/oauth/authorizations/new?` + `client_id=${config.getOAuthZendeskClientId()}` +