Skip to content

v2: triggers UI, team-owned workflow creation, role-aware teams panel - #215

Closed
xBalbinus wants to merge 1 commit into
dev-v2from
feat/v2-team-triggers-ui
Closed

v2: triggers UI, team-owned workflow creation, role-aware teams panel#215
xBalbinus wants to merge 1 commit into
dev-v2from
feat/v2-team-triggers-ui

Conversation

@xBalbinus

Copy link
Copy Markdown

Summary

Three V2 surface gaps closed in one pass — thin UI, plus two thin routes, over enforcement the API already has:

  • Triggers. The workflow editor gains a Triggers drawer: mint, copy, rotate, and delete the webhook trigger URL, and manage cron schedules. Schedules had a complete service (schedule-service.ts, swept by the scheduler) but no HTTP surface — this adds GET/POST /api/workflows/:id/schedules and DELETE /api/workflows/:id/schedules/:scheduleId over it. The routes resolve the workflow through getWorkflowDefinition first (own-rows 404 convention), and a delete requires the schedule to belong to that workflow, so one workflow's surface cannot remove another's rows.
  • Team-owned workflows. CreateWorkflowRequest.teamId existed on the wire with membership checks in the API, but no UI offered it. The New-workflow dialog gains an owner picker (you, or a team you belong to), and the workflows list badges team-owned rows with the team name.
  • Role-aware teams panel. TeamSummary gains callerRole ("admin" | "member" | null — null is an org admin viewing a team they are not on). The teams settings panel now hides mutation controls (delete team, add/remove member, role change) from callers the API's canMutateTeam gate would 404 anyway. No enforcement changed; the API checks stay authoritative.

The spec addendum is in docs/specs/2026-07-16-workflows-overhaul-design.md.

Verification

API and web suites pass (1966 api / 593 web). Exercised live on the dev stack: created a team, created a team-owned workflow through the dialog (ownerType: team confirmed server-side), minted the webhook URL from the drawer and started a run by POSTing to that exact URL (run settled), and created a schedule (correct next-fire in America/New_York) that the drawer lists.

@valet-valet-turnkey-dev valet-valet-turnkey-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The PR is well-structured and the implementation matches the stated intent. One correctness issue in the DELETE schedule route and one potential UX race condition are worth addressing before merge.

  1. packages/api/src/routes/workflows.ts:325 — schedule ownership check is read-then-delete, not atomic, and leaks the schedule's existence across orgs

    The DELETE handler calls listWorkflowSchedules(deps.db, owner.orgId, id) to verify the schedule belongs to the workflow, then calls deleteWorkflowSchedule(deps.db, owner.orgId, scheduleId) separately. deleteWorkflowSchedule is org-scoped but not workflow-scoped — if the schedule was reassigned between the list and delete (unlikely but possible under concurrent requests), the invariant breaks. More importantly, the list filters by workflowId, so a schedule belonging to a different workflow in the same org would correctly 404 here — but deleteWorkflowSchedule would still delete it if it somehow received a stale scheduleId. The cleaner fix is for deleteWorkflowSchedule to accept an optional workflowId constraint and apply it in the SQL WHERE, making the check atomic. As written the window is small but the belt-and-suspenders comment in the PR description implies this was meant to be air-tight.

  2. packages/web/src/components/workflows/triggers-drawer.tsx:66window.location.origin in a SSR-friendly codebase

    • The webhook URL is constructed as `${window.location.origin}/api/hooks/…`. If this component is ever rendered server-side (or in tests without jsdom), this will throw ReferenceError: window is not defined. The test file uses // @vitest-environment jsdom so tests pass, but if the route is SSR-rendered before hydration, it will blow up. A safe guard would be typeof window !== "undefined" ? window.location.origin : "" or pulling the origin from a config/env variable consistent with how the API base URL is resolved elsewhere in client.ts.
  3. packages/web/src/components/workflows/new-workflow-dialog.tsx:97 — the owner picker shows all org teams, not just teams the caller belongs to

    The GET /api/teams endpoint for a non-admin returns only the caller's own teams (listTeamsForUser), but for an org admin it returns every team in the org (line 155 of teams.ts). So an org admin who is not a member of a team will see it in the picker and can attempt to create a workflow owned by that team — the API's membership check will then reject it with a 404/403. This is a usability gap: the picker should filter to teams.data?.teams.filter(t => t.callerRole !== null) since callerRole === null means "you're an org admin viewing a team you're not on." The callerRole field was added specifically to enable this kind of gate.


Created on behalf of Xiangan He xiangan@turnkey.io

@xBalbinus

Copy link
Copy Markdown
Author

Superseded by #216, which carries this diff plus #214 and a shared-component pass.

@xBalbinus xBalbinus closed this Aug 11, 2026
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