@forestadmin/datasource-zendesk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -0
- package/dist/client/index.d.ts +91 -0
- package/dist/client/index.js +243 -0
- package/dist/client-options.d.ts +16 -0
- package/dist/client-options.js +10 -0
- package/dist/collections/base-zendesk-collection.d.ts +52 -0
- package/dist/collections/base-zendesk-collection.js +203 -0
- package/dist/collections/organization-collection.d.ts +15 -0
- package/dist/collections/organization-collection.js +145 -0
- package/dist/collections/searchable-collection.d.ts +6 -0
- package/dist/collections/searchable-collection.js +20 -0
- package/dist/collections/ticket/comments-embedder.d.ts +5 -0
- package/dist/collections/ticket/comments-embedder.js +40 -0
- package/dist/collections/ticket/relation-embedder.d.ts +10 -0
- package/dist/collections/ticket/relation-embedder.js +62 -0
- package/dist/collections/ticket/schema-definition.d.ts +13 -0
- package/dist/collections/ticket/schema-definition.js +147 -0
- package/dist/collections/ticket/serializer.d.ts +15 -0
- package/dist/collections/ticket/serializer.js +96 -0
- package/dist/collections/ticket-collection.d.ts +18 -0
- package/dist/collections/ticket-collection.js +139 -0
- package/dist/collections/user-collection.d.ts +15 -0
- package/dist/collections/user-collection.js +162 -0
- package/dist/datasource.d.ts +16 -0
- package/dist/datasource.js +49 -0
- package/dist/enums.d.ts +10 -0
- package/dist/enums.js +12 -0
- package/dist/errors.d.ts +14 -0
- package/dist/errors.js +31 -0
- package/dist/factory.d.ts +5 -0
- package/dist/factory.js +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +30 -0
- package/dist/plugins/close-ticket/errors.d.ts +7 -0
- package/dist/plugins/close-ticket/errors.js +44 -0
- package/dist/plugins/close-ticket/index.d.ts +13 -0
- package/dist/plugins/close-ticket/index.js +74 -0
- package/dist/plugins/close-ticket/messages.d.ts +12 -0
- package/dist/plugins/close-ticket/messages.js +33 -0
- package/dist/plugins/create-ticket-with-notification/form-builder.d.ts +30 -0
- package/dist/plugins/create-ticket-with-notification/form-builder.js +179 -0
- package/dist/plugins/create-ticket-with-notification/index.d.ts +19 -0
- package/dist/plugins/create-ticket-with-notification/index.js +81 -0
- package/dist/query/condition-tree-translator.d.ts +9 -0
- package/dist/query/condition-tree-translator.js +90 -0
- package/dist/query/operators.d.ts +15 -0
- package/dist/query/operators.js +34 -0
- package/dist/schema/custom-fields-introspector.d.ts +13 -0
- package/dist/schema/custom-fields-introspector.js +89 -0
- package/dist/types.d.ts +23 -0
- package/dist/types.js +3 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# @forestadmin/datasource-zendesk
|
|
2
|
+
|
|
3
|
+
Forest Admin datasource for Zendesk Support, with optional smart-action plugins.
|
|
4
|
+
|
|
5
|
+
Exposes three collections wired to the Zendesk REST + Search APIs:
|
|
6
|
+
|
|
7
|
+
| Forest collection | Zendesk resource | CRUD | Aggregation |
|
|
8
|
+
| ----------------------- | ---------------- | ------------- | --------------- |
|
|
9
|
+
| `zendesk_ticket` | Ticket | List, create, update, delete | Count only |
|
|
10
|
+
| `zendesk_user` | User | List, create, update, delete | Count only |
|
|
11
|
+
| `zendesk_organization` | Organization | List, create, update, delete | Count only |
|
|
12
|
+
|
|
13
|
+
The datasource introspects Zendesk **custom fields** on startup (ticket, user, organization) and registers them as Forest columns.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
yarn add @forestadmin/datasource-zendesk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createAgent } from '@forestadmin/agent';
|
|
25
|
+
import {
|
|
26
|
+
createZendeskClient,
|
|
27
|
+
createZendeskDataSource,
|
|
28
|
+
closeTicketPlugin,
|
|
29
|
+
createTicketWithNotificationPlugin,
|
|
30
|
+
} from '@forestadmin/datasource-zendesk';
|
|
31
|
+
|
|
32
|
+
const zendeskClient = createZendeskClient({
|
|
33
|
+
subdomain: process.env.ZENDESK_SUBDOMAIN!,
|
|
34
|
+
email: process.env.ZENDESK_EMAIL!,
|
|
35
|
+
apiToken: process.env.ZENDESK_API_TOKEN!,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const agent = createAgent({ /* ... */ })
|
|
39
|
+
.addDataSource(createZendeskDataSource({ client: zendeskClient }))
|
|
40
|
+
|
|
41
|
+
// Smart action on Zendesk tickets: Mark as solved / closed.
|
|
42
|
+
.customizeCollection('zendesk_ticket', collection =>
|
|
43
|
+
collection.use(closeTicketPlugin, {
|
|
44
|
+
client: zendeskClient,
|
|
45
|
+
ticketIdField: 'id',
|
|
46
|
+
}),
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Smart action on ANY collection (here: customers): create a Zendesk ticket
|
|
50
|
+
// and notify the requester by email.
|
|
51
|
+
.customizeCollection('customers', collection =>
|
|
52
|
+
collection.use(createTicketWithNotificationPlugin, {
|
|
53
|
+
client: zendeskClient,
|
|
54
|
+
requesterEmailDefault: record => String(record.email ?? ''),
|
|
55
|
+
defaultSubject: 'Follow-up about your account',
|
|
56
|
+
ticketIdField: 'last_zendesk_ticket_id',
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Datasource
|
|
62
|
+
|
|
63
|
+
### `createZendeskDataSource(options)`
|
|
64
|
+
|
|
65
|
+
`options` accepts either an already-built `client` (recommended when also passing it to plugins) or raw credentials:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
createZendeskDataSource({ client: createZendeskClient({ subdomain, email, apiToken }) });
|
|
69
|
+
|
|
70
|
+
// or
|
|
71
|
+
createZendeskDataSource({ subdomain, email, apiToken });
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Filter operators
|
|
75
|
+
|
|
76
|
+
Zendesk Search supports a restricted set of operators. The datasource exposes:
|
|
77
|
+
|
|
78
|
+
- `Equal`, `In` for the primary key (`id`), resolved by id lookup outside of Search
|
|
79
|
+
- `Equal`, `NotEqual`, `In`, `NotIn`, `Present`, `Blank` for strings and enums
|
|
80
|
+
- `Equal`, `NotEqual` for booleans
|
|
81
|
+
- `Equal`, `NotEqual`, `In`, `NotIn`, `Present`, `Blank`, `GreaterThan`, `LessThan` for numbers
|
|
82
|
+
- `Equal`, `Before`, `After`, `Present`, `Blank` for dates
|
|
83
|
+
|
|
84
|
+
Zendesk Search has no `OR` operator. Multi-value membership (`In`/`NotIn`) on the primary key matches each value exactly (id lookup), but on other fields Zendesk ANDs the terms — so `status In ['open', 'pending']` returns nothing. Unsupported operators (`Contains`, `Or` aggregator, …) raise `UnsupportedOperatorError`. The Zendesk Search API caps result pagination at 1000 records — large skips raise the same error.
|
|
85
|
+
|
|
86
|
+
### Custom fields
|
|
87
|
+
|
|
88
|
+
On boot, the datasource calls `GET /ticket_fields.json`, `/user_fields.json` and `/organization_fields.json`. Active fields are mapped to Forest columns:
|
|
89
|
+
|
|
90
|
+
| Zendesk type | Forest column |
|
|
91
|
+
| ------------ | ------------- |
|
|
92
|
+
| `text`, `textarea`, `regexp`, `partialcreditcard` | `String` |
|
|
93
|
+
| `integer`, `decimal`, `lookup` | `Number` |
|
|
94
|
+
| `date` | `Dateonly` |
|
|
95
|
+
| `checkbox` | `Boolean` |
|
|
96
|
+
| `dropdown`, `tagger` | `Enum` (or `String` if no options) |
|
|
97
|
+
| `multiselect` | `Json` |
|
|
98
|
+
|
|
99
|
+
Ticket custom fields are exposed as `custom_<id>`; user/organization ones use the Zendesk `key`.
|
|
100
|
+
|
|
101
|
+
## Plugins
|
|
102
|
+
|
|
103
|
+
Both plugins accept the same client contract as the datasource: pass an
|
|
104
|
+
already-built `client`, **or** raw credentials (`subdomain`, `email`, `apiToken`)
|
|
105
|
+
and the plugin builds one for you. Reusing a single client across the datasource
|
|
106
|
+
and the plugins is recommended, but no longer required.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// with a shared client
|
|
110
|
+
collection.use(closeTicketPlugin, { client, ticketIdField: 'id' });
|
|
111
|
+
|
|
112
|
+
// or with raw credentials (client is optional)
|
|
113
|
+
collection.use(closeTicketPlugin, {
|
|
114
|
+
subdomain: process.env.ZENDESK_SUBDOMAIN!,
|
|
115
|
+
email: process.env.ZENDESK_EMAIL!,
|
|
116
|
+
apiToken: process.env.ZENDESK_API_TOKEN!,
|
|
117
|
+
ticketIdField: 'id',
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `closeTicketPlugin`
|
|
122
|
+
|
|
123
|
+
Adds Single + Bulk actions to mark Zendesk tickets as `solved` or `closed`. Tickets that Zendesk reports as already closed are folded into the success message rather than counted as failures.
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
collection.use(closeTicketPlugin, {
|
|
127
|
+
client,
|
|
128
|
+
ticketIdField: 'zendesk_id',
|
|
129
|
+
// optional:
|
|
130
|
+
statuses: ['solved'], // default: ['solved', 'closed']
|
|
131
|
+
scopes: ['Single'], // default: ['Single', 'Bulk']
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `createTicketWithNotificationPlugin`
|
|
136
|
+
|
|
137
|
+
Adds a Single action that creates a Zendesk ticket. Useful on a customer-facing collection so an agent can reach out without leaving Forest. With `emailTemplates`, the form becomes two-page (template picker → body).
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
collection.use(createTicketWithNotificationPlugin, {
|
|
141
|
+
client,
|
|
142
|
+
actionName: 'Notify customer', // optional
|
|
143
|
+
emailTemplates: [
|
|
144
|
+
{ title: 'Welcome', content: 'Hi {{ record.first_name }}, welcome aboard.' },
|
|
145
|
+
],
|
|
146
|
+
requesterEmailDefault: record => String(record.email ?? ''),
|
|
147
|
+
defaultSubject: 'Follow-up',
|
|
148
|
+
priorityOverride: 'normal', // hides Priority from the form
|
|
149
|
+
ticketIdField: 'last_zendesk_ticket_id', // best-effort writeback after creation
|
|
150
|
+
showInternalNote: true, // adds a "Send as internal note" toggle
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Template interpolation supports `{{ record.field }}` (including dotted paths like `{{ record.org.name }}`).
|
|
155
|
+
|
|
156
|
+
## Errors
|
|
157
|
+
|
|
158
|
+
All exceptions raised by the datasource are subclasses of Forest Admin's `BusinessError` / `ValidationError`:
|
|
159
|
+
|
|
160
|
+
- `ZendeskConfigurationError` — missing/invalid client options
|
|
161
|
+
- `ZendeskApiError` — Zendesk HTTP failure (carries the operation, status code and response body)
|
|
162
|
+
- `UnsupportedOperatorError` — filter / aggregation that Zendesk cannot express
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { CustomFieldEntry, SearchParams, ZendeskClientOptions, ZendeskRecord, ZendeskResource } from '../types';
|
|
2
|
+
import type { Logger } from '@forestadmin/datasource-toolkit';
|
|
3
|
+
export declare const MAX_PER_PAGE = 100;
|
|
4
|
+
export declare const MAX_TOTAL_RESULTS = 1000;
|
|
5
|
+
export type RawCustomFieldDefinition = {
|
|
6
|
+
id: number;
|
|
7
|
+
key?: string;
|
|
8
|
+
title?: string;
|
|
9
|
+
raw_title?: string;
|
|
10
|
+
type: string;
|
|
11
|
+
active?: boolean;
|
|
12
|
+
removable?: boolean;
|
|
13
|
+
custom_field_options?: Array<{
|
|
14
|
+
value: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
}>;
|
|
17
|
+
};
|
|
18
|
+
export type ZendeskClient = {
|
|
19
|
+
search(type: ZendeskResource, params: SearchParams): Promise<ZendeskRecord[]>;
|
|
20
|
+
count(type: ZendeskResource, query: string): Promise<number>;
|
|
21
|
+
findTicket(id: number | string): Promise<ZendeskRecord | null>;
|
|
22
|
+
findUser(id: number | string): Promise<ZendeskRecord | null>;
|
|
23
|
+
findOrganization(id: number | string): Promise<ZendeskRecord | null>;
|
|
24
|
+
fetchTicketsByIds(ids: Array<number | string>): Promise<Map<number, ZendeskRecord>>;
|
|
25
|
+
fetchUsersByIds(ids: Array<number | string>): Promise<Map<number, ZendeskRecord>>;
|
|
26
|
+
fetchOrganizationsByIds(ids: Array<number | string>): Promise<Map<number, ZendeskRecord>>;
|
|
27
|
+
fetchUserEmails(ids: Array<number | string>): Promise<Map<number, string>>;
|
|
28
|
+
fetchTicketComments(ticketId: number | string): Promise<ZendeskRecord[]>;
|
|
29
|
+
fetchTicketFields(): Promise<RawCustomFieldDefinition[]>;
|
|
30
|
+
fetchUserFields(): Promise<RawCustomFieldDefinition[]>;
|
|
31
|
+
fetchOrganizationFields(): Promise<RawCustomFieldDefinition[]>;
|
|
32
|
+
createTicket(attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
33
|
+
updateTicket(id: number | string, attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
34
|
+
deleteTicket(id: number | string): Promise<void>;
|
|
35
|
+
createUser(attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
36
|
+
updateUser(id: number | string, attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
37
|
+
deleteUser(id: number | string): Promise<void>;
|
|
38
|
+
createOrganization(attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
39
|
+
updateOrganization(id: number | string, attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
40
|
+
deleteOrganization(id: number | string): Promise<void>;
|
|
41
|
+
readonly baseUrl: string;
|
|
42
|
+
/**
|
|
43
|
+
* Best-effort wrapper that logs and returns the default if the action throws.
|
|
44
|
+
* Used internally and exposed so embedders/plugins can degrade gracefully.
|
|
45
|
+
*/
|
|
46
|
+
bestEffort<T>(operation: string, defaultValue: T, fn: () => Promise<T>): Promise<T>;
|
|
47
|
+
};
|
|
48
|
+
export declare class ZendeskHttpClient implements ZendeskClient {
|
|
49
|
+
readonly baseUrl: string;
|
|
50
|
+
private readonly authHeader;
|
|
51
|
+
private readonly logger?;
|
|
52
|
+
constructor(options: ZendeskClientOptions, logger?: Logger);
|
|
53
|
+
search(type: ZendeskResource, params: SearchParams): Promise<ZendeskRecord[]>;
|
|
54
|
+
count(type: ZendeskResource, query: string): Promise<number>;
|
|
55
|
+
findTicket(id: number | string): Promise<ZendeskRecord | null>;
|
|
56
|
+
findUser(id: number | string): Promise<ZendeskRecord | null>;
|
|
57
|
+
findOrganization(id: number | string): Promise<ZendeskRecord | null>;
|
|
58
|
+
fetchTicketsByIds(ids: Array<number | string>): Promise<Map<number, ZendeskRecord>>;
|
|
59
|
+
fetchUsersByIds(ids: Array<number | string>): Promise<Map<number, ZendeskRecord>>;
|
|
60
|
+
fetchOrganizationsByIds(ids: Array<number | string>): Promise<Map<number, ZendeskRecord>>;
|
|
61
|
+
fetchUserEmails(ids: Array<number | string>): Promise<Map<number, string>>;
|
|
62
|
+
fetchTicketComments(ticketId: number | string): Promise<ZendeskRecord[]>;
|
|
63
|
+
fetchTicketFields(): Promise<RawCustomFieldDefinition[]>;
|
|
64
|
+
fetchUserFields(): Promise<RawCustomFieldDefinition[]>;
|
|
65
|
+
fetchOrganizationFields(): Promise<RawCustomFieldDefinition[]>;
|
|
66
|
+
createTicket(attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
67
|
+
updateTicket(id: number | string, attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
68
|
+
deleteTicket(id: number | string): Promise<void>;
|
|
69
|
+
createUser(attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
70
|
+
updateUser(id: number | string, attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
71
|
+
deleteUser(id: number | string): Promise<void>;
|
|
72
|
+
createOrganization(attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
73
|
+
updateOrganization(id: number | string, attrs: ZendeskRecord): Promise<ZendeskRecord>;
|
|
74
|
+
deleteOrganization(id: number | string): Promise<void>;
|
|
75
|
+
bestEffort<T>(operation: string, defaultValue: T, fn: () => Promise<T>): Promise<T>;
|
|
76
|
+
private findOne;
|
|
77
|
+
private showMany;
|
|
78
|
+
private fetchFields;
|
|
79
|
+
private createOne;
|
|
80
|
+
private updateOne;
|
|
81
|
+
private deleteOne;
|
|
82
|
+
private mustSucceed;
|
|
83
|
+
private get;
|
|
84
|
+
private post;
|
|
85
|
+
private put;
|
|
86
|
+
private delete;
|
|
87
|
+
private send;
|
|
88
|
+
}
|
|
89
|
+
export declare function createZendeskClient(options: ZendeskClientOptions, logger?: Logger): ZendeskClient;
|
|
90
|
+
export type { CustomFieldEntry };
|
|
91
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ZendeskHttpClient = exports.MAX_TOTAL_RESULTS = exports.MAX_PER_PAGE = void 0;
|
|
7
|
+
exports.createZendeskClient = createZendeskClient;
|
|
8
|
+
const superagent_1 = __importDefault(require("superagent"));
|
|
9
|
+
const errors_1 = require("../errors");
|
|
10
|
+
exports.MAX_PER_PAGE = 100;
|
|
11
|
+
exports.MAX_TOTAL_RESULTS = 1000;
|
|
12
|
+
const RESOURCE_PLURAL = {
|
|
13
|
+
ticket: 'tickets',
|
|
14
|
+
user: 'users',
|
|
15
|
+
organization: 'organizations',
|
|
16
|
+
};
|
|
17
|
+
function composeQuery(type, query) {
|
|
18
|
+
const parts = [type ? `type:${type}` : null, (query ?? '').trim()].filter(Boolean);
|
|
19
|
+
return parts.join(' ').trim();
|
|
20
|
+
}
|
|
21
|
+
function extractResource(body, key, operation) {
|
|
22
|
+
const resource = body[key];
|
|
23
|
+
if (resource && typeof resource === 'object' && !Array.isArray(resource)) {
|
|
24
|
+
return resource;
|
|
25
|
+
}
|
|
26
|
+
throw new errors_1.ZendeskApiError(operation, undefined, body, new Error(`Zendesk API returned an unexpected body shape (missing '${key}')`));
|
|
27
|
+
}
|
|
28
|
+
function validate(options) {
|
|
29
|
+
const missing = [];
|
|
30
|
+
if (!options?.subdomain?.trim())
|
|
31
|
+
missing.push('subdomain');
|
|
32
|
+
if (!options?.email?.trim())
|
|
33
|
+
missing.push('email');
|
|
34
|
+
if (!options?.apiToken?.trim())
|
|
35
|
+
missing.push('apiToken');
|
|
36
|
+
if (missing.length > 0) {
|
|
37
|
+
throw new errors_1.ZendeskConfigurationError(`Zendesk client is missing required configuration: ${missing.join(', ')}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
class ZendeskHttpClient {
|
|
41
|
+
constructor(options, logger) {
|
|
42
|
+
validate(options);
|
|
43
|
+
this.baseUrl = `https://${options.subdomain}.zendesk.com/api/v2`;
|
|
44
|
+
const credentials = Buffer.from(`${options.email}/token:${options.apiToken}`).toString('base64');
|
|
45
|
+
this.authHeader = `Basic ${credentials}`;
|
|
46
|
+
this.logger = logger;
|
|
47
|
+
}
|
|
48
|
+
async search(type, params) {
|
|
49
|
+
const query = composeQuery(type, params.query);
|
|
50
|
+
const perPage = Math.min(params.perPage ?? exports.MAX_PER_PAGE, exports.MAX_PER_PAGE);
|
|
51
|
+
return this.mustSucceed(`search(${type})`, async () => {
|
|
52
|
+
const queryParams = {
|
|
53
|
+
query,
|
|
54
|
+
per_page: perPage,
|
|
55
|
+
page: params.page ?? 1,
|
|
56
|
+
};
|
|
57
|
+
if (params.sortBy)
|
|
58
|
+
queryParams.sort_by = params.sortBy;
|
|
59
|
+
if (params.sortOrder)
|
|
60
|
+
queryParams.sort_order = params.sortOrder;
|
|
61
|
+
const body = await this.get('/search.json', queryParams);
|
|
62
|
+
return Array.isArray(body.results) ? body.results : [];
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async count(type, query) {
|
|
66
|
+
return this.mustSucceed(`count(${type})`, async () => {
|
|
67
|
+
const body = await this.get('/search/count.json', { query: composeQuery(type, query) });
|
|
68
|
+
return typeof body.count === 'number' ? body.count : 0;
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
findTicket(id) {
|
|
72
|
+
return this.findOne('ticket', id);
|
|
73
|
+
}
|
|
74
|
+
findUser(id) {
|
|
75
|
+
return this.findOne('user', id);
|
|
76
|
+
}
|
|
77
|
+
findOrganization(id) {
|
|
78
|
+
return this.findOne('organization', id);
|
|
79
|
+
}
|
|
80
|
+
fetchTicketsByIds(ids) {
|
|
81
|
+
return this.showMany('ticket', ids, (r) => [Number(r.id), r]);
|
|
82
|
+
}
|
|
83
|
+
fetchUsersByIds(ids) {
|
|
84
|
+
return this.bestEffort('fetch_users_by_ids', new Map(), () => this.showMany('user', ids, r => [Number(r.id), r]));
|
|
85
|
+
}
|
|
86
|
+
fetchOrganizationsByIds(ids) {
|
|
87
|
+
return this.bestEffort('fetch_organizations_by_ids', new Map(), () => this.showMany('organization', ids, r => [Number(r.id), r]));
|
|
88
|
+
}
|
|
89
|
+
fetchUserEmails(ids) {
|
|
90
|
+
return this.bestEffort('fetch_user_emails', new Map(), () => this.showMany('user', ids, r => [Number(r.id), String(r.email ?? '')]));
|
|
91
|
+
}
|
|
92
|
+
async fetchTicketComments(ticketId) {
|
|
93
|
+
return this.mustSucceed(`fetch_ticket_comments(${ticketId})`, async () => {
|
|
94
|
+
const body = await this.get(`/tickets/${encodeURIComponent(String(ticketId))}/comments.json`);
|
|
95
|
+
return Array.isArray(body.comments) ? body.comments : [];
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
fetchTicketFields() {
|
|
99
|
+
return this.fetchFields('ticket_fields');
|
|
100
|
+
}
|
|
101
|
+
fetchUserFields() {
|
|
102
|
+
return this.fetchFields('user_fields');
|
|
103
|
+
}
|
|
104
|
+
fetchOrganizationFields() {
|
|
105
|
+
return this.fetchFields('organization_fields');
|
|
106
|
+
}
|
|
107
|
+
createTicket(attrs) {
|
|
108
|
+
return this.createOne('ticket', attrs);
|
|
109
|
+
}
|
|
110
|
+
updateTicket(id, attrs) {
|
|
111
|
+
return this.updateOne('ticket', id, attrs);
|
|
112
|
+
}
|
|
113
|
+
deleteTicket(id) {
|
|
114
|
+
return this.deleteOne('ticket', id);
|
|
115
|
+
}
|
|
116
|
+
createUser(attrs) {
|
|
117
|
+
return this.createOne('user', attrs);
|
|
118
|
+
}
|
|
119
|
+
updateUser(id, attrs) {
|
|
120
|
+
return this.updateOne('user', id, attrs);
|
|
121
|
+
}
|
|
122
|
+
deleteUser(id) {
|
|
123
|
+
return this.deleteOne('user', id);
|
|
124
|
+
}
|
|
125
|
+
createOrganization(attrs) {
|
|
126
|
+
return this.createOne('organization', attrs);
|
|
127
|
+
}
|
|
128
|
+
updateOrganization(id, attrs) {
|
|
129
|
+
return this.updateOne('organization', id, attrs);
|
|
130
|
+
}
|
|
131
|
+
deleteOrganization(id) {
|
|
132
|
+
return this.deleteOne('organization', id);
|
|
133
|
+
}
|
|
134
|
+
async bestEffort(operation, defaultValue, fn) {
|
|
135
|
+
try {
|
|
136
|
+
return await fn();
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
this.logger?.('Warn', `[datasource-zendesk] ${operation} failed; degrading: ${err instanceof Error ? err.message : String(err)}`);
|
|
140
|
+
return defaultValue;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ===== Internal helpers =====
|
|
144
|
+
async findOne(type, id) {
|
|
145
|
+
const plural = RESOURCE_PLURAL[type];
|
|
146
|
+
const path = `/${plural}/${encodeURIComponent(String(id))}.json`;
|
|
147
|
+
try {
|
|
148
|
+
const body = await this.mustSucceed(`find(${plural}/${id})`, () => this.get(path));
|
|
149
|
+
return body[type] ?? null;
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
if (err instanceof errors_1.ZendeskApiError && err.status === 404)
|
|
153
|
+
return null;
|
|
154
|
+
throw err;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async showMany(type, ids, project) {
|
|
158
|
+
const plural = RESOURCE_PLURAL[type];
|
|
159
|
+
const uniqueIds = Array.from(new Set(ids.filter(id => id !== null && id !== undefined)));
|
|
160
|
+
const result = new Map();
|
|
161
|
+
if (uniqueIds.length === 0)
|
|
162
|
+
return result;
|
|
163
|
+
for (let i = 0; i < uniqueIds.length; i += exports.MAX_PER_PAGE) {
|
|
164
|
+
const chunk = uniqueIds.slice(i, i + exports.MAX_PER_PAGE);
|
|
165
|
+
// eslint-disable-next-line no-await-in-loop
|
|
166
|
+
const body = await this.mustSucceed(`fetch_${plural}_by_ids`, () => this.get(`/${plural}/show_many.json`, { ids: chunk.join(',') }));
|
|
167
|
+
const records = Array.isArray(body[plural]) ? body[plural] : [];
|
|
168
|
+
for (const record of records) {
|
|
169
|
+
const [key, value] = project(record);
|
|
170
|
+
result.set(key, value);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
async fetchFields(path) {
|
|
176
|
+
return this.mustSucceed(`fetch_${path}`, async () => {
|
|
177
|
+
const body = await this.get(`/${path}.json`);
|
|
178
|
+
return Array.isArray(body[path]) ? body[path] : [];
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async createOne(type, attrs) {
|
|
182
|
+
const plural = RESOURCE_PLURAL[type];
|
|
183
|
+
return this.mustSucceed(`create(${plural})`, async () => {
|
|
184
|
+
const body = await this.post(`/${plural}.json`, { [type]: attrs });
|
|
185
|
+
return extractResource(body, type, `create(${plural})`);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
async updateOne(type, id, attrs) {
|
|
189
|
+
const plural = RESOURCE_PLURAL[type];
|
|
190
|
+
return this.mustSucceed(`update(${plural}/${id})`, async () => {
|
|
191
|
+
const body = await this.put(`/${plural}/${encodeURIComponent(String(id))}.json`, {
|
|
192
|
+
[type]: attrs,
|
|
193
|
+
});
|
|
194
|
+
return extractResource(body, type, `update(${plural}/${id})`);
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async deleteOne(type, id) {
|
|
198
|
+
const plural = RESOURCE_PLURAL[type];
|
|
199
|
+
await this.mustSucceed(`delete(${plural}/${id})`, async () => {
|
|
200
|
+
await this.delete(`/${plural}/${encodeURIComponent(String(id))}.json`);
|
|
201
|
+
return null;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
async mustSucceed(operation, fn) {
|
|
205
|
+
try {
|
|
206
|
+
return await fn();
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
if (err instanceof errors_1.ZendeskApiError)
|
|
210
|
+
throw err;
|
|
211
|
+
const status = err?.status;
|
|
212
|
+
const body = err?.response?.body;
|
|
213
|
+
throw new errors_1.ZendeskApiError(operation, status, body, err);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
get(path, query) {
|
|
217
|
+
const request = superagent_1.default.get(`${this.baseUrl}${path}`);
|
|
218
|
+
if (query)
|
|
219
|
+
request.query(query);
|
|
220
|
+
return this.send(request);
|
|
221
|
+
}
|
|
222
|
+
post(path, body) {
|
|
223
|
+
return this.send(superagent_1.default.post(`${this.baseUrl}${path}`).send(body));
|
|
224
|
+
}
|
|
225
|
+
put(path, body) {
|
|
226
|
+
return this.send(superagent_1.default.put(`${this.baseUrl}${path}`).send(body));
|
|
227
|
+
}
|
|
228
|
+
delete(path) {
|
|
229
|
+
return this.send(superagent_1.default.delete(`${this.baseUrl}${path}`));
|
|
230
|
+
}
|
|
231
|
+
async send(request) {
|
|
232
|
+
const response = await request
|
|
233
|
+
.set('Authorization', this.authHeader)
|
|
234
|
+
.set('Accept', 'application/json')
|
|
235
|
+
.set('Content-Type', 'application/json');
|
|
236
|
+
return (response.body ?? {});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
exports.ZendeskHttpClient = ZendeskHttpClient;
|
|
240
|
+
function createZendeskClient(options, logger) {
|
|
241
|
+
return new ZendeskHttpClient(options, logger);
|
|
242
|
+
}
|
|
243
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY2xpZW50L2luZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQTRYQSxrREFFQztBQXBYRCw0REFBb0M7QUFFcEMsc0NBQXVFO0FBRTFELFFBQUEsWUFBWSxHQUFHLEdBQUcsQ0FBQztBQUNuQixRQUFBLGlCQUFpQixHQUFHLElBQUksQ0FBQztBQUV0QyxNQUFNLGVBQWUsR0FBb0M7SUFDdkQsTUFBTSxFQUFFLFNBQVM7SUFDakIsSUFBSSxFQUFFLE9BQU87SUFDYixZQUFZLEVBQUUsZUFBZTtDQUM5QixDQUFDO0FBNENGLFNBQVMsWUFBWSxDQUFDLElBQTRCLEVBQUUsS0FBYTtJQUMvRCxNQUFNLEtBQUssR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsUUFBUSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBYSxDQUFDO0lBRS9GLE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBRUQsU0FBUyxlQUFlLENBQ3RCLElBQW1CLEVBQ25CLEdBQW9CLEVBQ3BCLFNBQWlCO0lBRWpCLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUUzQixJQUFJLFFBQVEsSUFBSSxPQUFPLFFBQVEsS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxFQUFFLENBQUM7UUFDekUsT0FBTyxRQUF5QixDQUFDO0lBQ25DLENBQUM7SUFFRCxNQUFNLElBQUksd0JBQWUsQ0FDdkIsU0FBUyxFQUNULFNBQVMsRUFDVCxJQUFJLEVBQ0osSUFBSSxLQUFLLENBQUMsMkRBQTJELEdBQUcsSUFBSSxDQUFDLENB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
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ZendeskClient } from './client';
|
|
2
|
+
import type { ZendeskClientOptions } from './types';
|
|
3
|
+
import type { Logger } from '@forestadmin/datasource-toolkit';
|
|
4
|
+
/**
|
|
5
|
+
* A Zendesk client can always be supplied directly, or built on-the-fly from raw
|
|
6
|
+
* credentials. The datasource factory and the smart-action plugins share this
|
|
7
|
+
* contract so users never have to instantiate a client by hand when they don't
|
|
8
|
+
* already have one.
|
|
9
|
+
*/
|
|
10
|
+
export type ZendeskClientProvider = {
|
|
11
|
+
client: ZendeskClient;
|
|
12
|
+
} | ({
|
|
13
|
+
client?: never;
|
|
14
|
+
} & ZendeskClientOptions);
|
|
15
|
+
export declare function resolveZendeskClient(options: ZendeskClientProvider, logger?: Logger): ZendeskClient;
|
|
16
|
+
//# sourceMappingURL=client-options.d.ts.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveZendeskClient = resolveZendeskClient;
|
|
4
|
+
const client_1 = require("./client");
|
|
5
|
+
function resolveZendeskClient(options, logger) {
|
|
6
|
+
return 'client' in options && options.client
|
|
7
|
+
? options.client
|
|
8
|
+
: (0, client_1.createZendeskClient)(options, logger);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpZW50LW9wdGlvbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvY2xpZW50LW9wdGlvbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7QUFnQkEsb0RBT0M7QUFuQkQscUNBQStDO0FBWS9DLFNBQWdCLG9CQUFvQixDQUNsQyxPQUE4QixFQUM5QixNQUFlO0lBRWYsT0FBTyxRQUFRLElBQUksT0FBTyxJQUFJLE9BQU8sQ0FBQyxNQUFNO1FBQzFDLENBQUMsQ0FBQyxPQUFPLENBQUMsTUFBTTtRQUNoQixDQUFDLENBQUMsSUFBQSw0QkFBbUIsRUFBQyxPQUErQixFQUFFLE1BQU0sQ0FBQyxDQUFDO0FBQ25FLENBQUMifQ==
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ZendeskClient } from '../client';
|
|
2
|
+
import type { CustomFieldEntry, ZendeskRecord, ZendeskResource } from '../types';
|
|
3
|
+
import type { AggregateResult, Aggregation, Caller, ConditionTree, DataSource, Filter, Logger, PaginatedFilter, Projection, RecordData, Sort } from '@forestadmin/datasource-toolkit';
|
|
4
|
+
import { BaseCollection } from '@forestadmin/datasource-toolkit';
|
|
5
|
+
export { BOOLEAN_OPS, DATE_OPS, ID_OPS, NUMBER_OPS, STRING_OPS } from '../query/operators';
|
|
6
|
+
export default abstract class BaseZendeskCollection extends BaseCollection {
|
|
7
|
+
protected readonly client: ZendeskClient;
|
|
8
|
+
protected readonly resource: ZendeskResource;
|
|
9
|
+
protected readonly sortableFields: Record<string, string>;
|
|
10
|
+
protected readonly logger?: Logger;
|
|
11
|
+
protected readonly zendeskIdToColumnName: Map<number, string>;
|
|
12
|
+
constructor(name: string, datasource: DataSource, client: ZendeskClient, resource: ZendeskResource, sortableFields: Record<string, string>, logger?: Logger);
|
|
13
|
+
abstract create(caller: Caller, data: RecordData[]): Promise<RecordData[]>;
|
|
14
|
+
abstract list(caller: Caller, filter: PaginatedFilter, projection: Projection): Promise<RecordData[]>;
|
|
15
|
+
abstract update(caller: Caller, filter: Filter, patch: RecordData): Promise<void>;
|
|
16
|
+
abstract delete(caller: Caller, filter: Filter): Promise<void>;
|
|
17
|
+
aggregate(caller: Caller, filter: Filter, aggregation: Aggregation, limit?: number): Promise<AggregateResult[]>;
|
|
18
|
+
/** Fetch a single resource by primary key, or null when it does not exist (404). */
|
|
19
|
+
protected abstract findOne(id: number | string): Promise<ZendeskRecord | null>;
|
|
20
|
+
/** Convert a raw Zendesk payload into the Forest column layout this collection exposes. */
|
|
21
|
+
protected abstract serializeRecord(record: ZendeskRecord): RecordData;
|
|
22
|
+
/**
|
|
23
|
+
* Count the records matching the filter. An exact id-lookup is resolved by fetching the
|
|
24
|
+
* referenced records and re-applying the full condition tree in memory, so that non-existent
|
|
25
|
+
* ids and sibling conditions (scope/segment) are honored rather than blindly trusted.
|
|
26
|
+
*/
|
|
27
|
+
protected aggregateCount(caller: Caller, filter: Filter): Promise<number>;
|
|
28
|
+
protected addCustomFields(customFields: CustomFieldEntry[]): void;
|
|
29
|
+
protected translateSort(sort: Sort | undefined): {
|
|
30
|
+
sortBy?: string;
|
|
31
|
+
sortOrder?: 'asc' | 'desc';
|
|
32
|
+
};
|
|
33
|
+
protected searchRecords(filter: PaginatedFilter | undefined): Promise<ZendeskRecord[]>;
|
|
34
|
+
protected fetchRecordsByIds(ids: number[]): Promise<ZendeskRecord[]>;
|
|
35
|
+
protected serializeForFilter(records: ZendeskRecord[]): Promise<RecordData[]>;
|
|
36
|
+
protected refine(records: RecordData[], filter: PaginatedFilter | undefined, timezone: string): RecordData[];
|
|
37
|
+
protected buildZendeskQuery(filter: Filter | PaginatedFilter | undefined): string;
|
|
38
|
+
/**
|
|
39
|
+
* Returns the list of primary-key values if the condition tree is an exact `id`
|
|
40
|
+
* lookup (Equal N or In [N1, N2, ...]), otherwise null. Lets `list/update/delete`
|
|
41
|
+
* bypass the Search API and hit the resource endpoint directly.
|
|
42
|
+
*/
|
|
43
|
+
protected extractIdLookup(tree: ConditionTree | undefined): number[] | null;
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the primary keys targeted by update/delete. A pure id-lookup is authoritative; an
|
|
46
|
+
* id-lookup combined with other conditions (scope/segment) is re-checked in memory so we never
|
|
47
|
+
* mutate a record outside the caller's perimeter. Otherwise every matching page is collected.
|
|
48
|
+
*/
|
|
49
|
+
protected resolveIds(filter: Filter | undefined, timezone: string): Promise<number[]>;
|
|
50
|
+
private searchAllIds;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=base-zendesk-collection.d.ts.map
|