docs(pylon): document the Pylon datasource for the Ruby agent - #30
docs(pylon): document the Pylon datasource for the Ruby agent#30christophebrun-forest wants to merge 3 commits into
Conversation
The `forest_admin_datasource_pylon` gem landed in agent-ruby#369: five collections over the Pylon API, cursor pagination, per-endpoint rate limiting, custom-field introspection, CRUD writes and two action plugins. Two pages, mirroring how the Zendesk connector is documented: - `get-started/connect/data-sources/pylon` — installation, the configuration options and their defaults, the collections and their relations, the embedded conversation thread, custom-field mapping, then what the API cannot do and what the datasource does about it: the filter allow-list per collection, the primary-key short-circuit and its caps, the absence of any sort parameter, why only the two fetch-all collections aggregate, the write budget, the rate-limit / retry trade and the error classes. - `product/process/advanced-concepts/plugins/pylon` — `CloseIssue` and `CreateIssueWithNotification`: options, form fields, the wizard, which scope bounds a close, and the batch cap. Ruby-only pages rather than tabbed ones: there is no Node.js Pylon connector, so a `Node.js` tab would have nothing to say. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
| @agent.collection :Customer do |collection| | ||
| collection.use( | ||
| ForestAdminDatasourcePylon::Plugins::CreateIssueWithNotification, | ||
| datasource: pylon_datasource, | ||
| sender_email: 'support@acme.com' | ||
| ) | ||
| end | ||
|
|
||
| @agent.collection :Order do |collection| |
There was a problem hiding this comment.
🟠 High plugins/pylon.mdx:23
The snippet calls collection on the uninitialized @agent, so copying it raises a NoMethodError and registers neither plugin action. The documented factory setup stores the agent in @create_agent; use that receiver for both collection declarations.
-@agent.collection :Customer do |collection|
+@create_agent.collection :Customer do |collection|
collection.use(
ForestAdminDatasourcePylon::Plugins::CreateIssueWithNotification,
datasource: pylon_datasource,
sender_email: 'support@acme.com'
)
end
-@agent.collection :Order do |collection|
+@create_agent.collection :Order do |collection|🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @product/process/advanced-concepts/plugins/pylon.mdx around lines 23-31:
The snippet calls `collection` on the uninitialized `@agent`, so copying it raises a `NoMethodError` and registers neither plugin action. The documented factory setup stores the agent in `@create_agent`; use that receiver for both collection declarations.
There was a problem hiding this comment.
Fixed in b79d271, but not the way the suggestion proposes — the diagnosis was off by one line.
@agent.collection is a valid call: AgentFactory includes ForestAdminDatasourceCustomizer::DSL::DatasourceHelpers, which defines collection(name, &block) as a wrapper over customize_collection. So the receiver is not the problem.
The actual defect was on line 21, which called add_datasource and threw away its return value, leaving @agent unbound two lines later. add_datasource returns self, so binding it is the whole fix:
@agent = ForestAdminAgent::Builder::AgentFactory.instance.add_datasource(pylon_datasource, {})Switching to @create_agent instead would have made this the only plugins page using that name — plugins/zendesk.mdx and plugins/active-storage.mdx both use @agent. @create_agent is the datasource-page convention, which is why data-sources/pylon.mdx uses it: there the snippet shows the whole CreateAgent.setup! body, where that is the real local name.
| The Pylon connector is only available for Ruby (gem `forest_admin_datasource_pylon`). | ||
| </Warning> | ||
|
|
||
| Both plugins require the [Pylon datasource](/get-started/connect/data-sources/pylon) to be registered on your back-end: they need the `Datasource` instance to reach the Pylon API client. |
There was a problem hiding this comment.
🟡 Medium plugins/pylon.mdx:12
The page incorrectly tells users that both plugins require the Datasource to be registered with the agent, even though the actions use only the supplied datasource: object and its client; users who only need these actions are therefore steered into unnecessary datasource registration. Document that a constructed Datasource passed via datasource: is sufficient.
- Both plugins require the [Pylon datasource](/get-started/connect/data-sources/pylon) to be registered on your back-end: they need the `Datasource` instance to reach the Pylon API client.
+ Both plugins require a constructed `Datasource` instance passed via the `datasource:` option so they can reach the Pylon API client; they do not require that datasource to be registered with the agent.🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @product/process/advanced-concepts/plugins/pylon.mdx around line 12:
The page incorrectly tells users that both plugins require the `Datasource` to be registered with the agent, even though the actions use only the supplied `datasource:` object and its `client`; users who only need these actions are therefore steered into unnecessary datasource registration. Document that a constructed `Datasource` passed via `datasource:` is sufficient.
There was a problem hiding this comment.
Correct on the substance, and deliberately left as is in this PR.
Both plugins reach Pylon through options[:datasource].client only — CreateIssueWithNotification#create_issue and CloseIssue#apply_state — and never look the datasource up on the agent. A Datasource.new(api_key: ...) that is never passed to add_datasource builds its Client and is enough for both actions, so "must be registered" does overstate it.
The reason it stays: that sentence is copied verbatim from the Ruby tab of plugins/zendesk.mdx, where the same thing is true of CreateTicketWithNotification / CloseTicket. Relaxing it on the Pylon page alone would leave the two connectors documented as having different requirements when they have the same one, which is more confusing than the overstatement.
Worth its own PR that fixes both pages together. One thing to weigh there: constructing a Pylon Datasource runs the custom-field introspection (three GET /custom-fields calls in front of the Rails boot), so an unregistered instance kept only to feed the plugins pays that cost for a schema nobody reads — which is a reason to keep recommending registration even once the wording no longer requires it.
| | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ||
| | `datasource` | **Required.** The `ForestAdminDatasourcePylon::Datasource` instance. | | ||
| | `action_name` | Overrides the action label. Defaults to `'Create Pylon issue and notify'`. | | ||
| | `destination` | Channel the first message is delivered through: `email` (default), `slack`, `in_app_chat`, `customer_portal`, `sms`, `whatsapp` or `internal`. An unknown value is refused at registration. | |
There was a problem hiding this comment.
🟡 Medium plugins/pylon.mdx:68
The destination table says internal delivers the first message through a channel, but destination: 'internal' omits destination_metadata so the message remains internal and the requester is not contacted. Update the description to reflect this behavior.
| | `destination` | Channel the first message is delivered through: `email` (default), `slack`, `in_app_chat`, `customer_portal`, `sms`, `whatsapp` or `internal`. An unknown value is refused at registration. | | |
| | `destination` | Channel the first message is delivered through: `email` (default), `slack`, `in_app_chat`, `customer_portal`, `sms`, or `whatsapp`. Use `internal` to keep the first message internal without contacting the requester. An unknown value is refused at registration. | |
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @product/process/advanced-concepts/plugins/pylon.mdx around line 68:
The `destination` table says `internal` delivers the first message through a channel, but `destination: 'internal'` omits `destination_metadata` so the message remains internal and the requester is not contacted. Update the description to reflect this behavior.
Review of #30 against the merged code at cf20168a. Five places where the pages said something the package does not do. - `GET /me` was introduced as "the health check", which reads as a boot-time validation of the token. `Client#me` is called nowhere: the only boot call is `GET /custom-fields`, which degrades to the native schema on failure. A wrong api_key therefore boots the agent and fails on the first list. Say that, and point at `client.me` as the check to run yourself. - "Two relations Pylon's shape does not allow" covered one reason for two relations. Team membership is a shape limitation; `PylonAccount.owner_id` is not — the embedder would resolve it like any other key, and it is left a plain column because nothing in the panel asks for it yet. Split the two. - The plugins snippet called `add_datasource` without binding its return, then used `@agent` on the next line. Bind it. `@agent.collection` is valid — `AgentFactory` includes `DSL::DatasourceHelpers` — and is what the Zendesk plugins page already uses, so only the assignment was missing. - The `destination` row listed `internal` among the channels the first message is delivered through. It is the absence of a delivery: `Payload#build` omits `destination_metadata` entirely for it. The page had this right two paragraphs above and wrong in the table. - The filter tables list no `NOT_EQUAL`, because `operator_maps.rb` deliberately declares only `not_in` — the toolkit republishes the other spelling from it. The tables transcribe the wire maps, so the UI offers a filter the page reads as unavailable. One note under the tabs, next to the presence rewrite it shares a mechanism with. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Follow-up to b79d271, which overcorrected: it said nothing calls Pylon at boot and that the token is checked by the first request needing it. The custom-field introspection does call Pylon at boot and does carry the token -- `fetch_custom_fields` wraps `must_succeed` in `best_effort(default: nil)`, so a 401 there returns nil, logs a warning and leaves the agent on the native schema. The call happens; it just does not validate. Worth the precision because it is where the operator looks: a wrong key leaves a custom-fields warning in the boot log, which the Note further down already describes, and nothing else until the first list. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Why
agent-ruby#369 merged
forest_admin_datasource_pylon(released in agent-ruby 1.41.0). Nothing in the docs mentioned it, so the gem shipped with its package README as the only place a customer could learn how to configure it — or, more to the point, what the connector refuses to do and why.Pylon is a ticketing API, not a database, so most of the page is that second half: several things Forest asks for have no equivalent, and the datasource answers a
400naming the reason rather than something that looks right and is not. That is the behaviour a customer meets first and has no way to guess.What changed
Two new pages, mirroring how the Zendesk connector is documented (datasource page + plugins page):
get-started/connect/data-sources/pylon.mdxbase_url, both timeouts,retry_policy,rate_limiter: nil, theboot_*trio.messagesthread column: field-by-field shape, the lazy fetch, the 10-row cap, and why a capped row isnilrather than[].is_read_onlyflag rule, and the boot-time degradation.idshort-circuit andMAX_ID_LOOKUPS, the absence of any sort parameter, the cursor pagination caps, why onlyPylonUser/PylonTeamaggregate — and exactly — the write budget and the per-collection create-only / update-only fields.DEFAULT_MAX_INTERVAL: 12trade stated and the snippet to choose the other way; then the error-class table and logging.product/process/advanced-concepts/plugins/pylon.mdxCreateIssueWithNotification: options, form fields, the two-page templates wizard, the refusals at registration, and what the operator reads back.CloseIssue: options, the one-action-per-scope registration and its name-collision refusal, which scope bounds it, and the 20-issue batch cap.Both pages are Ruby-only, with a
<Warning>saying so, rather than tabbed: there is no Node.js Pylon connector, so aNode.jstab would have nothing in it.Also:
docs.jsonnav for both pages, and a Pylon entry in the "Available datasources" list ofget-started/connect/data-sources/overview.mdx.How it was written
Every figure, option name and default is transcribed from the merged code at
cf20168a, not from the PR description: the package README,configuration.rb,retry_policy.rb,rate_limits.rb, the five collections with theirschema_definition/api_filters,operator_maps.rb,base_collection.rb/fetch_all_collection.rb/writes.rb,custom_fields_introspector.rb,cursor_walker.rb, and both plugins.Two places where that mattered:
CloseIssuescope caveat is the corrected reading, the oneb6bd085blanded. Mounted onPylonIssue, a scope bounds exactly what the action closes — the ids are resolved through the host collection first, and the agent intersects the operator's scope into that filter. The caveat only applies to the host-collection form, where the id column is the authority. The earlier wording had it backwards, and telling an operator a scope buys them nothing when it buys them the whole selection is the one error worth not copying into the docs.MAX_ID_LOOKUPS, which bounds a page rather than a selection (a93474b1), so a wider selection is read a page at a time rather than truncated.Three limitations the PR filed on Linear rather than fixing are not documented as behaviour, since they are open tickets: EXT-21 (a non-positive page limit), EXT-22 (a nested
andcarrying anid— the page does state that this shape is refused, which is what EXT-22 would change) and EXT-23 (an aggregation applying an operation the column type cannot take).Note
Add documentation for Pylon datasource and plugins
Macroscope summarized d727d68.