Skip to content

Commit

Permalink
feat(idempotent): Zendesk-sync is now idempotent
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Balmos <[email protected]>
  • Loading branch information
abalmos committed Oct 17, 2024
1 parent 3aa048c commit df50c86
Show file tree
Hide file tree
Showing 19 changed files with 2,363 additions and 1,662 deletions.
925 changes: 925 additions & 0 deletions .yarn/releases/yarn-4.5.0.cjs

Large diffs are not rendered by default.

24 changes: 13 additions & 11 deletions service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@
"license": "Apache-2.0",
"dependencies": {
"@oada/client": "^5.2.3",
"@oada/jobs": "^4.5.2",
"@oada/lib-config": "^3.9.1",
"@oada/lib-prom": "^3.8.0",
"@oada/pino-debug": "^3.10.0",
"@oada/jobs": "^4.7.1",
"@oada/lib-config": "^4.0.0",
"@oada/lib-prom": "^4.0.0",
"@oada/pino-debug": "^4.0.1",
"@oada/types": "^3.5.3",
"axios": "^1.7.7",
"axios-cache-interceptor": "^1.6.0",
Expand All @@ -65,9 +65,9 @@
"md5": "^2.3.0",
"p-queue": "^8.0.1",
"p-throttle": "^6.2.0",
"p-timeout": "^6.1.2",
"puppeteer": "^23.5.3",
"tslib": "^2.7.0"
"p-timeout": "^6.1.3",
"puppeteer": "^23.6.0",
"tslib": "^2.8.0"
},
"devDependencies": {
"@ava/typescript": "^5.0.0",
Expand All @@ -77,7 +77,7 @@
"@types/cron": "^2.4.0",
"@types/debug": "^4.1.12",
"@types/md5": "^2.3.5",
"@types/node": "^22.7.5",
"@types/node": "^22.7.6",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@yarnpkg/sdks": "^3.2.0",
Expand All @@ -102,16 +102,18 @@
"eslint-plugin-notice": "^1.0.0",
"eslint-plugin-optimize-regex": "^1.2.1",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-promise": "^7.1.0",
"eslint-plugin-regexp": "^2.6.0",
"eslint-plugin-security": "^3.0.1",
"eslint-plugin-sonarjs": "^1.0.4",
"eslint-plugin-unicorn": "^54.0.0",
"eslint-plugin-sonarjs": "^2.0.3",
"eslint-plugin-unicorn": "^56.0.0",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3"
},
"resolutions": {
"cookie": "^1.0.0",
"jsonpath-plus": "^10.0.0",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
},
"volta": {
Expand Down
94 changes: 94 additions & 0 deletions service/src/archivers/laserfiche.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* @license
* Copyright 2024 Qlever LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import type JobSchema from '@oada/types/oada/service/job.js';
import { type Logger } from '@oada/pino-debug';
import { type OADAClient } from '@oada/client';
import { type Ticket } from '../types.js';
import { config } from '../config.js';
import { doJob } from '@oada/client/jobs';
import { setCustomField } from '../zd/zendesk.js';

interface LFSyncDocJob extends JobSchema {
service: 'lf-sync';
type: 'sync-doc';
config: {
doc: { _id: string };
tradingPartner: string;
};
}

type LFSyncDocJobResult = Record<
string,
{
EntryId: number;
Path: string;
Name: string;
}
>;

const PATH_FIELD_ID = config.get('service.archivers.laserfiche.pathFieldId');
const LF_ID_FIELD_ID = config.get('service.archivers.laserfiche.lfIdFieldId');

export async function doLfSync(
log: Logger,
oada: OADAClient,
ticket: Ticket,
doc: { trellisId: string; tradingPartner: string },
) {
const syncJob: LFSyncDocJob = {
service: 'lf-sync',
type: 'sync-doc',
config: {
doc: { _id: doc.trellisId },
tradingPartner: doc.tradingPartner,
},
};

// FIXME: doJob really should take a generic for the job type response or something
const { result: entities } = (await doJob(oada, syncJob)) as unknown as {
result: LFSyncDocJobResult;
};

// log = log.child({ entities });

const lfId = entities.email_archive?.EntryId;
if (!lfId) {
log.error('No Laserfiche ID for ticket PDF?');
throw new Error('Could not determine Laserfiche ID for ticket PDF?');
}

const path = entities.email_archive?.Path.split('\\').slice(0, -1).join('\\');
if (!path) {
log.error('No Laserfiche path for ticket PDF?');
throw new Error('Could not determine Laserfiche path for ticket folder?');
}

log.debug('Ticket is in LF. Updating Zendesk state');

if (ticket.status !== 'closed') {
if (PATH_FIELD_ID > 0) {
log.trace('Add LF path meta data back to Zendesk');
await setCustomField(log, ticket, [{ id: PATH_FIELD_ID, value: path }]);
}

if (LF_ID_FIELD_ID > 0) {
log.trace('Add LF ID meta data back to Zendesk');
await setCustomField(log, ticket, [{ id: LF_ID_FIELD_ID, value: lfId }]);
}
}
}
91 changes: 54 additions & 37 deletions service/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@

import libConfig from '@oada/lib-config';

/*
AddFormats({
archivers: {
name: 'archivers',
validate: assertIsArchiverArray,
coerce(value: unknown) {
if (typeof value === 'string') {
return value.split(',').map((item) => item.trim());
}
return value;
},
},
});
*/

export const { config } = await libConfig({
mode: {
doc: 'Run sync in production mode',
Expand All @@ -34,48 +50,46 @@ export const { config } = await libConfig({
env: 'SERVICE_POLLER_RATE',
arg: 'service-poller-rate',
},
'force-age': {
doc: 'The age (in seconds) when a ticket is forced archived under the default organization',
forceAge: {
doc: 'The age (in seconds) when a ticket is forced archived under the default organiation',
format: Number,
default: 27 * 24 * 60 * 60,
env: 'SERVICE_POLLER_FORCE_ARCHIVE_AGE',
arg: 'service-poller-force-archive-age',
},
closer: {
doc: 'The closer to use when processing tickets.',
format: String,
default: 'none',
env: 'SERVICE_POLLER_CLOSER',
arg: 'service-poller-closer',
archivers: {
doc: 'The archiver(s) to use when processing tickets.',
format: Array,
default: [],
env: 'SERVICE_POLLER_ARCHIVERS',
arg: 'service-poller-archivers',
},
},
archiveTicket: {
name: {
doc: 'Service name of ZenDesk archive service',
default: 'archiveTicket',
env: 'SERVICE_ARCHIVE_NAME',
arg: 'service-archive-name',
},
syncTicket: {
timeout: {
doc: 'Time limit for processing a ticket archive',
format: Number,
default: 5 * 60 * 1000,
env: 'SERVICE_ARCHIVE_TIMEOUT',
arg: 'service-archive-timeout',
},
pathFieldId: {
doc: 'Zendesk custom field ID for "Laserfiche Path"',
format: Number,
default: -1,
env: 'SERVICE_LFCLOSER_PATH_FIELD_ID',
arg: 'service-lfcloser-path-field-id',
},
lfIdFieldId: {
doc: 'Zendesk custom field ID for "Laserfiche Entity ID"',
format: Number,
default: -1,
env: 'SERVICE_LFCLOSER_ENTITY_ID_FIELD_ID',
arg: 'service-lfcloser-entity-id-field-id',
},
archivers: {
laserfiche: {
pathFieldId: {
doc: 'Zendesk custom field ID for "Laserfiche Path"',
default: -1,
format: Number,
env: 'SERVICE_LF_PATH_FIELD_ID',
arg: 'service-lf-path-field-id',
},
lfIdFieldId: {
doc: 'Zendesk custom field ID for "Laserfiche Entity ID"',
default: -1,
format: Number,
env: 'SERVICE_LF_ENTITY_ID_FIELD_ID',
arg: 'service-lf-entity-id-field-id',
},
},
},
},
Expand All @@ -93,6 +107,7 @@ export const { config } = await libConfig({
default: 'god',
env: 'TOKEN',
arg: 'token',
sensitive: true,
},
},
zendesk: {
Expand All @@ -103,12 +118,12 @@ export const { config } = await libConfig({
env: 'SERVICE_ARCHIVE_CONCURRENCY',
arg: 'service-archive-concurrency',
},
domain: {
doc: 'Zendesk API domain',
baseURL: {
doc: 'Zendesk API base URL',
format: String,
default: '',
env: 'ZD_DOMAIN',
arg: 'zd-domain',
env: 'ZD_BASE_URL',
arg: 'zd-base-url',
},
username: {
doc: 'Zendesk username email',
Expand All @@ -123,6 +138,7 @@ export const { config } = await libConfig({
default: '',
env: 'ZD_PASS',
arg: 'zd-pass',
sensitive: true,
},
api_limit: {
doc: 'Zendesk API per interval rate limit',
Expand All @@ -141,21 +157,22 @@ export const { config } = await libConfig({
fields: {
state: {
doc: 'Zendesk ID for custom field "Trellis Automation State"',
format: Number,
default: -1,
format: Number,
env: 'ZD_FIELDS_STATE_ID',
arg: 'zd-fields-state-id',
},
status: {
doc: 'Zendesk ID for custom field "Trellis Automation Status"',
format: Number,
default: -1,
format: Number,
env: 'ZD_FIELDS_STATUS_ID',
arg: 'zd-fields-status-id',
},
SAPId: {
doc: 'Name of the Zendesk field which stores the trading partner SAP ID',
default: -1,
format: String,
default: '',
env: 'ZD_FIELDS_SAP_ID',
arg: 'zd-sap-id',
},
Expand All @@ -168,11 +185,11 @@ export const { config } = await libConfig({
},
},
default_org: {
doc: 'Zendesk Organization ID to use when no organization was cataloged',
doc: 'Zendesk Organziation ID to use when no organziation was cataloged',
default: -1,
format: Number,
env: 'ZD_DEFAULT_ORG_ID',
arg: 'zd-default-org-id',
},
},
});
});
Loading

0 comments on commit df50c86

Please sign in to comment.