Skip to content

feat(datasource-zendesk): add Zendesk datasource#1658

Open
christophebrun-forest wants to merge 5 commits into
mainfrom
feat/datasource-zendesk
Open

feat(datasource-zendesk): add Zendesk datasource#1658
christophebrun-forest wants to merge 5 commits into
mainfrom
feat/datasource-zendesk

Conversation

@christophebrun-forest

@christophebrun-forest christophebrun-forest commented Jun 11, 2026

Copy link
Copy Markdown
Member

Summary

New @forestadmin/datasource-zendesk package: a datasource exposing Zendesk tickets, users and organizations over the Zendesk API, with condition-tree translation, custom-field introspection, and two plugins (close-ticket, create-ticket-with-notification).

What's included

  • Collections for tickets / users / organizations (CRUD + count) over the Zendesk Search & resource APIs
  • Condition-tree → Zendesk Search translation, with the primary key resolved via direct id lookup (outside Search)
  • Custom-field introspection at boot (ticket_fields / user_fields / organization_fields) mapped to Forest columns
  • Plugins: close-ticket and create-ticket-with-notification (multi-step form with email templates)

Behavior & limitations

  • Zendesk Search has no OR operator: multi-value In/NotIn matches each value exactly only on the primary key; on other fields Zendesk ANDs the terms (documented in the README).
  • The Search API caps pagination at 1000 results; larger windows raise UnsupportedOperatorError.
  • Aggregation is limited to Count (no groups), reflecting Zendesk Search capabilities.

Correctness highlights (addressed during review)

  • Scope/segment enforcement: an id-lookup combined with other conditions is re-checked in memory, so list/update/delete/count never bypass the caller's perimeter (including scopes on the derived requester_email).
  • Bulk update/delete page through every match up to the Search cap instead of silently affecting only the first 100, and warn when the cap is exceeded.
  • Pagination honors non-page-aligned skip/limit by fetching the covering pages and slicing.
  • Aggregate count verifies record existence rather than trusting the requested id count.
  • description is ignored on update (Zendesk derives it from the first comment and cannot edit it afterwards).
  • create-ticket form loads the selected record's fields so requesterEmailDefault and {{record.x}} template tokens are populated on the second step.
  • Operator vocabularies are advertised so the operators-equivalence decorator does not rewrite supported filters into an unsupported Or.

Testing

  • yarn workspace @forestadmin/datasource-zendesk test133 tests passing
  • lint + build green
  • Validated end-to-end against a live Zendesk account from a NestJS POC (ticket creation with notification, filtering, scoped delete)

🤖 Generated with Claude Code

Note

Add Zendesk datasource with ticket, user, and organization collections

  • Introduces a new @forestadmin/datasource-zendesk package exposing three collections (zendesk_ticket, zendesk_user, zendesk_organization) backed by the Zendesk Search and REST APIs via a new ZendeskHttpClient.
  • Supports list, create, update, delete, and count operations with filter/sort/pagination; searches are translated from Forest condition trees to Zendesk Search query strings via condition-tree-translator.ts.
  • Introspects active custom fields at startup and maps them into the collection schema; ticket custom fields use custom_<id> naming, user/organization fields use the Zendesk field key.
  • Adds two smart-action plugins: closeTicketPlugin (mark tickets solved/closed with outcome classification) and createTicketWithNotificationPlugin (create a ticket with an optional public/private comment and writeback of the ticket id).
  • Risk: Zendesk Search is capped at 1,000 results (MAX_TOTAL_RESULTS); bulk update/delete operations that match more than 1,000 records will silently stop after that cap with a logger warning.

Macroscope summarized f9e1f86.

christophebrun-forest and others added 5 commits June 11, 2026 11:49
New @forestadmin/datasource-zendesk package: collections for tickets,
users and organizations over the Zendesk API, condition-tree translation,
custom-field introspection, and plugins (close-ticket,
create-ticket-with-notification).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address correctness issues found in review of the Zendesk datasource:

- id-lookups combined with sibling conditions (scope/segment) are now
  re-checked in memory, so list/update/delete/count never bypass the
  caller's perimeter
- update/delete page through every match up to the Search cap instead of
  silently affecting only the first 100, warning when the cap is exceeded
- In/NotIn dropped from Search-backed fields (Zendesk has no OR); kept on
  the primary key, which is resolved by id lookup
- sort and pagination are applied in memory on the id-lookup path
- aggregateCount fetches and filters the referenced ids instead of trusting
  their count blindly
- searchRecords fetches the pages covering a non-aligned window and slices,
  instead of snapping skip to a page boundary
- description is ignored on update (Zendesk derives it from the first
  comment and cannot edit it afterwards)
- schema made honest: id advertises {Equal, In}, booleans {Equal, NotEqual},
  non-sortable foreign keys no longer claim isSortable
- operator sets extracted to a single shared module

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The multi-step "create ticket and notify" form resolved its requester
email default and message template against an empty record: getSingleRecord
called getRecord([]) with an empty projection, so {{record.x}} tokens and
record-based defaults always rendered blank on the second step.

Request the collection's column fields from the action context schema so the
record is populated before interpolation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removing In/NotIn from the string/number operator sets backfired: the
operators-equivalence decorator re-advertises them (derivable from Equal /
NotEqual) and rewrites incoming In into an Or tree, which the translator
rejects — turning a common `status In [...]` filter into a 500 instead of
the previous (empty) result.

Advertise In/NotIn again so the decorator leaves them untouched and the
translator handles them natively. Multi-value membership on Search-resolved
fields keeps Zendesk's AND semantics (documented); only the primary key
matches each value, via the id-lookup path. ID_OPS and the isSortable
cleanup are kept.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
serializeRecord builds tickets with an empty email map, so re-applying a
scope/segment condition on requester_email in memory (aggregateCount and
resolveIds) always saw null and dropped every record — making count return 0
and update/delete no-op for tickets that legitimately matched.

Add a serializeForFilter hook the ticket collection overrides to resolve
requester_email before in-memory filtering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@qltysh

qltysh Bot commented Jun 11, 2026

Copy link
Copy Markdown

26 new issues

Tool Category Rule Count
qlty Structure Function with high complexity (count = 14): extractIdLookup 11
qlty Structure Function with many parameters (count = 6): constructor 7
qlty Structure Function with many returns (count = 5): extractIdLookup 6
qlty Structure Deeply nested control flow (level = 4) 2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant