Skip to Content
Internal docs are powered by Nextra Docs Theme.
SystemsPlatformAPI2Rails to API2 migrationPackagesConversation exploration and QA read Rails-client retirement

Conversation exploration and QA read Rails-client retirement

ENG-3037  (child of ENG-2681) removes the Rails client from the 14 conversation exploration and QA read files in rulebase-ui/app/features/conversations. Owner: Chidi. The scoped files now import only ~/lib/rulebase-api2-client/client and the generated API2 schema; there is no Rails fallback path and no Rails base URL.

Explicitly out of scope and still Rails-backed: delete conversation, request QA, transcription, transcript editing, contest-feedback submission, attachment upload, import conversation (POST /conversations/import stays on rulebaseApi), manual-evaluation submission, and Admin source retirement.

Routes

Existing parity-complete API2 routes wired as-is: GET /conversations (cursor list), GET /conversation-filters, GET /tags, evaluation contest reads, and QA agent evaluation reads.

New native reads in src/routes/conversation-qa-reads.ts with query glue in src/lib/conversation-qa-reads/, plus one taxonomy route:

RoutePermission / scopeBehavior reproduced from Rails
GET /conversation_threadsweb session, tenantpage[page]/page[limit], JSON filter (same snake_case keys as /conversations; team_ids → group_ids), JSON sort over contacts_count, initial_contact_at, last_contact_at; default initial_contact_at DESC, id DESC; plainto_tsquery('simple') search; Rails meta.page.{next,limit,total}; contact aggregation; root conversation prefixed IDs.
GET /conversations/{conversation_id}/qa_evaluationsweb session, tenant, conversation must belong to tenantqa_evaluation_* prefixed IDs, ai/human source, created_at DESC, Active Storage files attachments served as signed absolute URLs.
GET /conversations/{conversation_id}/conversation_cx_risk_snapshot_scorecard_resultsweb session, tenantScorecard → criterion (binary/scaled/options) → nested children results with score options, ordered as Rails.
GET /conversation_cx_risk_snapshots/{id}conversation_cx_risk_snapshot:allSnapshot detail; excludes last_updated_at IS NULL; churn thresholds high ≥ 0.7, medium ≥ 0.4, low < 0.4, none when null.
GET /conversations/searchweb session, tenantTicket-ID/external-ID search for the ticket-IDs filter command and the import suggestion read shape (import itself remains Rails). Rails ConversationsController#search guard preserved: any same-organization conversation with the external_id (including rows the member’s policy scope hides) returns null before any provider or cache call. Rails SuggestedConversationImport.search cache preserved as Redis suggested-conversation-import:v1:{rails_org_id}:{sha256(external_id)}, TTL 1 day, positive and negative ("none") results; provider failures are not cached; Redis unavailability falls through to the providers (never treated as a hit or a miss).
GET /conversation-filters/{id}web session, tenantJSON:API saved-filter detail; UI flattens data.attributes into { id, ...attributes }.

GET /conversations list serialization was tightened (typed review_status, legacy tags enum, customer_complaints with categories, assignee timestamps) so the generated UI types replace the Rails ConversationSummary alias without loosening the contract. Stored-view preview totals use the API2 facet query instead of the Rails meta.page.total.

Behavior inventory

Preserved: authenticated membership and tenant isolation, Rails policy scope, prefixed IDs, list/detail shapes consumed by the components, infinite pagination (cursor for /conversations, page numbers for /conversation_threads) via a Rails-shaped meta.page.next adapter, default sort, every URL/filter key in stringifyFilters (ticket IDs, text, dates, brands, channels, teams, employees, assignees, review status, risk/sentiment, scorecards, QA source/status, contest outcome, conversation field values; empty arrays stripped), group counts and list statistics, query keys and structuralSharing, invalidations, thread contact aggregation, applicable scorecards, QA and CX-risk ordering, tag option cap (100) and display-name resolution, attachment URLs (neutral ~/lib/attachments#resolveAttachmentUrl passes signed/blob/data URLs through and never rewrites onto VITE_API_BASE_URL), and loading/empty/error/unauthorized behavior through handleApi2Error.

Tests

  • rulebase-api2/src/routes/conversation-qa-reads.db.test.ts: real PGlite acceptance with anonymized production-shaped US org 25 and EU org 41 fixtures; cross-tenant negatives, prefixed IDs, mixed human/AI/contest/CX-risk records, conversation fields, realistic filter values, sort, pagination, attachments, permissions, search. No route query or policy scope is mocked; the search cases stub only the Zendesk HTTP client/credentials and assert the provider is never called when a hidden same-organization conversation already has the external_id.
  • rulebase-api2/src/lib/conversation-qa-reads/conversation-import-suggestion.db.test.ts: PGlite coverage of the organization-wide suppression, cross-tenant non-suppression, one-day tenant/external-ID cache key shape, positive and negative result caching, tenant isolation of cached results, and provider-failure misses not being cached.
  • rulebase-api2/src/routes/conversation-taxonomy-reads.db.test.ts: saved filter detail and tenant negatives.
  • rulebase-ui/app/features/conversations/api/conversation-read-clients.test.ts: parameter serialization, cursor and page pagination, stable query keys, response-shape adapters, API2 error and 401 handling.
  • rulebase-ui/app/lib/attachments.test.ts: attachment URL resolution.
  • rulebase-ui/app/features/conversations/conversation-read-source-gate.test.ts: fails if any of the 14 files contains rulebase-api-client, VITE_API_BASE_URL, api.rulebase.co, buildApiURL, rulebaseApi, or a Rails fallback marker.

Required checks: pnpm typecheck and the focused vitest files in rulebase-api2, pnpm openapi:export + pnpm rulebase-api2-openapi:generate, and pnpm lint, pnpm typecheck, focused vitest in rulebase-ui.

Source delta

rulebase-ui Rails-client importers: 253 source/test (242 runtime) → 239 (228 runtime), a reduction of exactly the 14 scoped files. rulebase-beam-web stays at 0/0. Type-only aliases in ~/lib/rulebase-api-client/types remain for components outside the scoped files and are retired with the shared types module.

Known gaps

  • Consumers outside the 14 files that still import the Rails client for the excluded mutations are unchanged by design.
  • meta.page.total for the cursor-based list is reported as 0; the only consumer (stored-view preview) now reads the facet count.
  • Assignee created_at/updated_at fall back to the epoch when the Rails row lacks timestamps; no UI consumer renders them.

Rollout, outcome query, and rollback

No feature flag: a default-off rollout is not practical without leaving a normal-path Rails fallback, which this package forbids. Deploy API2 in both regions before the UI bundle. Rollback is a code revert of the UI commit.

Merge gate: source zero in the scoped files (the source-gate test). Runtime gate: after the UI deploy, the following Rails controllers tagged first_party_client:rulebase-ui must stay absent for a 72-hour observation window (checked at 24 h and 72 h) on the Sentry API2 Migration dashboard (5423172): ConversationsController#index, #search, ConversationThreadsController#index, QAEvaluationsController#index, ConversationCxRiskSnapshotScorecardResultsController#index, ConversationCxRiskSnapshotsController#show, ConversationFiltersController#show, TagsController#index.

# Sentry Discover, per region transaction.op:http.server first_party_client:rulebase-ui transaction:[ConversationsController#index, ConversationsController#search, ConversationThreadsController#index, QAEvaluationsController#index, ConversationCxRiskSnapshotScorecardResultsController#index, ConversationCxRiskSnapshotsController#show, ConversationFiltersController#show, TagsController#index]

State: implemented in PR #11908 ; review requested (Ronald); not merged, not deployed, no observation started. Current gate: review and merge.

Last updated on