From f49e41fd091e1942f922cfd703b09864d65e007e Mon Sep 17 00:00:00 2001 From: Denis Jannot Date: Thu, 16 Apr 2026 11:17:15 +0200 Subject: [PATCH] Adding zendesk exclude org option Signed-off-by: Denis Jannot --- README.md | 2 ++ doc2vec.ts | 31 +++++++++++++++++++++++++++++++ package.json | 2 +- types.ts | 1 + 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4cc22d6..3255be7 100644 --- a/README.md +++ b/README.md @@ -218,6 +218,7 @@ Configuration is managed through two files: * `start_date`: (Optional) Only process tickets/articles updated since this date (e.g., `'2025-01-01'`). * `ticket_status`: (Optional) Filter tickets by status (defaults to `['new', 'open', 'pending', 'hold', 'solved']`). * `ticket_priority`: (Optional) Filter tickets by priority (defaults to all priorities). + * `excluded_organizations`: (Optional) An array of Zendesk organization names whose tickets should be skipped. The sync will abort if any name cannot be resolved. For S3 buckets (`type: 's3'`): * `bucket`: The S3 bucket name. @@ -359,6 +360,7 @@ Configuration is managed through two files: start_date: '2025-01-01' ticket_status: ['open', 'pending'] ticket_priority: ['high'] + excluded_organizations: ['Acme Corp', 'Internal Testing'] max_size: 1048576 database_config: type: 'sqlite' diff --git a/doc2vec.ts b/doc2vec.ts index 1f331b6..33f1d59 100644 --- a/doc2vec.ts +++ b/doc2vec.ts @@ -1326,6 +1326,9 @@ export class Doc2Vec { // transitioning to closed get updated rather than left stale) const statusFilter = new Set(config.ticket_status || ['new', 'open', 'pending', 'hold', 'solved', 'closed']); + const excludedOrgNames = new Set((config.excluded_organizations || []).map(n => n.toLowerCase())); + const excludedOrgIds = new Set(); + const fetchWithRetry = async (url: string, retries = 3): Promise => { for (let attempt = 0; attempt < retries; attempt++) { try { @@ -1409,6 +1412,12 @@ export class Doc2Vec { return; } + // Skip tickets belonging to excluded organizations + if (ticket.organization_id && excludedOrgIds.has(ticket.organization_id)) { + logger.debug(`Ticket #${ticketId} belongs to excluded organization ${ticket.organization_id} — skipping`); + return; + } + // Skip tickets whose status is outside the configured filter if (!statusFilter.has(ticket.status)) { logger.debug(`Ticket #${ticketId} has status '${ticket.status}' outside configured filter — skipping`); @@ -1441,6 +1450,28 @@ export class Doc2Vec { await this.processChunksForUrl(chunks, url, dbConnection, logger); }; + if (excludedOrgNames.size > 0) { + logger.info(`Resolving ${excludedOrgNames.size} excluded organization name(s) to IDs`); + const resolvedNames = new Set(); + let orgsUrl: string | null = `${baseUrl}/organizations.json?page[size]=100`; + while (orgsUrl) { + const orgsData: any = await fetchWithRetry(orgsUrl); + for (const org of orgsData?.organizations || []) { + const orgName = (org.name || '').toLowerCase(); + if (excludedOrgNames.has(orgName)) { + excludedOrgIds.add(org.id); + resolvedNames.add(orgName); + } + } + orgsUrl = orgsData?.meta?.has_more ? orgsData?.links?.next : null; + } + logger.info(`Excluding tickets from ${excludedOrgIds.size} organization(s): ${[...excludedOrgIds].join(', ')}`); + const unresolved = [...excludedOrgNames].filter(name => !resolvedNames.has(name)); + if (unresolved.length > 0) { + throw new Error(`Cannot resolve excluded organization(s): ${unresolved.join(', ')}. Aborting to avoid syncing data for them.`); + } + } + logger.info(`Fetching Zendesk tickets updated since ${lastRunDate}`); // Build query parameters — use the status filter for the search query diff --git a/package.json b/package.json index be71aee..fe20406 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "doc2vec", - "version": "2.9.2", + "version": "2.10.0", "type": "commonjs", "description": "", "main": "dist/doc2vec.js", diff --git a/types.ts b/types.ts index 8037464..9f1341b 100644 --- a/types.ts +++ b/types.ts @@ -44,6 +44,7 @@ export interface ZendeskSourceConfig extends BaseSourceConfig { start_date?: string; // For incremental updates (default: start of current year) ticket_status?: string[]; // Filter tickets by status (default: ['new', 'open', 'pending', 'hold', 'solved']) ticket_priority?: string[]; // Filter tickets by priority (default: all) + excluded_organizations?: string[]; // Organization names whose tickets should be skipped } // Configuration specific to code sources (local directory or GitHub repo)