Skip to content

Commit

Permalink
[connectors] Throw ExternalOAuth errors on Zendesk 403 errors (#8893)
Browse files Browse the repository at this point in the history
* enh: throw ExternalOAuthTokenError on 403 when fetching article section + user

* enh: throw ExternalOAuthTokenError on 403 in wrapped fetches

* enh: throw ExternalOAuthTokenError on 403 in syncZendeskArticleBatchActivity

* fix: remove user data from the logs

* add a new error type and check

* replace ts-expect errors with the typeguard

* fix: do not wrap the article sync in the try catch clause
  • Loading branch information
aubin-tchoi authored Nov 26, 2024
1 parent f1fdd03 commit 5ac8932
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 9 deletions.
19 changes: 19 additions & 0 deletions connectors/src/connectors/zendesk/lib/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Errors returned by the library node-zendesk.
* Check out https://github.com/blakmatrix/node-zendesk/blob/fa069d927bd418ee2058bb7bb913f9414e395110/src/clients/helpers.js#L262
*/
interface NodeZendeskError extends Error {
statusCode: number;
result: string | null;
}

export function isNodeZendeskForbiddenError(
err: unknown
): err is NodeZendeskError {
return (
typeof err === "object" &&
err !== null &&
"statusCode" in err &&
err.statusCode === 403
);
}
31 changes: 31 additions & 0 deletions connectors/src/connectors/zendesk/lib/zendesk_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import { createClient } from "node-zendesk";
import type {
ZendeskFetchedArticle,
ZendeskFetchedCategory,
ZendeskFetchedSection,
ZendeskFetchedTicket,
ZendeskFetchedUser,
} from "@connectors/@types/node-zendesk";
import { isNodeZendeskForbiddenError } from "@connectors/connectors/zendesk/lib/errors";
import { ExternalOAuthTokenError } from "@connectors/lib/error";
import logger from "@connectors/logger/logger";
import type { ZendeskCategoryResource } from "@connectors/resources/zendesk_resources";
Expand Down Expand Up @@ -147,6 +149,8 @@ async function fetchFromZendeskWithRetries({
`[Zendesk] Zendesk API 404 error on: ${getEndpointFromUrl(url)}`
);
return null;
} else if (rawResponse.status === 403) {
throw new ExternalOAuthTokenError();
}
logger.error(
{ rawResponse, status: rawResponse.status, text: rawResponse.text },
Expand Down Expand Up @@ -361,3 +365,30 @@ export async function fetchZendeskCurrentUser({
const data = await response.json();
return data.user;
}

/**
* Fetches the Section and the User for an article.
*/
export async function fetchArticleMetadata(
zendeskApiClient: Client,
article: ZendeskFetchedArticle
): Promise<{ section: ZendeskFetchedSection; user: ZendeskFetchedUser }> {
try {
const { result: section } = await zendeskApiClient.helpcenter.sections.show(
article.section_id
);
const { result: user } = await zendeskApiClient.users.show(
article.author_id
);
return { section, user };
} catch (e) {
logger.error(
{ articleId: article.id, error: e },
"[Zendesk] Error fetching article metadata"
);
if (isNodeZendeskForbiddenError(e)) {
throw new ExternalOAuthTokenError(e);
}
throw e;
}
}
22 changes: 17 additions & 5 deletions connectors/src/connectors/zendesk/temporal/activities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ModelId } from "@dust-tt/types";

import { isNodeZendeskForbiddenError } from "@connectors/connectors/zendesk/lib/errors";
import { syncArticle } from "@connectors/connectors/zendesk/lib/sync_article";
import { syncCategory } from "@connectors/connectors/zendesk/lib/sync_category";
import { syncTicket } from "@connectors/connectors/zendesk/lib/sync_ticket";
Expand All @@ -14,6 +15,7 @@ import {
import { ZENDESK_BATCH_SIZE } from "@connectors/connectors/zendesk/temporal/config";
import { dataSourceConfigFromConnector } from "@connectors/lib/api/data_source_config";
import { concurrentExecutor } from "@connectors/lib/async_utils";
import { ExternalOAuthTokenError } from "@connectors/lib/error";
import { ZendeskTimestampCursor } from "@connectors/lib/models/zendesk";
import { syncStarted, syncSucceeded } from "@connectors/lib/sync_status";
import logger from "@connectors/logger/logger";
Expand Down Expand Up @@ -328,11 +330,21 @@ export async function syncZendeskArticleBatchActivity({
`[Zendesk] Processing ${articles.length} articles in batch`
);

const sections =
await zendeskApiClient.helpcenter.sections.listByCategory(categoryId);
const { result: users } = await zendeskApiClient.users.showMany(
articles.map((article) => article.author_id)
);
let sections;
let users;
try {
sections =
await zendeskApiClient.helpcenter.sections.listByCategory(categoryId);
const { result: usersResult } = await zendeskApiClient.users.showMany(
articles.map((article) => article.author_id)
);
users = usersResult;
} catch (e) {
if (isNodeZendeskForbiddenError(e)) {
throw new ExternalOAuthTokenError(e);
}
throw e;
}

await concurrentExecutor(
articles,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { getZendeskSubdomainAndAccessToken } from "@connectors/connectors/zendes
import {
changeZendeskClientSubdomain,
createZendeskClient,
fetchArticleMetadata,
fetchRecentlyUpdatedArticles,
fetchRecentlyUpdatedTickets,
} from "@connectors/connectors/zendesk/lib/zendesk_api";
Expand Down Expand Up @@ -112,10 +113,9 @@ export async function syncZendeskArticleUpdateBatchActivity({
await concurrentExecutor(
articles,
async (article) => {
const { result: section } =
await zendeskApiClient.helpcenter.sections.show(article.section_id);
const { result: user } = await zendeskApiClient.users.show(
article.author_id
const { section, user } = await fetchArticleMetadata(
zendeskApiClient,
article
);

if (section.category_id) {
Expand Down

0 comments on commit 5ac8932

Please sign in to comment.