feat(platform): auto-provision conversation sync and fix empty states#490
Conversation
Auto-provision a conversation sync workflow when an integration with messaging capabilities is connected, matching the pattern used for website scan workflows. The sync is generic and works with any integration that has message listing operations. Also fix the conversations empty state so the "Activate conversations" CTA only shows when the org has zero conversations. When a status tab is empty but conversations exist elsewhere, the list shows an inline empty view instead. Additionally fix workflow schedule registration: provisioning functions now create wfSchedules entries so the cron scanner picks up scheduled workflows. Previously schedules were only created by a one-time migration, causing newly provisioned workflows to never run on schedule.
📝 WalkthroughWalkthroughThis PR introduces a conversation synchronization system for integrations. It adds a Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@services/platform/app/features/conversations/components/conversations-client.tsx`:
- Around line 101-103: The component currently renders
ActivateConversationsEmptyState when hasConversations is false even while the
query is loading; update the useHasConversations call to return its loading
state (isLoading) and change the render guard in conversations-client.tsx to
only show ActivateConversationsEmptyState when loading is false and
hasConversations is false (e.g., if (!isLoading && !hasConversations) ...),
ensuring you propagate isLoading from useHasConversations through any
intermediate hooks/props if needed.
In `@services/platform/convex/workflows/triggers/internal_mutations.ts`:
- Around line 68-88: provisionSchedule currently always inserts into wfSchedules
which can create duplicate cron entries on retries; modify provisionSchedule to
first query wfSchedules for an existing active schedule matching organizationId,
workflowRootId, cronExpression and timezone (and createdBy if needed) and if
found return its Id instead of inserting; otherwise insert as before; use the
wfSchedules index that covers (organizationId, workflowRootId, cronExpression,
timezone, isActive) to make the check efficient and return the existing row's id
when present.
…heck Prevent briefly flashing the "Activate conversations" empty state while the hasConversations query is still loading by checking isLoading before rendering the empty state.
Greptile SummaryThis PR introduces auto-provisioning of a "Conversation Sync" workflow when a messaging integration is connected, fixes the conversations empty state so the "Activate conversations" CTA only appears when an org has zero conversations, and fixes a pre-existing bug where provisioned scheduled workflows never got a Key changes and findings:
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| services/platform/convex/integrations/provision_conversation_sync_workflow.ts | Core provisioning function — creates workflow, publishes it, registers schedule, updates integration metadata. Does not check for an existing sync workflow before provisioning, creating a duplicate risk if triggered more than once per integration. |
| services/platform/convex/workflows/triggers/internal_mutations.ts | New provisionSchedule mutation inserts directly into wfSchedules without checking for an existing entry for the same workflowRootId, making duplicate schedule records possible if called more than once for the same workflow. |
| services/platform/app/routes/dashboard/$id/conversations/$status.tsx | Uses useHasConversations but discards isLoading — on re-navigation with a cached paginated result but uncached hasConversations, hasConversations defaults to false and the "Activate conversations" CTA will flash before the query resolves. |
| services/platform/convex/integrations/create_integration.ts | Correctly schedules conversation sync provisioning via ctx.scheduler.runAfter for integrations with message listing operations. Error is caught and logged rather than surfaced, which is appropriate for a non-critical background operation. |
| services/platform/convex/organizations/save_default_workflows.ts | Now calls provisionSchedule for each default workflow after creation, but there is no guard against initializeDefaultWorkflows being called more than once for the same org — it would insert duplicate schedule records. |
| services/platform/convex/migrations/backfill_workflow_schedules.ts | Correctly backfills wfSchedules entries for active scheduled workflows using a by_workflowRoot index to skip existing schedules. Iterates all wfStepDefs via for await (more memory-efficient than .collect()); N+1 lookups per step could time out on very large datasets, but this pattern mirrors existing migrations. |
| services/platform/app/components/ui/data-display/email-preview.tsx | Show-quoted-email button styling updated: border-radius removed from the pre block CSS, button gets padding and rounded corners. hover:text-foreground and mt-2 are removed — the button now has no hover color change. |
Sequence Diagram
sequenceDiagram
actor User
participant CI as createIntegration
participant Sched as ctx.scheduler
participant PCS as provisionConversationSync (action)
participant PCSW as provisionConversationSyncWorkflow
participant WFDef as wf_definitions.internal_mutations
participant Triggers as workflows.triggers.internal_mutations
participant IntMeta as integrations.internal_mutations
User->>CI: create integration (with list_messages op)
CI->>CI: hasMessageListOperation() → true
CI->>Sched: runAfter(0, provisionConversationSync, args)
CI-->>User: integrationId
Note over Sched,PCSW: Runs asynchronously after integration creation
Sched->>PCS: provisionConversationSync(args)
PCS->>PCSW: provisionConversationSyncWorkflow(ctx, args)
PCSW->>WFDef: provisionWorkflowWithSteps(...)
WFDef-->>PCSW: { workflowId }
PCSW->>WFDef: provisionPublishDraft(workflowId)
PCSW->>Triggers: provisionSchedule(workflowRootId, cronExpression)
Note over Triggers: ⚠️ No duplicate check
Triggers-->>PCSW: wfScheduleId
PCSW->>IntMeta: updateIntegration(metadata.conversationSyncWorkflowId)
PCSW->>Sched: runAfter(0, startWorkflow) — initial sync
Last reviewed commit: 2629499
Summary
list_messages) is connected, a conversation sync workflow is automatically created and scheduled — matching the website scan auto-provisioning pattern. Works generically with any messaging integration.save_default_workflows,provision_website_scan_workflow,provision_conversation_sync_workflow) now createwfSchedulesentries via a newprovisionScheduleinternal mutation, so the cron scanner picks up scheduled workflows. Previously, schedules were only created by thetrigger_steps_to_startmigration, causing any newly provisioned workflow to run once and never again.backfill_workflow_schedulesmigration to create missingwfSchedulesentries for existing active workflows with scheduled start steps.Test plan
wfSchedulestable has an entry)wfSchedulesentrybackfill_workflow_schedulesmigration and verify it creates missing schedule entriesprovision_conversation_sync_workflow.test.tsSummary by CodeRabbit
Release Notes
New Features
UI Updates