@graph8/sdk 0.308.1 → 0.357.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +81 -0
- package/blocks/README.md +32 -0
- package/blocks/_foundation/README.md +78 -0
- package/blocks/_foundation/block.json +79 -0
- package/blocks/_foundation/components/api-error.tsx +62 -0
- package/blocks/_foundation/components/confirm-spend-dialog.tsx +142 -0
- package/blocks/_foundation/components/empty-state.tsx +24 -0
- package/blocks/_foundation/hooks/use-g8-mutation.ts +88 -0
- package/blocks/_foundation/hooks/use-g8-query.ts +87 -0
- package/blocks/_foundation/hooks/use-job-poll.ts +130 -0
- package/blocks/_foundation/hooks/use-paginated.ts +105 -0
- package/blocks/_foundation/lib/g8-client.ts +128 -0
- package/blocks/_foundation/recipes/verify-proxy-key.ts +18 -0
- package/blocks/_foundation/server/proxy.ts +233 -0
- package/blocks/_foundation/server/route.ts +30 -0
- package/blocks/agents/README.md +133 -0
- package/blocks/agents/block.json +223 -0
- package/blocks/agents/components/agent-builder.tsx +199 -0
- package/blocks/agents/components/agent-knowledge-panel.tsx +144 -0
- package/blocks/agents/components/agent-runs.tsx +175 -0
- package/blocks/agents/components/agent-skills-panel.tsx +126 -0
- package/blocks/agents/components/agent-test-console.tsx +175 -0
- package/blocks/agents/lib/agent-types.ts +114 -0
- package/blocks/agents/recipes/create-agent-and-attach-skill.ts +50 -0
- package/blocks/agents/recipes/run-and-follow-agent-run.ts +46 -0
- package/blocks/agents/recipes/test-agent-before-activating.ts +36 -0
- package/blocks/analytics/README.md +73 -0
- package/blocks/analytics/block.json +178 -0
- package/blocks/analytics/components/funnel-chart.tsx +79 -0
- package/blocks/analytics/components/kpi-tiles.tsx +74 -0
- package/blocks/analytics/components/leaderboard.tsx +86 -0
- package/blocks/analytics/components/report-builder.tsx +200 -0
- package/blocks/analytics/hooks/use-report.ts +26 -0
- package/blocks/analytics/lib/analytics-types.ts +108 -0
- package/blocks/analytics/lib/use-debounced.ts +16 -0
- package/blocks/analytics/recipes/01-weekly-outbound-report.ts +32 -0
- package/blocks/analytics/recipes/02-revenue-snapshot.ts +22 -0
- package/blocks/analytics/recipes/03-saved-report.ts +30 -0
- package/blocks/appointments/README.md +80 -0
- package/blocks/appointments/block.json +218 -0
- package/blocks/appointments/components/availability-editor.tsx +192 -0
- package/blocks/appointments/components/booking-page.tsx +154 -0
- package/blocks/appointments/components/event-type-editor.tsx +214 -0
- package/blocks/appointments/components/meeting-detail.tsx +147 -0
- package/blocks/appointments/components/meeting-list.tsx +148 -0
- package/blocks/appointments/hooks/use-available-slots.ts +29 -0
- package/blocks/appointments/hooks/use-schedules.ts +18 -0
- package/blocks/appointments/lib/appointments-types.ts +159 -0
- package/blocks/appointments/lib/idempotency.ts +30 -0
- package/blocks/appointments/recipes/01-create-event-type-and-hours.ts +27 -0
- package/blocks/appointments/recipes/02-find-slots-and-book.ts +34 -0
- package/blocks/appointments/recipes/03-bookings-digest.ts +22 -0
- package/blocks/appointments/recipes/04-calendar-invite.ts +21 -0
- package/blocks/block.schema.json +130 -0
- package/blocks/block.types.ts +109 -0
- package/blocks/campaigns/README.md +118 -0
- package/blocks/campaigns/block.json +361 -0
- package/blocks/campaigns/components/campaign-assets.tsx +180 -0
- package/blocks/campaigns/components/campaign-audience-step.tsx +91 -0
- package/blocks/campaigns/components/campaign-launch-step.tsx +147 -0
- package/blocks/campaigns/components/campaign-list.tsx +120 -0
- package/blocks/campaigns/components/campaign-metrics.tsx +93 -0
- package/blocks/campaigns/components/campaign-select.tsx +52 -0
- package/blocks/campaigns/components/campaign-sequence-step.tsx +158 -0
- package/blocks/campaigns/components/campaign-wizard.tsx +238 -0
- package/blocks/campaigns/components/form-field.tsx +29 -0
- package/blocks/campaigns/components/icp-editor.tsx +230 -0
- package/blocks/campaigns/components/persona-editor.tsx +210 -0
- package/blocks/campaigns/hooks/use-company-profile.ts +34 -0
- package/blocks/campaigns/lib/campaigns-utils.ts +155 -0
- package/blocks/campaigns/recipes/attach-audience-and-launch.ts +33 -0
- package/blocks/campaigns/recipes/campaign-report.ts +22 -0
- package/blocks/campaigns/recipes/generate-ideas-and-create-campaign.ts +51 -0
- package/blocks/campaigns/recipes/set-up-icp-and-personas.ts +37 -0
- package/blocks/coach/README.md +93 -0
- package/blocks/coach/block.json +125 -0
- package/blocks/coach/components/call-scorecard.tsx +126 -0
- package/blocks/coach/components/coaching-feed.tsx +140 -0
- package/blocks/coach/lib/coach-types.ts +116 -0
- package/blocks/coach/recipes/read-a-scorecard.ts +27 -0
- package/blocks/coach/recipes/weekly-coaching-digest.ts +53 -0
- package/blocks/copilot/README.md +99 -0
- package/blocks/copilot/block.json +151 -0
- package/blocks/copilot/components/copilot-memory.tsx +87 -0
- package/blocks/copilot/components/copilot-panel.tsx +189 -0
- package/blocks/copilot/components/copilot-team-chat.tsx +111 -0
- package/blocks/copilot/lib/copilot-types.ts +111 -0
- package/blocks/copilot/recipes/ask-the-copilot.ts +35 -0
- package/blocks/copilot/recipes/post-in-a-work-channel.ts +33 -0
- package/blocks/copilot/recipes/remember-and-recall.ts +19 -0
- package/blocks/crm/README.md +122 -0
- package/blocks/crm/block.json +378 -0
- package/blocks/crm/components/activity-timeline.tsx +99 -0
- package/blocks/crm/components/company-detail.tsx +118 -0
- package/blocks/crm/components/company-table.tsx +173 -0
- package/blocks/crm/components/contact-detail.tsx +135 -0
- package/blocks/crm/components/contact-table.tsx +217 -0
- package/blocks/crm/components/dedupe-review.tsx +164 -0
- package/blocks/crm/components/import-wizard.tsx +193 -0
- package/blocks/crm/components/notes-panel.tsx +112 -0
- package/blocks/crm/components/record-form.tsx +191 -0
- package/blocks/crm/components/task-list.tsx +107 -0
- package/blocks/crm/hooks/use-custom-fields.ts +18 -0
- package/blocks/crm/hooks/use-debounced.ts +13 -0
- package/blocks/crm/hooks/use-page-query.ts +52 -0
- package/blocks/crm/lib/crm-types.ts +136 -0
- package/blocks/crm/recipes/custom-fields-and-values.ts +28 -0
- package/blocks/crm/recipes/dedupe-and-merge.ts +30 -0
- package/blocks/crm/recipes/notes-tasks-and-activity.ts +23 -0
- package/blocks/crm/recipes/upsert-contact-with-company.ts +33 -0
- package/blocks/deals/README.md +78 -0
- package/blocks/deals/block.json +198 -0
- package/blocks/deals/components/deal-detail.tsx +134 -0
- package/blocks/deals/components/forecast-view.tsx +112 -0
- package/blocks/deals/components/pipeline-board.tsx +142 -0
- package/blocks/deals/components/stage-editor.tsx +96 -0
- package/blocks/deals/hooks/use-pipeline-deals.ts +67 -0
- package/blocks/deals/hooks/use-pipelines.ts +19 -0
- package/blocks/deals/lib/deals-types.ts +128 -0
- package/blocks/deals/recipes/01-create-deal-and-advance.ts +32 -0
- package/blocks/deals/recipes/02-pipeline-report.ts +35 -0
- package/blocks/deals/recipes/03-stale-deals-sweep.ts +20 -0
- package/blocks/dialer/README.md +114 -0
- package/blocks/dialer/block.json +260 -0
- package/blocks/dialer/components/call-log.tsx +78 -0
- package/blocks/dialer/components/call-queue.tsx +188 -0
- package/blocks/dialer/components/dialer-workspace.tsx +47 -0
- package/blocks/dialer/components/disposition-panel.tsx +75 -0
- package/blocks/dialer/components/live-calls-table.tsx +107 -0
- package/blocks/dialer/components/session-start-form.tsx +111 -0
- package/blocks/dialer/components/softphone.tsx +122 -0
- package/blocks/dialer/components/transcript-viewer.tsx +82 -0
- package/blocks/dialer/hooks/use-dialer-numbers.ts +19 -0
- package/blocks/dialer/hooks/use-dialer-session.ts +68 -0
- package/blocks/dialer/hooks/use-dispositions.ts +29 -0
- package/blocks/dialer/lib/dialer-types.ts +158 -0
- package/blocks/dialer/lib/softphone-transport.ts +67 -0
- package/blocks/dialer/lib/twilio-transport.ts +74 -0
- package/blocks/dialer/recipes/01-create-session-and-dial.ts +39 -0
- package/blocks/dialer/recipes/02-disposition-calls.ts +26 -0
- package/blocks/dialer/recipes/03-review-a-call.ts +23 -0
- package/blocks/enrichment/README.md +105 -0
- package/blocks/enrichment/block.json +204 -0
- package/blocks/enrichment/components/bulk-enrich-panel.tsx +205 -0
- package/blocks/enrichment/components/email-verify-badge.tsx +82 -0
- package/blocks/enrichment/components/enrich-button.tsx +136 -0
- package/blocks/enrichment/components/waterfall-config.tsx +127 -0
- package/blocks/enrichment/hooks/use-providers.ts +18 -0
- package/blocks/enrichment/lib/enrichment-types.ts +104 -0
- package/blocks/enrichment/recipes/bulk-enrich-and-poll.ts +41 -0
- package/blocks/enrichment/recipes/lookup-and-verify.ts +25 -0
- package/blocks/enrichment/recipes/waterfall-pipeline.ts +26 -0
- package/blocks/forms/README.md +112 -0
- package/blocks/forms/block.json +200 -0
- package/blocks/forms/components/form-builder.tsx +150 -0
- package/blocks/forms/components/form-embed.tsx +103 -0
- package/blocks/forms/components/form-mapping-editor.tsx +125 -0
- package/blocks/forms/components/site-audit-panel.tsx +73 -0
- package/blocks/forms/components/submissions-table.tsx +158 -0
- package/blocks/forms/lib/form-types.ts +127 -0
- package/blocks/forms/recipes/install-snippet-and-form.ts +21 -0
- package/blocks/forms/recipes/retry-failed-deliveries.ts +26 -0
- package/blocks/forms/recipes/route-form-to-list.ts +44 -0
- package/blocks/inbox/README.md +72 -0
- package/blocks/inbox/block.json +194 -0
- package/blocks/inbox/components/inbox-list.tsx +156 -0
- package/blocks/inbox/components/inbox-screen.tsx +49 -0
- package/blocks/inbox/components/reply-composer.tsx +183 -0
- package/blocks/inbox/components/tag-and-assign.tsx +189 -0
- package/blocks/inbox/components/thread-badge.tsx +17 -0
- package/blocks/inbox/components/thread-view.tsx +108 -0
- package/blocks/inbox/hooks/use-inbox-thread.ts +24 -0
- package/blocks/inbox/hooks/use-inbox-threads.ts +40 -0
- package/blocks/inbox/lib/idempotency.ts +30 -0
- package/blocks/inbox/lib/inbox-types.ts +100 -0
- package/blocks/inbox/recipes/01-triage-and-reply.ts +43 -0
- package/blocks/inbox/recipes/02-tag-and-assign.ts +31 -0
- package/blocks/inbox/recipes/03-hot-contacts-digest.ts +16 -0
- package/blocks/integrations/README.md +75 -0
- package/blocks/integrations/block.json +172 -0
- package/blocks/integrations/components/integration-list.tsx +165 -0
- package/blocks/integrations/components/sync-status.tsx +223 -0
- package/blocks/integrations/components/webhook-manager.tsx +300 -0
- package/blocks/integrations/lib/integrations-utils.ts +62 -0
- package/blocks/integrations/recipes/crm-sync-health.ts +19 -0
- package/blocks/integrations/recipes/push-contacts-to-crm.ts +20 -0
- package/blocks/integrations/recipes/register-webhook.ts +18 -0
- package/blocks/integrations/recipes/verify-webhook-route.ts +33 -0
- package/blocks/intent/README.md +57 -0
- package/blocks/intent/block.json +138 -0
- package/blocks/intent/components/account-intent-score.tsx +91 -0
- package/blocks/intent/components/intent-feed.tsx +215 -0
- package/blocks/intent/components/intent-keyword-manager.tsx +331 -0
- package/blocks/intent/hooks/use-account-intent.ts +46 -0
- package/blocks/intent/lib/intent-utils.ts +95 -0
- package/blocks/intent/recipes/rank-intent-accounts.ts +36 -0
- package/blocks/intent/recipes/track-competitor-domain.ts +23 -0
- package/blocks/intent/recipes/who-visited-url.ts +18 -0
- package/blocks/lifecycle/README.md +79 -0
- package/blocks/lifecycle/block.json +198 -0
- package/blocks/lifecycle/components/churn-list.tsx +92 -0
- package/blocks/lifecycle/components/customer-table.tsx +119 -0
- package/blocks/lifecycle/components/lead-queue.tsx +127 -0
- package/blocks/lifecycle/components/lifecycle-board.tsx +102 -0
- package/blocks/lifecycle/components/stage-move-dialog.tsx +75 -0
- package/blocks/lifecycle/hooks/use-debounced.ts +13 -0
- package/blocks/lifecycle/hooks/use-lifecycle-stages.ts +27 -0
- package/blocks/lifecycle/lib/lifecycle.ts +99 -0
- package/blocks/lifecycle/recipes/customer-brief.ts +23 -0
- package/blocks/lifecycle/recipes/move-company-stage.ts +17 -0
- package/blocks/lifecycle/recipes/read-lifecycle-board.ts +21 -0
- package/blocks/lifecycle/recipes/unlock-stage-contacts.ts +25 -0
- package/blocks/lists/README.md +96 -0
- package/blocks/lists/block.json +166 -0
- package/blocks/lists/components/add-to-list-button.tsx +111 -0
- package/blocks/lists/components/list-picker.tsx +94 -0
- package/blocks/lists/components/list-table.tsx +160 -0
- package/blocks/lists/components/suppression-manager.tsx +153 -0
- package/blocks/lists/recipes/create-list-and-add-contacts.ts +35 -0
- package/blocks/lists/recipes/refresh-and-check-lists.ts +21 -0
- package/blocks/lists/recipes/suppress-and-reinstate.ts +30 -0
- package/blocks/mailboxes/README.md +71 -0
- package/blocks/mailboxes/block.json +187 -0
- package/blocks/mailboxes/components/connect-mailbox.tsx +284 -0
- package/blocks/mailboxes/components/domain-purchase.tsx +271 -0
- package/blocks/mailboxes/components/mailbox-list.tsx +185 -0
- package/blocks/mailboxes/components/warmup-health.tsx +232 -0
- package/blocks/mailboxes/lib/mailboxes-utils.ts +69 -0
- package/blocks/mailboxes/recipes/buy-domain-and-mailboxes.ts +64 -0
- package/blocks/mailboxes/recipes/connect-imap-mailbox.ts +38 -0
- package/blocks/mailboxes/recipes/is-it-safe-to-send.ts +17 -0
- package/blocks/mailboxes/recipes/warmup-report.ts +31 -0
- package/blocks/newsletter/README.md +122 -0
- package/blocks/newsletter/block.json +224 -0
- package/blocks/newsletter/components/issue-form.tsx +124 -0
- package/blocks/newsletter/components/issue-scheduler.tsx +152 -0
- package/blocks/newsletter/components/newsletter-editor.tsx +131 -0
- package/blocks/newsletter/components/newsletter-stats.tsx +93 -0
- package/blocks/newsletter/components/subscriber-table.tsx +173 -0
- package/blocks/newsletter/lib/newsletter-types.ts +97 -0
- package/blocks/newsletter/recipes/create-newsletter-and-draft-issue.ts +27 -0
- package/blocks/newsletter/recipes/import-audience-and-send.ts +43 -0
- package/blocks/newsletter/recipes/issue-performance-report.ts +26 -0
- package/blocks/nurture/README.md +110 -0
- package/blocks/nurture/block.json +191 -0
- package/blocks/nurture/components/nurture-builder.tsx +114 -0
- package/blocks/nurture/components/nurture-enroll.tsx +107 -0
- package/blocks/nurture/components/nurture-form.tsx +120 -0
- package/blocks/nurture/components/nurture-stats.tsx +81 -0
- package/blocks/nurture/lib/nurture-types.ts +109 -0
- package/blocks/nurture/recipes/create-a-nurture.ts +34 -0
- package/blocks/nurture/recipes/enroll-contacts-and-launch.ts +24 -0
- package/blocks/nurture/recipes/nurture-health-check.ts +28 -0
- package/blocks/objects/README.md +120 -0
- package/blocks/objects/block.json +243 -0
- package/blocks/objects/components/attribute-field-input.tsx +82 -0
- package/blocks/objects/components/attribute-form-dialog.tsx +159 -0
- package/blocks/objects/components/object-not-found.tsx +34 -0
- package/blocks/objects/components/object-record-detail.tsx +143 -0
- package/blocks/objects/components/object-schema-editor.tsx +197 -0
- package/blocks/objects/components/object-table.tsx +130 -0
- package/blocks/objects/components/record-form-dialog.tsx +112 -0
- package/blocks/objects/components/record-roles-panel.tsx +106 -0
- package/blocks/objects/hooks/use-object-schema.ts +36 -0
- package/blocks/objects/lib/object-types.ts +214 -0
- package/blocks/objects/recipes/define-object-and-add-records.ts +24 -0
- package/blocks/objects/recipes/update-audit-and-archive-records.ts +39 -0
- package/blocks/objects/recipes/upsert-records-from-your-system.ts +30 -0
- package/blocks/pages/README.md +104 -0
- package/blocks/pages/block.json +223 -0
- package/blocks/pages/components/brand-kit-editor.tsx +152 -0
- package/blocks/pages/components/content-library.tsx +163 -0
- package/blocks/pages/components/page-editor.tsx +253 -0
- package/blocks/pages/components/page-list.tsx +159 -0
- package/blocks/pages/components/seo-report.tsx +159 -0
- package/blocks/pages/lib/studio.ts +77 -0
- package/blocks/pages/recipes/audit-site-seo.ts +28 -0
- package/blocks/pages/recipes/create-and-publish-page.ts +26 -0
- package/blocks/pages/recipes/edit-page-section.ts +25 -0
- package/blocks/pages/recipes/refresh-brand-kit.ts +22 -0
- package/blocks/quotes/README.md +79 -0
- package/blocks/quotes/block.json +183 -0
- package/blocks/quotes/components/product-picker.tsx +80 -0
- package/blocks/quotes/components/quote-builder.tsx +195 -0
- package/blocks/quotes/components/quote-list.tsx +79 -0
- package/blocks/quotes/components/quote-viewer.tsx +135 -0
- package/blocks/quotes/components/signature-status.tsx +77 -0
- package/blocks/quotes/hooks/use-debounced.ts +13 -0
- package/blocks/quotes/lib/quotes.ts +142 -0
- package/blocks/quotes/recipes/create-and-send-quote.ts +44 -0
- package/blocks/quotes/recipes/revise-sent-quote.ts +18 -0
- package/blocks/quotes/recipes/track-quote-signature.ts +22 -0
- package/blocks/radar/README.md +106 -0
- package/blocks/radar/block.json +189 -0
- package/blocks/radar/components/competitor-battle-card.tsx +53 -0
- package/blocks/radar/components/competitor-tracker.tsx +158 -0
- package/blocks/radar/components/radar-overview.tsx +67 -0
- package/blocks/radar/components/radar-report.tsx +86 -0
- package/blocks/radar/hooks/use-competitors.ts +23 -0
- package/blocks/radar/lib/radar-types.ts +124 -0
- package/blocks/radar/recipes/battle-card.ts +28 -0
- package/blocks/radar/recipes/deep-dive-report.ts +32 -0
- package/blocks/radar/recipes/marketing-snapshot.ts +18 -0
- package/blocks/radar/recipes/track-and-scan.ts +39 -0
- package/blocks/search/README.md +129 -0
- package/blocks/search/block.json +185 -0
- package/blocks/search/components/filter-builder.tsx +149 -0
- package/blocks/search/components/prospect-results.tsx +175 -0
- package/blocks/search/components/save-to-list-dialog.tsx +135 -0
- package/blocks/search/components/saved-searches.tsx +95 -0
- package/blocks/search/hooks/use-filter-options.ts +41 -0
- package/blocks/search/lib/search-types.ts +149 -0
- package/blocks/search/recipes/explore-filter-values.ts +27 -0
- package/blocks/search/recipes/saved-searches.ts +34 -0
- package/blocks/search/recipes/search-and-save-to-list.ts +37 -0
- package/blocks/sequencer/README.md +87 -0
- package/blocks/sequencer/block.json +214 -0
- package/blocks/sequencer/components/enroll-dialog.tsx +93 -0
- package/blocks/sequencer/components/sequence-builder.tsx +306 -0
- package/blocks/sequencer/components/sequence-list.tsx +200 -0
- package/blocks/sequencer/components/sequence-stats.tsx +256 -0
- package/blocks/sequencer/components/step-editor.tsx +213 -0
- package/blocks/sequencer/lib/sequencer-utils.ts +168 -0
- package/blocks/sequencer/recipes/create-multichannel-sequence.ts +51 -0
- package/blocks/sequencer/recipes/launch-and-enroll.ts +44 -0
- package/blocks/sequencer/recipes/pause-when-unsafe.ts +30 -0
- package/blocks/sequencer/recipes/sequence-report.ts +22 -0
- package/blocks/social-ads/README.md +128 -0
- package/blocks/social-ads/block.json +256 -0
- package/blocks/social-ads/components/ad-campaign-list.tsx +136 -0
- package/blocks/social-ads/components/audience-sync.tsx +150 -0
- package/blocks/social-ads/components/creative-gallery.tsx +121 -0
- package/blocks/social-ads/components/post-composer.tsx +122 -0
- package/blocks/social-ads/components/social-post-list.tsx +116 -0
- package/blocks/social-ads/hooks/use-social-accounts.ts +16 -0
- package/blocks/social-ads/lib/social-ads-types.ts +167 -0
- package/blocks/social-ads/recipes/creative-to-gallery.ts +41 -0
- package/blocks/social-ads/recipes/schedule-post.ts +51 -0
- package/blocks/social-ads/recipes/sync-audience-and-launch.ts +41 -0
- package/blocks/visitors/README.md +56 -0
- package/blocks/visitors/block.json +139 -0
- package/blocks/visitors/components/snippet-install.tsx +169 -0
- package/blocks/visitors/components/traffic-chart.tsx +156 -0
- package/blocks/visitors/components/visitor-company-card.tsx +92 -0
- package/blocks/visitors/components/visitor-feed.tsx +157 -0
- package/blocks/visitors/lib/visitors-utils.ts +78 -0
- package/blocks/visitors/recipes/check-snippet-installed.ts +24 -0
- package/blocks/visitors/recipes/recent-company-visits.ts +27 -0
- package/blocks/visitors/recipes/traffic-summary.ts +24 -0
- package/blocks/webchat/README.md +87 -0
- package/blocks/webchat/block.json +134 -0
- package/blocks/webchat/components/chat-widget-config.tsx +160 -0
- package/blocks/webchat/components/conversation-list.tsx +74 -0
- package/blocks/webchat/lib/webchat-types.ts +83 -0
- package/blocks/webchat/recipes/create-bot-and-style-widget.ts +24 -0
- package/blocks/webchat/recipes/list-chat-visitors.ts +24 -0
- package/blocks/workbench/README.md +135 -0
- package/blocks/workbench/block.json +259 -0
- package/blocks/workbench/components/column-recipe-editor.tsx +183 -0
- package/blocks/workbench/components/run-progress.tsx +132 -0
- package/blocks/workbench/components/workbench-add-rows.tsx +100 -0
- package/blocks/workbench/components/workbench-grid.tsx +159 -0
- package/blocks/workbench/components/workbench-row-actions.tsx +102 -0
- package/blocks/workbench/components/workbench-table-list.tsx +105 -0
- package/blocks/workbench/hooks/use-workbench-table.ts +30 -0
- package/blocks/workbench/lib/waterfall-presets.ts +119 -0
- package/blocks/workbench/lib/workbench-types.ts +191 -0
- package/blocks/workbench/recipes/build-enriched-table.ts +65 -0
- package/blocks/workbench/recipes/import-from-search.ts +42 -0
- package/blocks/workbench/recipes/scrape-into-table.ts +36 -0
- package/blocks/workbench/recipes/waterfall-column.ts +55 -0
- package/blocks/workflows/README.md +100 -0
- package/blocks/workflows/block.json +188 -0
- package/blocks/workflows/components/run-history.tsx +136 -0
- package/blocks/workflows/components/trigger-config.tsx +134 -0
- package/blocks/workflows/components/workflow-editor.tsx +142 -0
- package/blocks/workflows/components/workflow-list.tsx +153 -0
- package/blocks/workflows/hooks/use-workflow.ts +51 -0
- package/blocks/workflows/lib/graph.ts +187 -0
- package/blocks/workflows/recipes/create-workflow.ts +35 -0
- package/blocks/workflows/recipes/find-failed-runs.ts +15 -0
- package/blocks/workflows/recipes/run-workflow-and-wait.ts +32 -0
- package/blocks/workflows/recipes/set-trigger.ts +22 -0
- package/dist/cli.js +5330 -29
- package/dist/index.browser.js +7740 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.browser.mjs +7717 -0
- package/dist/index.browser.mjs.map +1 -0
- package/dist/index.d.mts +1440 -21
- package/dist/index.d.ts +1440 -21
- package/dist/index.js +42 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +42 -3
- package/dist/index.mjs.map +1 -1
- package/dist/react.browser.js +7598 -0
- package/dist/react.browser.js.map +1 -0
- package/dist/react.browser.mjs +7568 -0
- package/dist/react.browser.mjs.map +1 -0
- package/dist/react.d.mts +1340 -18
- package/dist/react.d.ts +1340 -18
- package/dist/react.js +41 -2
- package/dist/react.js.map +1 -1
- package/dist/react.mjs +41 -2
- package/dist/react.mjs.map +1 -1
- package/dist/types/agentSetup.d.ts +6 -6
- package/dist/types/blocks/add.d.ts +53 -0
- package/dist/types/blocks/create.d.ts +46 -0
- package/dist/types/blocks/doctor.d.ts +26 -0
- package/dist/types/blocks/registry.d.ts +134 -0
- package/dist/types/cli.d.ts +16 -0
- package/dist/types/core.d.ts +1 -1
- package/dist/types/crypto-browser.d.ts +2 -0
- package/dist/types/crypto-node.d.ts +6 -0
- package/dist/types/react.d.ts +1 -1
- package/dist/types/version.d.ts +2 -2
- package/dist/types/webhooks.d.ts +2 -2
- package/dist/ui/index.d.mts +54 -0
- package/dist/ui/index.d.ts +54 -0
- package/dist/ui/index.js +105 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +44 -0
- package/dist/ui/index.mjs.map +1 -0
- package/dist/ui/lib/compose-refs.d.mts +19 -0
- package/dist/ui/lib/compose-refs.d.ts +19 -0
- package/dist/ui/lib/compose-refs.js +76 -0
- package/dist/ui/lib/compose-refs.js.map +1 -0
- package/dist/ui/lib/compose-refs.mjs +41 -0
- package/dist/ui/lib/compose-refs.mjs.map +1 -0
- package/dist/ui/lib/use-mobile.d.mts +4 -0
- package/dist/ui/lib/use-mobile.d.ts +4 -0
- package/dist/ui/lib/use-mobile.js +57 -0
- package/dist/ui/lib/use-mobile.js.map +1 -0
- package/dist/ui/lib/use-mobile.mjs +22 -0
- package/dist/ui/lib/use-mobile.mjs.map +1 -0
- package/dist/ui/lib/utils.d.mts +10 -0
- package/dist/ui/lib/utils.d.ts +10 -0
- package/dist/ui/lib/utils.js +33 -0
- package/dist/ui/lib/utils.js.map +1 -0
- package/dist/ui/lib/utils.mjs +9 -0
- package/dist/ui/lib/utils.mjs.map +1 -0
- package/dist/ui/primitives/alert-dialog.d.mts +22 -0
- package/dist/ui/primitives/alert-dialog.d.ts +22 -0
- package/dist/ui/primitives/alert-dialog.js +163 -0
- package/dist/ui/primitives/alert-dialog.js.map +1 -0
- package/dist/ui/primitives/alert-dialog.mjs +119 -0
- package/dist/ui/primitives/alert-dialog.mjs.map +1 -0
- package/dist/ui/primitives/avatar.d.mts +8 -0
- package/dist/ui/primitives/avatar.d.ts +8 -0
- package/dist/ui/primitives/avatar.js +91 -0
- package/dist/ui/primitives/avatar.js.map +1 -0
- package/dist/ui/primitives/avatar.mjs +55 -0
- package/dist/ui/primitives/avatar.mjs.map +1 -0
- package/dist/ui/primitives/breadcrumb.d.mts +13 -0
- package/dist/ui/primitives/breadcrumb.d.ts +13 -0
- package/dist/ui/primitives/breadcrumb.js +127 -0
- package/dist/ui/primitives/breadcrumb.js.map +1 -0
- package/dist/ui/primitives/breadcrumb.mjs +97 -0
- package/dist/ui/primitives/breadcrumb.mjs.map +1 -0
- package/dist/ui/primitives/button.d.mts +15 -0
- package/dist/ui/primitives/button.d.ts +15 -0
- package/dist/ui/primitives/button.js +91 -0
- package/dist/ui/primitives/button.js.map +1 -0
- package/dist/ui/primitives/button.mjs +56 -0
- package/dist/ui/primitives/button.mjs.map +1 -0
- package/dist/ui/primitives/card.d.mts +10 -0
- package/dist/ui/primitives/card.d.ts +10 -0
- package/dist/ui/primitives/card.js +101 -0
- package/dist/ui/primitives/card.js.map +1 -0
- package/dist/ui/primitives/card.mjs +62 -0
- package/dist/ui/primitives/card.mjs.map +1 -0
- package/dist/ui/primitives/carousel.d.mts +45 -0
- package/dist/ui/primitives/carousel.d.ts +45 -0
- package/dist/ui/primitives/carousel.js +300 -0
- package/dist/ui/primitives/carousel.js.map +1 -0
- package/dist/ui/primitives/carousel.mjs +261 -0
- package/dist/ui/primitives/carousel.mjs.map +1 -0
- package/dist/ui/primitives/chart.d.mts +68 -0
- package/dist/ui/primitives/chart.d.ts +68 -0
- package/dist/ui/primitives/chart.js +280 -0
- package/dist/ui/primitives/chart.js.map +1 -0
- package/dist/ui/primitives/chart.mjs +241 -0
- package/dist/ui/primitives/chart.mjs.map +1 -0
- package/dist/ui/primitives/collapsible.d.mts +8 -0
- package/dist/ui/primitives/collapsible.d.ts +8 -0
- package/dist/ui/primitives/collapsible.js +72 -0
- package/dist/ui/primitives/collapsible.js.map +1 -0
- package/dist/ui/primitives/collapsible.mjs +36 -0
- package/dist/ui/primitives/collapsible.mjs.map +1 -0
- package/dist/ui/primitives/context-menu.d.mts +29 -0
- package/dist/ui/primitives/context-menu.d.ts +29 -0
- package/dist/ui/primitives/context-menu.js +203 -0
- package/dist/ui/primitives/context-menu.js.map +1 -0
- package/dist/ui/primitives/context-menu.mjs +155 -0
- package/dist/ui/primitives/context-menu.mjs.map +1 -0
- package/dist/ui/primitives/dialog.d.mts +26 -0
- package/dist/ui/primitives/dialog.d.ts +26 -0
- package/dist/ui/primitives/dialog.js +192 -0
- package/dist/ui/primitives/dialog.js.map +1 -0
- package/dist/ui/primitives/dialog.mjs +148 -0
- package/dist/ui/primitives/dialog.mjs.map +1 -0
- package/dist/ui/primitives/drawer.d.mts +26 -0
- package/dist/ui/primitives/drawer.d.ts +26 -0
- package/dist/ui/primitives/drawer.js +146 -0
- package/dist/ui/primitives/drawer.js.map +1 -0
- package/dist/ui/primitives/drawer.mjs +103 -0
- package/dist/ui/primitives/drawer.mjs.map +1 -0
- package/dist/ui/primitives/input.d.mts +5 -0
- package/dist/ui/primitives/input.d.ts +5 -0
- package/dist/ui/primitives/input.js +46 -0
- package/dist/ui/primitives/input.js.map +1 -0
- package/dist/ui/primitives/input.mjs +22 -0
- package/dist/ui/primitives/input.mjs.map +1 -0
- package/dist/ui/primitives/item.d.mts +27 -0
- package/dist/ui/primitives/item.d.ts +27 -0
- package/dist/ui/primitives/item.js +221 -0
- package/dist/ui/primitives/item.js.map +1 -0
- package/dist/ui/primitives/item.mjs +188 -0
- package/dist/ui/primitives/item.mjs.map +1 -0
- package/dist/ui/primitives/kbd.d.mts +15 -0
- package/dist/ui/primitives/kbd.d.ts +15 -0
- package/dist/ui/primitives/kbd.js +45 -0
- package/dist/ui/primitives/kbd.js.map +1 -0
- package/dist/ui/primitives/kbd.mjs +21 -0
- package/dist/ui/primitives/kbd.mjs.map +1 -0
- package/dist/ui/primitives/label.d.mts +6 -0
- package/dist/ui/primitives/label.d.ts +6 -0
- package/dist/ui/primitives/label.js +55 -0
- package/dist/ui/primitives/label.js.map +1 -0
- package/dist/ui/primitives/label.mjs +21 -0
- package/dist/ui/primitives/label.mjs.map +1 -0
- package/dist/ui/primitives/resizable.d.mts +10 -0
- package/dist/ui/primitives/resizable.d.ts +10 -0
- package/dist/ui/primitives/resizable.js +68 -0
- package/dist/ui/primitives/resizable.js.map +1 -0
- package/dist/ui/primitives/resizable.mjs +42 -0
- package/dist/ui/primitives/resizable.mjs.map +1 -0
- package/dist/ui/primitives/save-status.d.mts +31 -0
- package/dist/ui/primitives/save-status.d.ts +31 -0
- package/dist/ui/primitives/save-status.js +150 -0
- package/dist/ui/primitives/save-status.js.map +1 -0
- package/dist/ui/primitives/save-status.mjs +126 -0
- package/dist/ui/primitives/save-status.mjs.map +1 -0
- package/dist/ui/primitives/separator.d.mts +6 -0
- package/dist/ui/primitives/separator.d.ts +6 -0
- package/dist/ui/primitives/separator.js +62 -0
- package/dist/ui/primitives/separator.js.map +1 -0
- package/dist/ui/primitives/separator.mjs +28 -0
- package/dist/ui/primitives/separator.mjs.map +1 -0
- package/dist/ui/primitives/sheet.d.mts +16 -0
- package/dist/ui/primitives/sheet.d.ts +16 -0
- package/dist/ui/primitives/sheet.js +170 -0
- package/dist/ui/primitives/sheet.js.map +1 -0
- package/dist/ui/primitives/sheet.mjs +129 -0
- package/dist/ui/primitives/sheet.mjs.map +1 -0
- package/dist/ui/primitives/sidebar.d.mts +74 -0
- package/dist/ui/primitives/sidebar.d.ts +74 -0
- package/dist/ui/primitives/sidebar.js +697 -0
- package/dist/ui/primitives/sidebar.js.map +1 -0
- package/dist/ui/primitives/sidebar.mjs +651 -0
- package/dist/ui/primitives/sidebar.mjs.map +1 -0
- package/dist/ui/primitives/skeleton.d.mts +5 -0
- package/dist/ui/primitives/skeleton.d.ts +5 -0
- package/dist/ui/primitives/skeleton.js +40 -0
- package/dist/ui/primitives/skeleton.js.map +1 -0
- package/dist/ui/primitives/skeleton.mjs +16 -0
- package/dist/ui/primitives/skeleton.mjs.map +1 -0
- package/dist/ui/primitives/slider.d.mts +20 -0
- package/dist/ui/primitives/slider.d.ts +20 -0
- package/dist/ui/primitives/slider.js +112 -0
- package/dist/ui/primitives/slider.js.map +1 -0
- package/dist/ui/primitives/slider.mjs +78 -0
- package/dist/ui/primitives/slider.mjs.map +1 -0
- package/dist/ui/primitives/sortable.d.mts +50 -0
- package/dist/ui/primitives/sortable.d.ts +50 -0
- package/dist/ui/primitives/sortable.js +456 -0
- package/dist/ui/primitives/sortable.js.map +1 -0
- package/dist/ui/primitives/sortable.mjs +435 -0
- package/dist/ui/primitives/sortable.mjs.map +1 -0
- package/dist/ui/primitives/switch.d.mts +6 -0
- package/dist/ui/primitives/switch.d.ts +6 -0
- package/dist/ui/primitives/switch.js +67 -0
- package/dist/ui/primitives/switch.js.map +1 -0
- package/dist/ui/primitives/switch.mjs +33 -0
- package/dist/ui/primitives/switch.mjs.map +1 -0
- package/dist/ui/primitives/table.d.mts +12 -0
- package/dist/ui/primitives/table.d.ts +12 -0
- package/dist/ui/primitives/table.js +130 -0
- package/dist/ui/primitives/table.js.map +1 -0
- package/dist/ui/primitives/table.mjs +89 -0
- package/dist/ui/primitives/table.mjs.map +1 -0
- package/dist/ui/primitives/tabs.d.mts +9 -0
- package/dist/ui/primitives/tabs.d.ts +9 -0
- package/dist/ui/primitives/tabs.js +86 -0
- package/dist/ui/primitives/tabs.js.map +1 -0
- package/dist/ui/primitives/tabs.mjs +49 -0
- package/dist/ui/primitives/tabs.mjs.map +1 -0
- package/dist/ui/primitives/textarea.d.mts +5 -0
- package/dist/ui/primitives/textarea.d.ts +5 -0
- package/dist/ui/primitives/textarea.js +43 -0
- package/dist/ui/primitives/textarea.js.map +1 -0
- package/dist/ui/primitives/textarea.mjs +19 -0
- package/dist/ui/primitives/textarea.mjs.map +1 -0
- package/dist/ui/primitives/tooltip.d.mts +9 -0
- package/dist/ui/primitives/tooltip.d.ts +9 -0
- package/dist/ui/primitives/tooltip.js +94 -0
- package/dist/ui/primitives/tooltip.js.map +1 -0
- package/dist/ui/primitives/tooltip.mjs +57 -0
- package/dist/ui/primitives/tooltip.mjs.map +1 -0
- package/docs/agents.md +72 -29
- package/docs/api/README.md +6 -3
- package/docs/api/appObjects.d.ts +1 -1
- package/docs/api/appObjects.md +1 -1
- package/docs/api/by-path.md +29 -0
- package/docs/api/objects.d.ts +72 -5
- package/docs/api/objects.md +84 -7
- package/docs/api/processLists.d.ts +247 -0
- package/docs/api/processLists.md +409 -0
- package/docs/api/reports.d.ts +476 -0
- package/docs/api/reports.md +539 -0
- package/docs/api/scheduleBuy.d.ts +12 -8
- package/docs/api/scheduleBuy.md +13 -10
- package/docs/api/seo.d.ts +406 -3
- package/docs/api/seo.md +505 -1
- package/docs/api/workspaceBackup.d.ts +324 -0
- package/docs/api/workspaceBackup.md +427 -0
- package/package.json +174 -5
- package/stacks/6sense.json +46 -0
- package/stacks/README.md +20 -0
- package/stacks/apollo.json +128 -0
- package/stacks/clay.json +59 -0
- package/stacks/gong.json +72 -0
- package/stacks/hubspot.json +162 -0
- package/stacks/intercom.json +53 -0
- package/stacks/mailchimp.json +71 -0
- package/stacks/outreach.json +86 -0
- package/stacks/salesforce.json +94 -0
- package/stacks/stack.schema.json +32 -0
- package/stacks/starter.json +22 -0
- package/styles/styles.css +2 -0
- package/styles/theme.css +181 -0
- package/styles/tokens.css +284 -0
- package/templates/next-app/README.md.tmpl +35 -0
- package/templates/next-app/_pages/redirect-page.tsx.tmpl +5 -0
- package/templates/next-app/_pages/route-page.tsx.tmpl +9 -0
- package/templates/next-app/app/globals.css.tmpl +8 -0
- package/templates/next-app/app/layout.tsx.tmpl +19 -0
- package/templates/next-app/components/app-shell.tsx.tmpl +34 -0
- package/templates/next-app/dot-env.local.example.tmpl +6 -0
- package/templates/next-app/dot-gitignore.tmpl +7 -0
- package/templates/next-app/lib/nav.ts.tmpl +2 -0
- package/templates/next-app/next.config.mjs.tmpl +4 -0
- package/templates/next-app/package.json.tmpl +25 -0
- package/templates/next-app/postcss.config.mjs.tmpl +4 -0
- package/templates/next-app/tsconfig.json.tmpl +21 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../block.schema.json",
|
|
3
|
+
"id": "workflows",
|
|
4
|
+
"title": "Workflows",
|
|
5
|
+
"summary": "Workflow list, step-list editor, trigger configuration and run history with pause, resume, stop and restart.",
|
|
6
|
+
"features": [
|
|
7
|
+
"workflow list with run preview, run, enable and delete",
|
|
8
|
+
"ordered step editor for linear workflows",
|
|
9
|
+
"trigger type, interval and cursor reset",
|
|
10
|
+
"run history with polled detail and run controls"
|
|
11
|
+
],
|
|
12
|
+
"dependsOn": [
|
|
13
|
+
"foundation"
|
|
14
|
+
],
|
|
15
|
+
"operations": [
|
|
16
|
+
"list_workflows_workflows_get",
|
|
17
|
+
"create_workflow_workflows_post",
|
|
18
|
+
"plan_workflow_workflows__action_id__plan_post",
|
|
19
|
+
"execute_workflow_workflows__action_id__execute_post",
|
|
20
|
+
"update_workflow_workflows__action_id__put",
|
|
21
|
+
"delete_workflow_workflows__action_id__delete",
|
|
22
|
+
"get_workflow_workflows__action_id__get",
|
|
23
|
+
"get_node_types_schema_workflows_node_types_schema_get",
|
|
24
|
+
"get_trigger_status_workflows__action_id__trigger_status_get",
|
|
25
|
+
"reset_trigger_cursor_workflows__action_id__trigger_reset_post",
|
|
26
|
+
"list_workflow_executions_workflows_executions_get",
|
|
27
|
+
"get_execution_workflows_executions__execution_id__get",
|
|
28
|
+
"pause_execution_workflows_executions__execution_id__pause_post",
|
|
29
|
+
"resume_execution_workflows_executions__execution_id__resume_post",
|
|
30
|
+
"stop_execution_workflows_executions__execution_id__stop_post",
|
|
31
|
+
"restart_execution_workflows_executions__execution_id__restart_post"
|
|
32
|
+
],
|
|
33
|
+
"components": [
|
|
34
|
+
{
|
|
35
|
+
"name": "WorkflowList",
|
|
36
|
+
"file": "components/workflow-list.tsx",
|
|
37
|
+
"purpose": "Workflows with run (plan preview and JSON input behind a confirm), enable or disable, create and delete.",
|
|
38
|
+
"operations": [
|
|
39
|
+
"list_workflows_workflows_get",
|
|
40
|
+
"create_workflow_workflows_post",
|
|
41
|
+
"plan_workflow_workflows__action_id__plan_post",
|
|
42
|
+
"execute_workflow_workflows__action_id__execute_post",
|
|
43
|
+
"update_workflow_workflows__action_id__put",
|
|
44
|
+
"delete_workflow_workflows__action_id__delete"
|
|
45
|
+
],
|
|
46
|
+
"tiers": [
|
|
47
|
+
"read",
|
|
48
|
+
"external",
|
|
49
|
+
"destructive"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "WorkflowEditor",
|
|
54
|
+
"file": "components/workflow-editor.tsx",
|
|
55
|
+
"purpose": "Edit a workflow as an ordered list of steps: names, JSON configs, and add, move or remove steps on linear graphs; save behind a confirm.",
|
|
56
|
+
"operations": [
|
|
57
|
+
"get_workflow_workflows__action_id__get",
|
|
58
|
+
"get_node_types_schema_workflows_node_types_schema_get",
|
|
59
|
+
"update_workflow_workflows__action_id__put"
|
|
60
|
+
],
|
|
61
|
+
"tiers": [
|
|
62
|
+
"read",
|
|
63
|
+
"external"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"name": "TriggerConfig",
|
|
68
|
+
"file": "components/trigger-config.tsx",
|
|
69
|
+
"purpose": "Trigger type, polling interval and filters with the live trigger status and a reset-cursor action, both behind a confirm.",
|
|
70
|
+
"operations": [
|
|
71
|
+
"get_workflow_workflows__action_id__get",
|
|
72
|
+
"get_trigger_status_workflows__action_id__trigger_status_get",
|
|
73
|
+
"get_node_types_schema_workflows_node_types_schema_get",
|
|
74
|
+
"update_workflow_workflows__action_id__put",
|
|
75
|
+
"reset_trigger_cursor_workflows__action_id__trigger_reset_post"
|
|
76
|
+
],
|
|
77
|
+
"tiers": [
|
|
78
|
+
"read",
|
|
79
|
+
"external"
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"name": "RunHistory",
|
|
84
|
+
"file": "components/run-history.tsx",
|
|
85
|
+
"purpose": "Runs with a status filter, a polled run detail, and pause, resume, stop and restart behind a confirm.",
|
|
86
|
+
"operations": [
|
|
87
|
+
"list_workflow_executions_workflows_executions_get",
|
|
88
|
+
"get_execution_workflows_executions__execution_id__get",
|
|
89
|
+
"pause_execution_workflows_executions__execution_id__pause_post",
|
|
90
|
+
"resume_execution_workflows_executions__execution_id__resume_post",
|
|
91
|
+
"stop_execution_workflows_executions__execution_id__stop_post",
|
|
92
|
+
"restart_execution_workflows_executions__execution_id__restart_post"
|
|
93
|
+
],
|
|
94
|
+
"tiers": [
|
|
95
|
+
"read",
|
|
96
|
+
"external"
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"hooks": [
|
|
101
|
+
{
|
|
102
|
+
"name": "useWorkflow",
|
|
103
|
+
"file": "hooks/use-workflow.ts",
|
|
104
|
+
"purpose": "Load one workflow and save changes to its graph (saving is external tier, so the caller confirms first).",
|
|
105
|
+
"operations": [
|
|
106
|
+
"get_workflow_workflows__action_id__get",
|
|
107
|
+
"update_workflow_workflows__action_id__put"
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
"lib": [
|
|
112
|
+
{
|
|
113
|
+
"file": "lib/graph.ts",
|
|
114
|
+
"purpose": "Pure helpers: read a graph as ordered steps, add, move and remove steps while keeping connections and edges in sync, and the row types."
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
"recipes": [
|
|
118
|
+
{
|
|
119
|
+
"file": "recipes/create-workflow.ts",
|
|
120
|
+
"title": "Create a workflow from a list of steps, validate it, and save it",
|
|
121
|
+
"when": "An automation is described; read the node catalog first, validate, then save (external)."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"file": "recipes/run-workflow-and-wait.ts",
|
|
125
|
+
"title": "Preview a workflow run, start it, and wait for the result",
|
|
126
|
+
"when": "A workflow should run now; plan is free, run is external, then poll the execution."
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"file": "recipes/find-failed-runs.ts",
|
|
130
|
+
"title": "List a workflow's failed runs with their errors",
|
|
131
|
+
"when": "Something did not happen and the cause is needed; reading runs is free."
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"file": "recipes/set-trigger.ts",
|
|
135
|
+
"title": "Change a workflow's trigger and skip the backlog",
|
|
136
|
+
"when": "A different start event or interval is wanted; external, and a cursor reset skips events."
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
"prerequisites": [
|
|
140
|
+
{
|
|
141
|
+
"id": "workflow-resources",
|
|
142
|
+
"description": "Anything a step references (a list, a sequence, a mailbox, a form, a workbench) already exists; steps hold ids, not names.",
|
|
143
|
+
"where": "api",
|
|
144
|
+
"verify": "Run POST /workflows/validate on the graph: missing_references must be empty."
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"id": "integrations",
|
|
148
|
+
"description": "Slack, Roam or LinkedIn steps need those integrations connected by a person in graph8.",
|
|
149
|
+
"where": "app",
|
|
150
|
+
"verify": "GET /workflows/integrations/slack/channels returns channels (or the equivalent for the provider)."
|
|
151
|
+
}
|
|
152
|
+
],
|
|
153
|
+
"scopes": [
|
|
154
|
+
"workflows:delete",
|
|
155
|
+
"workflows:read",
|
|
156
|
+
"workflows:run"
|
|
157
|
+
],
|
|
158
|
+
"defaults": [
|
|
159
|
+
{
|
|
160
|
+
"decision": "How a new workflow starts",
|
|
161
|
+
"default": "A manual trigger (trigger_type tool_call) and an end step; the person adds steps",
|
|
162
|
+
"askHuman": false
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"decision": "Whether to enable a new workflow that sends messages",
|
|
166
|
+
"default": "Leave disabled until the person has seen the plan",
|
|
167
|
+
"askHuman": true
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"decision": "Editing branching workflows",
|
|
171
|
+
"default": "Names and configs only; shape changes happen in the graph8 canvas",
|
|
172
|
+
"askHuman": false
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"decision": "Run input",
|
|
176
|
+
"default": "Empty JSON object unless the workflow declares required inputs",
|
|
177
|
+
"askHuman": false
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"decision": "Page size for run history",
|
|
181
|
+
"default": "25 rows",
|
|
182
|
+
"askHuman": false
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
"peerDependencies": {
|
|
186
|
+
"react": ">=18"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RunHistory: a workflow's runs (or every run), one run's detail, and pause / resume / stop / restart.
|
|
5
|
+
*
|
|
6
|
+
* SDK operations, in the order the screen uses them:
|
|
7
|
+
* 1. listWorkflowExecutions GET /workflows/executions?action_id&status_filter&limit&offset (read)
|
|
8
|
+
* 2. getExecution GET /workflows/executions/{execution_id} (read; polled while
|
|
9
|
+
* the run is pending or running, because the API has no stream)
|
|
10
|
+
* 3. pauseExecution POST /workflows/executions/{id}/pause (external)
|
|
11
|
+
* 4. resumeExecution POST /workflows/executions/{id}/resume (external)
|
|
12
|
+
* 5. stopExecution POST /workflows/executions/{id}/stop (external)
|
|
13
|
+
* 6. restartExecution POST /workflows/executions/{id}/restart (external)
|
|
14
|
+
*
|
|
15
|
+
* Every state-changing action goes through ConfirmSpendDialog: resuming or restarting a run can message people
|
|
16
|
+
* again. Statuses: pending | running | completed | failed | paused | pending_approval | stopped. `paused` and
|
|
17
|
+
* `pending_approval` wait for a person and are NOT terminal.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useState } from "react";
|
|
21
|
+
import { Button, Card, CardContent, CardHeader, CardTitle, Skeleton, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@graph8/sdk/ui";
|
|
22
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
23
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
24
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
25
|
+
import { useJobPoll } from "@/g8/hooks/use-job-poll";
|
|
26
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
27
|
+
import { EmptyState } from "@/g8/components/empty-state";
|
|
28
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
29
|
+
import { TERMINAL_STATUSES, WAITING_STATUSES, payload, str } from "../lib/graph";
|
|
30
|
+
import type { ExecutionRow, Rec } from "../lib/graph";
|
|
31
|
+
|
|
32
|
+
export interface RunHistoryProps {
|
|
33
|
+
/** Only this workflow's runs. Omit for every workflow. */
|
|
34
|
+
actionId?: string;
|
|
35
|
+
/** Open this run's detail first (for example the id returned by WorkflowList's onRunStarted). */
|
|
36
|
+
initialExecutionId?: string;
|
|
37
|
+
pageSize?: number;
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type Verb = "pause" | "resume" | "stop" | "restart";
|
|
42
|
+
const STATUSES = ["", "running", "completed", "failed", "paused", "pending_approval", "stopped"];
|
|
43
|
+
const VERB_COPY: Record<Verb, { title: string; description: string; label: string }> = {
|
|
44
|
+
pause: { title: "Pause this run", description: "No further steps run until it is resumed.", label: "Pause" },
|
|
45
|
+
resume: { title: "Resume this run", description: "The remaining steps run now, including any that message people.", label: "Resume" },
|
|
46
|
+
stop: { title: "Stop this run", description: "The run ends and cannot be resumed.", label: "Stop" },
|
|
47
|
+
restart: { title: "Restart this run", description: "Runs the workflow again from the start with the same input. Steps that message people will run again.", label: "Restart" },
|
|
48
|
+
};
|
|
49
|
+
/** Which verbs make sense for a status. */
|
|
50
|
+
const allowed = (status: string): Verb[] =>
|
|
51
|
+
status === "running" ? ["pause", "stop"] : status === "paused" || status === "pending_approval" ? ["resume", "stop"] : TERMINAL_STATUSES.includes(status) ? ["restart"] : [];
|
|
52
|
+
|
|
53
|
+
function Detail({ executionId, onChanged }: { executionId: string; onChanged: () => void }) {
|
|
54
|
+
const [verb, setVerb] = useState<Verb | null>(null);
|
|
55
|
+
const poll = useJobPoll<Rec>(async (signal) => payload(await g8Get(`/workflows/executions/${encodeURIComponent(executionId)}`, undefined, { signal })), {
|
|
56
|
+
isTerminal: (e) => TERMINAL_STATUSES.includes(str(e.status)) || WAITING_STATUSES.includes(str(e.status)),
|
|
57
|
+
});
|
|
58
|
+
const act = useG8Mutation((v: Verb) => g8Post(`/workflows/executions/${encodeURIComponent(executionId)}/${v}`), {
|
|
59
|
+
onSuccess: () => { setVerb(null); poll.start(); onChanged(); },
|
|
60
|
+
});
|
|
61
|
+
const e = poll.status ?? {};
|
|
62
|
+
const status = str(e.status, "pending");
|
|
63
|
+
return (
|
|
64
|
+
<Card data-testid="run-detail">
|
|
65
|
+
<CardHeader><CardTitle className="text-sm">Run {executionId.slice(0, 8)}</CardTitle></CardHeader>
|
|
66
|
+
<CardContent className="space-y-3 text-sm">
|
|
67
|
+
{poll.error ? <ApiError error={poll.error} title="Could not read this run" onRetry={poll.start} /> : null}
|
|
68
|
+
{act.error ? <ApiError error={act.error} title="That action failed" /> : null}
|
|
69
|
+
<p>Status: <strong>{status.replace(/_/g, " ")}</strong>{e.duration_ms != null ? ` in ${Math.round(Number(e.duration_ms) / 100) / 10}s` : ""}</p>
|
|
70
|
+
{e.error_message ? <p className="text-destructive">{str(e.error_message)}</p> : null}
|
|
71
|
+
{e.input_data ? <details><summary>Input</summary><pre className="overflow-auto text-xs">{JSON.stringify(e.input_data, null, 2)}</pre></details> : null}
|
|
72
|
+
{e.output_data ? <details open><summary>Output</summary><pre className="overflow-auto text-xs">{JSON.stringify(e.output_data, null, 2)}</pre></details> : null}
|
|
73
|
+
<div className="flex gap-2">
|
|
74
|
+
{allowed(status).map((v) => <Button key={v} size="sm" variant="outline" onClick={() => setVerb(v)}>{VERB_COPY[v].label}</Button>)}
|
|
75
|
+
</div>
|
|
76
|
+
{verb ? (
|
|
77
|
+
<ConfirmSpendDialog open tier="external" title={VERB_COPY[verb].title} description={VERB_COPY[verb].description} confirmLabel={VERB_COPY[verb].label}
|
|
78
|
+
confirming={act.loading} onConfirm={() => void act.mutate(verb)} onCancel={() => setVerb(null)} />
|
|
79
|
+
) : null}
|
|
80
|
+
</CardContent>
|
|
81
|
+
</Card>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function RunHistory({ actionId, initialExecutionId, pageSize = 25, className }: RunHistoryProps) {
|
|
86
|
+
const [status, setStatus] = useState("");
|
|
87
|
+
const [offset, setOffset] = useState(0);
|
|
88
|
+
const [selected, setSelected] = useState<string | null>(initialExecutionId ?? null);
|
|
89
|
+
const list = useG8Query(
|
|
90
|
+
(signal) => g8Get("/workflows/executions", { action_id: actionId, status_filter: status || undefined, limit: pageSize, offset }, { signal }),
|
|
91
|
+
[actionId, status, offset, pageSize],
|
|
92
|
+
);
|
|
93
|
+
const body = payload(list.data);
|
|
94
|
+
const rows = (Array.isArray(body.executions) ? body.executions : []) as ExecutionRow[];
|
|
95
|
+
const total = typeof body.total_count === "number" ? body.total_count : rows.length;
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div className={className}>
|
|
99
|
+
<div className="mb-3 flex items-center gap-2">
|
|
100
|
+
<select aria-label="Status" value={status} onChange={(e) => { setStatus(e.target.value); setOffset(0); }} className="border-input bg-background h-9 rounded-md border px-2 text-sm">
|
|
101
|
+
{STATUSES.map((s) => <option key={s} value={s}>{s ? s.replace(/_/g, " ") : "All statuses"}</option>)}
|
|
102
|
+
</select>
|
|
103
|
+
<Button size="sm" variant="outline" onClick={() => void list.refetch()}>Refresh</Button>
|
|
104
|
+
</div>
|
|
105
|
+
{list.error ? <ApiError error={list.error} onRetry={() => void list.refetch()} /> : null}
|
|
106
|
+
<div className="grid gap-4 lg:grid-cols-2">
|
|
107
|
+
<div>
|
|
108
|
+
{list.loading && !list.data ? <div className="space-y-2" aria-busy="true">{[0, 1, 2].map((i) => <Skeleton key={i} className="h-10 w-full" />)}</div> : !list.error && rows.length === 0 ? (
|
|
109
|
+
<EmptyState title="No runs yet" description="Runs appear here when a trigger fires or someone runs the workflow." />
|
|
110
|
+
) : (
|
|
111
|
+
<Table>
|
|
112
|
+
<TableHeader><TableRow><TableHead>Started</TableHead><TableHead>Status</TableHead><TableHead>By</TableHead></TableRow></TableHeader>
|
|
113
|
+
<TableBody>
|
|
114
|
+
{rows.map((r) => (
|
|
115
|
+
<TableRow key={r.execution_id} data-state={selected === r.execution_id ? "selected" : undefined}>
|
|
116
|
+
<TableCell><button type="button" className="hover:underline" onClick={() => setSelected(r.execution_id)}>{new Date(str(r.started_at ?? r.created_at)).toLocaleString()}</button></TableCell>
|
|
117
|
+
<TableCell className="capitalize">{r.status.replace(/_/g, " ")}</TableCell>
|
|
118
|
+
<TableCell>{r.triggered_by ?? r.trigger_type ?? ""}</TableCell>
|
|
119
|
+
</TableRow>
|
|
120
|
+
))}
|
|
121
|
+
</TableBody>
|
|
122
|
+
</Table>
|
|
123
|
+
)}
|
|
124
|
+
<div className="mt-3 flex items-center justify-between text-sm">
|
|
125
|
+
<span className="text-muted-foreground">{total} {total === 1 ? "run" : "runs"}</span>
|
|
126
|
+
<div className="flex gap-2">
|
|
127
|
+
<Button size="sm" variant="outline" disabled={offset === 0} onClick={() => setOffset(Math.max(0, offset - pageSize))}>Previous</Button>
|
|
128
|
+
<Button size="sm" variant="outline" disabled={offset + pageSize >= total} onClick={() => setOffset(offset + pageSize)}>Next</Button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
<div>{selected ? <Detail key={selected} executionId={selected} onChanged={() => void list.refetch()} /> : <p className="text-muted-foreground text-sm">Select a run to see its input, output and errors.</p>}</div>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* TriggerConfig: what starts a workflow (event type, polling interval, filters) and where its trigger cursor is.
|
|
5
|
+
*
|
|
6
|
+
* Event triggers read events newer than a stored cursor, so a workflow that was off for a week would replay a
|
|
7
|
+
* week of events when it comes back; "Reset cursor to now" skips that backlog.
|
|
8
|
+
*
|
|
9
|
+
* SDK operations, in the order the screen uses them:
|
|
10
|
+
* 1. getWorkflow GET /workflows/{id} (read; via useWorkflow)
|
|
11
|
+
* 2. getTriggerStatus GET /workflows/{id}/trigger-status (read; type, interval, cursor)
|
|
12
|
+
* 3. getNodeTypesSchema GET /workflows/node-types/schema?node_type=trigger (read; the trigger types offered)
|
|
13
|
+
* 4. updateWorkflow PUT /workflows/{id} { config } (external, via useWorkflow)
|
|
14
|
+
* 5. resetTriggerCursor POST /workflows/{id}/trigger-reset (external: skips unprocessed events)
|
|
15
|
+
*
|
|
16
|
+
* Save and reset both go through ConfirmSpendDialog: a changed trigger decides what real events start real work.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { useState } from "react";
|
|
20
|
+
import { Button, Card, CardContent, CardHeader, CardTitle, Input, Label, Skeleton, Textarea } from "@graph8/sdk/ui";
|
|
21
|
+
import { g8Get, g8Post } from "@/g8/lib/g8-client";
|
|
22
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
23
|
+
import { useG8Mutation } from "@/g8/hooks/use-g8-mutation";
|
|
24
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
25
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
26
|
+
import { useWorkflow } from "../hooks/use-workflow";
|
|
27
|
+
import { asRec, nodeIdOf, nodesOf, payload, setStep, str, triggerOf } from "../lib/graph";
|
|
28
|
+
import type { Rec } from "../lib/graph";
|
|
29
|
+
|
|
30
|
+
export interface TriggerConfigProps {
|
|
31
|
+
actionId: string;
|
|
32
|
+
onSaved?: () => void;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** The trigger config without the two fields that get their own inputs. */
|
|
37
|
+
function restOf(config: Rec): string {
|
|
38
|
+
const { trigger_type: _t, polling_interval_minutes: _p, ...rest } = config;
|
|
39
|
+
return JSON.stringify(rest, null, 2);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type SaveHandle = ReturnType<typeof useWorkflow>["save"];
|
|
43
|
+
|
|
44
|
+
function Form({ actionId, config, save, saveAndRefresh, trigger, types, onSaved }: { actionId: string; config: Rec; save: SaveHandle; saveAndRefresh: (u: { config: Rec }) => Promise<unknown>; trigger: Rec; types: string[]; onSaved: () => void }) {
|
|
45
|
+
const cfg = asRec(trigger.config);
|
|
46
|
+
const [type, setType] = useState(str(cfg.trigger_type));
|
|
47
|
+
const [interval, setIntervalMin] = useState(cfg.polling_interval_minutes == null ? "" : String(cfg.polling_interval_minutes));
|
|
48
|
+
const [rest, setRest] = useState(restOf(cfg));
|
|
49
|
+
const [confirm, setConfirm] = useState<"save" | "reset" | null>(null);
|
|
50
|
+
const reset = useG8Mutation(() => g8Post(`/workflows/${encodeURIComponent(actionId)}/trigger-reset`), { onSuccess: () => { setConfirm(null); onSaved(); } });
|
|
51
|
+
let parsed: Rec | null = null;
|
|
52
|
+
try { parsed = asRec(JSON.parse(rest || "{}")); } catch { parsed = null; }
|
|
53
|
+
|
|
54
|
+
const next = (): Rec => {
|
|
55
|
+
const merged: Rec = { ...parsed, trigger_type: type };
|
|
56
|
+
if (interval.trim()) merged.polling_interval_minutes = Number(interval);
|
|
57
|
+
return setStep(config, nodeIdOf(trigger), { config: merged });
|
|
58
|
+
};
|
|
59
|
+
const options = type && !types.includes(type) ? [type, ...types] : types;
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<Card>
|
|
63
|
+
<CardHeader><CardTitle className="text-sm">Trigger</CardTitle></CardHeader>
|
|
64
|
+
<CardContent className="space-y-3">
|
|
65
|
+
<div className="space-y-1">
|
|
66
|
+
<Label htmlFor="trg-type">Starts when</Label>
|
|
67
|
+
<select id="trg-type" value={type} onChange={(e) => setType(e.target.value)} className="border-input bg-background h-9 w-full rounded-md border px-2 text-sm">
|
|
68
|
+
{options.map((t) => <option key={t} value={t}>{t.replace(/_/g, " ")}</option>)}
|
|
69
|
+
</select>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="space-y-1">
|
|
72
|
+
<Label htmlFor="trg-interval">Check every (minutes)</Label>
|
|
73
|
+
<Input id="trg-interval" type="number" min={1} value={interval} onChange={(e) => setIntervalMin(e.target.value)} className="w-32" />
|
|
74
|
+
</div>
|
|
75
|
+
<div className="space-y-1">
|
|
76
|
+
<Label htmlFor="trg-rest">Filters and other settings (JSON)</Label>
|
|
77
|
+
<Textarea id="trg-rest" rows={5} className="font-mono text-xs" value={rest} aria-invalid={parsed === null} onChange={(e) => setRest(e.target.value)} />
|
|
78
|
+
{parsed === null ? <p className="text-destructive text-xs">That is not valid JSON.</p> : null}
|
|
79
|
+
</div>
|
|
80
|
+
{save.error ? <ApiError error={save.error} title="Could not save the trigger" /> : null}
|
|
81
|
+
{reset.error ? <ApiError error={reset.error} title="Could not reset the cursor" /> : null}
|
|
82
|
+
<div className="flex gap-2">
|
|
83
|
+
<Button disabled={parsed === null || !type} onClick={() => setConfirm("save")}>Save trigger</Button>
|
|
84
|
+
<Button variant="outline" onClick={() => setConfirm("reset")}>Reset cursor to now</Button>
|
|
85
|
+
</div>
|
|
86
|
+
</CardContent>
|
|
87
|
+
{confirm ? (
|
|
88
|
+
<ConfirmSpendDialog open tier="external"
|
|
89
|
+
title={confirm === "save" ? "Save this trigger" : "Reset the trigger cursor to now"}
|
|
90
|
+
description={confirm === "save" ? "New events matching this trigger start runs that can reach real contacts." : "Events that arrived since the last run are skipped and never processed."}
|
|
91
|
+
confirmLabel={confirm === "save" ? "Save" : "Reset"} confirming={save.loading || reset.loading}
|
|
92
|
+
onConfirm={() => {
|
|
93
|
+
if (confirm === "reset") return void reset.mutate(undefined);
|
|
94
|
+
void saveAndRefresh({ config: next() }).then((r) => { setConfirm(null); if (r) onSaved(); });
|
|
95
|
+
}}
|
|
96
|
+
onCancel={() => setConfirm(null)} />
|
|
97
|
+
) : null}
|
|
98
|
+
</Card>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function TriggerConfig({ actionId, onSaved, className }: TriggerConfigProps) {
|
|
103
|
+
const wf = useWorkflow(actionId);
|
|
104
|
+
const status = useG8Query((signal) => g8Get(`/workflows/${encodeURIComponent(actionId)}/trigger-status`, undefined, { signal }), [actionId]);
|
|
105
|
+
const catalog = useG8Query((signal) => g8Get("/workflows/node-types/schema", { node_type: "trigger", detail: "slim" }, { signal }), []);
|
|
106
|
+
|
|
107
|
+
if (wf.loading) return <div className={className} aria-busy="true"><Skeleton className="h-40 w-full" /></div>;
|
|
108
|
+
if (wf.error) return <ApiError error={wf.error} onRetry={() => void wf.refetch()} />;
|
|
109
|
+
const trigger = triggerOf(wf.config);
|
|
110
|
+
const s = payload(status.data);
|
|
111
|
+
const entry = asRec(payload(catalog.data).node_type);
|
|
112
|
+
const types = Object.keys(asRec(entry.trigger_output_schemas)).sort();
|
|
113
|
+
const refresh = () => { void wf.refetch(); void status.refetch(); onSaved?.(); };
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className={className}>
|
|
117
|
+
<Card className="mb-4">
|
|
118
|
+
<CardHeader><CardTitle className="text-sm">Status</CardTitle></CardHeader>
|
|
119
|
+
<CardContent className="text-sm" data-testid="trigger-status">
|
|
120
|
+
{status.error ? <ApiError error={status.error} onRetry={() => void status.refetch()} /> : s.has_trigger === false || !trigger ? "This workflow has no trigger; it only runs when you run it." : (
|
|
121
|
+
<>
|
|
122
|
+
<p>{str(s.trigger_type).replace(/_/g, " ")}, checked every {str(s.polling_interval_minutes, "5")} minutes. {s.enabled === false ? "The workflow is disabled, so nothing fires." : "Enabled."}</p>
|
|
123
|
+
<p className="text-muted-foreground">Cursor: {s.cursor ? str(s.cursor) : "no events processed yet"}</p>
|
|
124
|
+
</>
|
|
125
|
+
)}
|
|
126
|
+
</CardContent>
|
|
127
|
+
</Card>
|
|
128
|
+
{catalog.error ? <p className="mb-2 text-xs">The trigger catalog could not be loaded; the current type is still editable.</p> : null}
|
|
129
|
+
{trigger ? <Form key={JSON.stringify(trigger.config)} actionId={actionId} config={wf.config} save={wf.save} saveAndRefresh={wf.saveAndRefresh} trigger={trigger} types={types} onSaved={refresh} /> : (
|
|
130
|
+
<p className="text-muted-foreground text-sm">There is no trigger node to configure ({nodesOf(wf.config).length} steps). Add a trigger step in the graph8 app.</p>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WorkflowEditor: a workflow as an ordered list of steps (not a canvas).
|
|
5
|
+
*
|
|
6
|
+
* Each step has a name and a JSON config. For a LINEAR workflow you can add a step (from the node catalog),
|
|
7
|
+
* move it up or down, or remove it; for a branching one only names and configs are editable, because a list
|
|
8
|
+
* cannot show where a branch goes (the graph8 app has the canvas). Saving rewrites `connections` and `edges`
|
|
9
|
+
* together (see ../lib/graph.ts) and PUTs the whole graph.
|
|
10
|
+
*
|
|
11
|
+
* SDK operations, in the order the screen uses them:
|
|
12
|
+
* 1. getWorkflow GET /workflows/{id} (read; via useWorkflow)
|
|
13
|
+
* 2. getNodeTypesSchema GET /workflows/node-types/schema?detail=slim (read; the "Add step" catalog)
|
|
14
|
+
* 3. updateWorkflow PUT /workflows/{id} { config } (external, via useWorkflow; saving can
|
|
15
|
+
* change what an enabled workflow sends, so it goes through ConfirmSpendDialog)
|
|
16
|
+
*
|
|
17
|
+
* Unsaved edits are kept in the page, never sent until the person confirms Save.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { useState } from "react";
|
|
21
|
+
import { Button, Card, CardContent, CardHeader, CardTitle, Input, Skeleton, Textarea } from "@graph8/sdk/ui";
|
|
22
|
+
import { g8Get } from "@/g8/lib/g8-client";
|
|
23
|
+
import { useG8Query } from "@/g8/hooks/use-g8-query";
|
|
24
|
+
import { ApiError } from "@/g8/components/api-error";
|
|
25
|
+
import { ConfirmSpendDialog } from "@/g8/components/confirm-spend-dialog";
|
|
26
|
+
import { useWorkflow } from "../hooks/use-workflow";
|
|
27
|
+
import { addStep, asRec, canMove, isLinear, moveStep, newStep, nodeIdOf, nodeNameOf, nodeTypeOf, orderedSteps, payload, removeStep, setStep, str } from "../lib/graph";
|
|
28
|
+
import type { Rec, WfNode } from "../lib/graph";
|
|
29
|
+
|
|
30
|
+
export interface WorkflowEditorProps {
|
|
31
|
+
actionId: string;
|
|
32
|
+
onSaved?: () => void;
|
|
33
|
+
className?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Types an author may add by hand. `start` is deprecated and `trigger` has its own screen (TriggerConfig). */
|
|
37
|
+
const NOT_ADDABLE = new Set(["start", "trigger", "end"]);
|
|
38
|
+
|
|
39
|
+
function StepCard({ step, index, canUp, canDown, linear, onChange, onMove, onRemove }: { step: WfNode; index: number; canUp: boolean; canDown: boolean; linear: boolean; onChange: (patch: Rec) => void; onMove: (d: -1 | 1) => void; onRemove: () => void }) {
|
|
40
|
+
const id = nodeIdOf(step);
|
|
41
|
+
const [text, setText] = useState(() => JSON.stringify(asRec(step.config), null, 2));
|
|
42
|
+
const [bad, setBad] = useState(false);
|
|
43
|
+
const canRemove = linear && nodeTypeOf(step) !== "trigger" && nodeTypeOf(step) !== "end";
|
|
44
|
+
return (
|
|
45
|
+
<Card data-testid={`step-${id}`}>
|
|
46
|
+
<CardHeader className="flex flex-row items-center gap-2">
|
|
47
|
+
<span className="text-muted-foreground text-xs">{index + 1}</span>
|
|
48
|
+
<CardTitle className="flex-1 text-sm">{nodeNameOf(step)} <span className="text-muted-foreground font-normal">({nodeTypeOf(step)})</span></CardTitle>
|
|
49
|
+
{linear ? (
|
|
50
|
+
<>
|
|
51
|
+
<Button size="sm" variant="ghost" aria-label={`Move ${id} up`} disabled={!canUp} onClick={() => onMove(-1)}>Up</Button>
|
|
52
|
+
<Button size="sm" variant="ghost" aria-label={`Move ${id} down`} disabled={!canDown} onClick={() => onMove(1)}>Down</Button>
|
|
53
|
+
</>
|
|
54
|
+
) : null}
|
|
55
|
+
{canRemove ? <Button size="sm" variant="ghost" aria-label={`Remove ${id}`} onClick={onRemove}>Remove</Button> : null}
|
|
56
|
+
</CardHeader>
|
|
57
|
+
<CardContent className="space-y-2">
|
|
58
|
+
<Input aria-label={`Name of ${id}`} value={str(step.name)} onChange={(e) => onChange({ name: e.target.value })} />
|
|
59
|
+
<Textarea aria-label={`Config of ${id}`} rows={5} className="font-mono text-xs" value={text} aria-invalid={bad}
|
|
60
|
+
onChange={(e) => {
|
|
61
|
+
setText(e.target.value);
|
|
62
|
+
try { onChange({ config: JSON.parse(e.target.value) }); setBad(false); } catch { setBad(true); }
|
|
63
|
+
}} />
|
|
64
|
+
{bad ? <p className="text-destructive text-xs">Config must be valid JSON; the last valid version is kept.</p> : null}
|
|
65
|
+
</CardContent>
|
|
66
|
+
</Card>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function AddStep({ onAdd }: { onAdd: (type: string) => void }) {
|
|
71
|
+
const catalog = useG8Query((signal) => g8Get("/workflows/node-types/schema", { detail: "slim" }, { signal }), []);
|
|
72
|
+
const [type, setType] = useState("");
|
|
73
|
+
const types = (Array.isArray(payload(catalog.data).node_types) ? (payload(catalog.data).node_types as Rec[]) : [])
|
|
74
|
+
.filter((n) => !NOT_ADDABLE.has(str(n.node_type)) && n.deprecated !== true);
|
|
75
|
+
return (
|
|
76
|
+
<div className="flex items-center gap-2">
|
|
77
|
+
<select aria-label="Step type" value={type} onChange={(e) => setType(e.target.value)} className="border-input bg-background h-9 rounded-md border px-2 text-sm">
|
|
78
|
+
<option value="">{catalog.loading ? "Loading step types..." : "Choose a step type"}</option>
|
|
79
|
+
{types.map((n) => <option key={str(n.node_type)} value={str(n.node_type)}>{str(n.node_type).replace(/_/g, " ")}</option>)}
|
|
80
|
+
</select>
|
|
81
|
+
<Button variant="outline" disabled={!type} onClick={() => { onAdd(type); setType(""); }}>Add step</Button>
|
|
82
|
+
{catalog.error ? <span className="text-xs">The step catalog could not be loaded.</span> : null}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function WorkflowEditor({ actionId, onSaved, className }: WorkflowEditorProps) {
|
|
88
|
+
const wf = useWorkflow(actionId);
|
|
89
|
+
// The draft remembers which workflow it belongs to, so switching `actionId` can never show or save the old one.
|
|
90
|
+
const [draftState, setDraftState] = useState<{ id: string; config: Rec } | null>(null);
|
|
91
|
+
const draft = draftState && draftState.id === actionId ? draftState.config : null;
|
|
92
|
+
const setDraft = (config: Rec | null) => setDraftState(config ? { id: actionId, config } : null);
|
|
93
|
+
const [confirm, setConfirm] = useState(false);
|
|
94
|
+
// Bumped on discard and after a save so StepCards re-read their config; typing never rewrites the textarea.
|
|
95
|
+
const [epoch, setEpoch] = useState(0);
|
|
96
|
+
const config = draft ?? wf.config;
|
|
97
|
+
|
|
98
|
+
if (wf.loading) return <div className={className} aria-busy="true"><Skeleton className="h-40 w-full" /></div>;
|
|
99
|
+
if (wf.error) return <ApiError error={wf.error} onRetry={() => void wf.refetch()} />;
|
|
100
|
+
|
|
101
|
+
const steps = orderedSteps(config);
|
|
102
|
+
const linear = isLinear(config);
|
|
103
|
+
const edit = (next: Rec) => setDraft(next);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className={className}>
|
|
107
|
+
<div className="mb-4 flex flex-wrap items-center gap-2">
|
|
108
|
+
<h2 className="text-lg font-semibold">{str(wf.workflow.name, "Workflow")}</h2>
|
|
109
|
+
<span className="text-muted-foreground text-sm">{wf.workflow.enabled === false ? "Disabled" : "Enabled"}</span>
|
|
110
|
+
<div className="ml-auto flex items-center gap-2">
|
|
111
|
+
{draft ? <span className="text-sm">Unsaved changes</span> : null}
|
|
112
|
+
<Button variant="outline" disabled={!draft} onClick={() => { setDraft(null); setEpoch((e) => e + 1); }}>Discard</Button>
|
|
113
|
+
<Button disabled={!draft} onClick={() => setConfirm(true)}>Save workflow</Button>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
{!linear ? <p className="text-muted-foreground mb-3 text-sm">This workflow branches, so steps can be renamed and reconfigured here but not added, moved or removed. Use the graph8 app to change its shape.</p> : null}
|
|
117
|
+
{wf.save.error ? <ApiError error={wf.save.error} title="Could not save the workflow" /> : null}
|
|
118
|
+
<div className="mb-4 space-y-3">
|
|
119
|
+
{steps.map((s, i) => (
|
|
120
|
+
<StepCard key={`${actionId}-${nodeIdOf(s)}-${epoch}`} step={s} index={i} canUp={linear && canMove(config, nodeIdOf(s), -1)} canDown={linear && canMove(config, nodeIdOf(s), 1)} linear={linear}
|
|
121
|
+
onChange={(patch) => edit(setStep(config, nodeIdOf(s), patch))}
|
|
122
|
+
onMove={(d) => edit(moveStep(config, nodeIdOf(s), d))}
|
|
123
|
+
onRemove={() => edit(removeStep(config, nodeIdOf(s)))} />
|
|
124
|
+
))}
|
|
125
|
+
</div>
|
|
126
|
+
{linear ? (
|
|
127
|
+
<AddStep onAdd={(type) => {
|
|
128
|
+
const afterEnd = steps.length > 0 && nodeTypeOf(steps[steps.length - 1]) === "end";
|
|
129
|
+
const after = afterEnd ? (steps.length > 1 ? nodeIdOf(steps[steps.length - 2]) : null) : undefined;
|
|
130
|
+
edit(addStep(config, newStep(config, type, type.replace(/_/g, " ")), after));
|
|
131
|
+
}} />
|
|
132
|
+
) : null}
|
|
133
|
+
{confirm ? (
|
|
134
|
+
<ConfirmSpendDialog open tier="external" title={`Save "${str(wf.workflow.name, "this workflow")}"`}
|
|
135
|
+
description={wf.workflow.enabled === false ? "The workflow is disabled, so saving does not start it." : "The workflow is enabled: new steps take effect on the next trigger and can reach real contacts."}
|
|
136
|
+
confirmLabel="Save" confirming={wf.save.loading}
|
|
137
|
+
onConfirm={() => void wf.saveAndRefresh({ config }).then((r) => { if (r) { setConfirm(false); setDraft(null); setEpoch((e) => e + 1); onSaved?.(); } else setConfirm(false); })}
|
|
138
|
+
onCancel={() => setConfirm(false)} />
|
|
139
|
+
) : null}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
}
|