Skip to content

feat(datasource-pylon): all collections + relations (EXT-8) - #354

Open
christophebrun-forest wants to merge 5 commits into
feat/datasource-pylonfrom
ext-8-collections-relations
Open

feat(datasource-pylon): all collections + relations (EXT-8)#354
christophebrun-forest wants to merge 5 commits into
feat/datasource-pylonfrom
ext-8-collections-relations

Conversation

@christophebrun-forest

@christophebrun-forest christophebrun-forest commented Aug 12, 2026

Copy link
Copy Markdown
Member

Story

EXT-8 — Story 4: All collections + relations

Adds the four remaining read-only collections and wires the relational graph between issues, accounts, contacts, users and teams.

What's inside

Client — search/list/fetch endpoints for the four resources, mutualized in generic private helpers (search_issues/fetch_issue now delegate to them, behavior unchanged).

Collections

  • PylonAccount, PylonContact (CursorCollection): cursor-paginated listing (GET, 60 req/min) when browsing, POST /search (20 req/min) as soon as a filter or free-text search is present, and a single-id fast path on the record endpoint for detail views. Their search endpoints filter id server-side, so unlike issues they need no primary-key short-circuit: id sits in api_filters and translates like any other field, or included.
  • PylonUser, PylonTeam (FetchAllCollection): GET /users / GET /teams return the complete dataset, so filtering, sorting and pagination run in memory — correct because the snapshot is complete, and every advertised operator is proven in-memory-evaluable through the toolkit's equivalence machinery (guarded by a programmatic spec). Deactivated agents are included so historical assignees stay resolvable.

Relationsissue → account / requester (contact) / assignee (user) / team (ManyToOne) with their OneToMany inverses, plus contact → account and account → contacts. All reverse sides ride the existing server-side filters (account_id, requester_id, assignee_id, team_id).

RelationEmbedder — schema-driven port of the Zendesk pattern: ManyToOne projections are resolved in bulk at read time (one id in [...] search per foreign collection per page for accounts/contacts, chunked at 100; one indexed fetch-all for users/teams), reusing each foreign collection's own serializer instead of duplicating field lists. A relation the projection does not ask for costs zero requests.

Filter allow-lists are transcribed from the Pylon API reference (static.usepylon.com/openapi.json); fields the API cannot filter advertise no operator, and external_ids advertises none because the API matches bare id strings while the column displays {external_id, label} objects.

Tests

  • 430 examples, 0 failures
  • Line coverage 100% (877/877), branch 93.31%
  • RuboCop: 0 offenses

🤖 Generated with Claude Code

Note

Add PylonAccount, PylonContact, PylonUser, and PylonTeam collections to the Pylon datasource

  • Registers four new collections (Account, Contact, User, Team) alongside the existing Issue collection in datasource.rb.
  • Adds CursorCollection as a base for cursor-paginated collections (Account, Contact, Issue), routing list requests to the optimal Pylon endpoint (GET list, POST search, or GET by id) and batching related-record lookups by id.
  • Adds FetchAllCollection as a base for unpaginated collections (User, Team), fetching the full dataset once and applying filtering, sorting, and pagination in memory.
  • Adds RelationEmbedder to bulk-fetch and embed ManyToOne related records into list results; Issue now embeds account, requester, assignee, and team relations.
  • Extracts shared operator maps into Query::OperatorMaps and shared serialization helpers into RecordSerialization, used by all collection schemas.
  • Extends client.rb with search, list, fetch-all, and fetch-one methods for accounts, contacts, users, and teams via private shared helpers.

Macroscope summarized e3a0366.

christophebrun-forest and others added 5 commits August 12, 2026 18:28
Adds search/list/fetch methods for the four new resources, routed
through shared private helpers; search_issues and fetch_issue now
delegate to the same helpers with unchanged behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Extracts the cursor-walk search flow, sort warning and operator maps
into BaseCollection and shared modules; declares the four ManyToOne
relations on PylonIssue and adds the schema-driven RelationEmbedder.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cursor-paginated read-only collections with server-side filters,
free-text search, a single-id fast path on the record endpoint and
their relations to issues, contacts and account.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fetch-all read-only collections: the endpoints return the complete
dataset, so filtering, sorting and pagination run in memory over
operators proven evaluable by the toolkit equivalence machinery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Registers the five collections on the datasource and covers the
relation embedding end to end: bulk id search per foreign collection,
chunking, dedup and missing-record handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Aug 12, 2026

Copy link
Copy Markdown

EXT-8

@qltysh

qltysh Bot commented Aug 12, 2026

Copy link
Copy Markdown

12 new issues

Tool Category Rule Count
qlty Structure Function with many parameters (count = 4): search_accounts 8
qlty Duplication Found 34 lines of similar code in 2 locations (mass = 114) 2
qlty Structure Function with high complexity (count = 5): compare_clauses 2

fetch_resource('issues', id)
end

def search_accounts(limit:, cursor: nil, filter: nil, search_text: nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 4): search_accounts [qlty:function-parameters]

fetch_resource('accounts', id)
end

def search_contacts(limit:, cursor: nil, filter: nil, search_text: nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 4): search_contacts [qlty:function-parameters]


private

def search_resource(path, limit:, cursor: nil, filter: nil, search_text: nil)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 5): search_resource [qlty:function-parameters]

end
end
end
end

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 34 lines of similar code in 2 locations (mass = 114) [qlty:similar-code]

'nor POST /accounts/search takes a sort parameter, so accounts come back in the order the API imposes.'
end

def search_page(limit:, cursor:, filter:, search_text:)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 4): search_page [qlty:function-parameters]

'nor POST /contacts/search takes a sort parameter, so contacts come back in the order the API imposes.'
end

def search_page(limit:, cursor:, filter:, search_text:)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 4): search_page [qlty:function-parameters]

return ascending ? comparison : -comparison
end

0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 5): compare_clauses [qlty:function-complexity]

return 1 if left.nil?
return -1 if right.nil?

(left <=> right) || (left.to_s <=> right.to_s)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with high complexity (count = 5): compare_values [qlty:function-complexity]

'POST /issues/search always returns issues from the most recent to the oldest.'
end

def search_page(limit:, cursor:, filter:, search_text:)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 4): search_page [qlty:function-parameters]

# not it resolved: a null foreign key, and a record the operator can no
# longer reach, both read as "no related record" rather than as a row
# missing the field.
def embed_foreign(foreign_collection, relations, records, rows)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 4): embed_foreign [qlty:function-parameters]

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