Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions frontend/src/features/workflows/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,10 +574,9 @@ export interface CreateWorkflowRunInput {
/**
* The Project the run's Workspace provisions in. The contract requires it — a run that names
* none is refused with 409 `workflow_run_project_required` — so the mutation refuses a caller
* that has none instead of sending a body the contract does not describe. Optional here only
* because the run form that picks a Project is not built yet.
* that has none instead of sending a body the contract does not describe.
*/
projectId?: string
projectId: string
/** Optional display name; defaults to the workflow's name. */
name?: string
/** The exact snapshot to execute, or omit to pin the latest published one. */
Expand All @@ -597,8 +596,8 @@ export interface CreateWorkflowRunInput {
* minting a second execution.
*
* The run's Project is required by the contract — a run that names none is refused with 409
* `workflow_run_project_required` — and this editor states none yet: the run form that picks a
* Project is not built, so a run started here is refused until that lands.
* `workflow_run_project_required` — and the run form cannot submit before one is chosen, so the
* refusal here is the backstop for a caller that builds its own request.
*
* @param tenantId - Tenant the workflow belongs to.
* @returns The create mutation; `data` is the stored run.
Expand All @@ -613,7 +612,7 @@ export function useCreateWorkflowRun(tenantId: string | undefined) {
// aggregate that can never provision the Workspace its agent nodes need, so the service
// answers this same code (409) and there is no value the client could put in the field
// instead — the caller either names a Project or names none.
if (input.projectId === undefined || input.projectId === '') {
if (input.projectId === '') {
throw new Error('workflow_run_project_required')
}
return postApiV1TenantsTidWorkflowsWfidRuns(
Expand Down
31 changes: 24 additions & 7 deletions frontend/src/features/workflows/editor/workflow-editor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { Workflow, WorkflowRun, WorkflowSnapshot } from '@/api/generated.schemas'
import { createDefaultWorkflowCapabilities } from '@/features/workflows/runtime/capabilities'
import { WorkflowEditor } from '@/features/workflows/editor/workflow-editor'
import { installCloudSpaceHandlers, TEST_TENANT_ID, workflowFixture } from '@/test/cloud-handlers'
import {
TEST_PROJECT_ID,
TEST_TENANT_ID,
chooseProject,
installCloudSpaceHandlers,
installSpaceProjectHandlers,
workflowFixture,
} from '@/test/cloud-handlers'
import { server } from '@/test/msw-server'
import { stubViewportGeometry } from '@/test/viewport'
import { renderWithProviders } from '@/test/render'
Expand Down Expand Up @@ -201,8 +208,12 @@ function renderEditor({
),
)
installCloudSpaceHandlers('admin')
// The run dialog lists the space's Projects, and the editor mounts in the `cloud-dev` space the
// fixtures declare, so the space's Project list has to answer before a run can name one.
installSpaceProjectHandlers()
renderWithProviders(
<WorkflowEditor tenantId={TEST_TENANT_ID} workflow={workflowFixture({ graph })} />,
{ slug: 'cloud-dev' },
)
return bodies
}
Expand Down Expand Up @@ -1022,7 +1033,7 @@ describe('WorkflowEditor run tools', () => {
expect(screen.getByText('还没有运行记录。')).toBeInTheDocument()
})

it('starts no run from the toolbar, which cannot state the Project a run freezes', async () => {
it('starts the published version in the Project the member picks', async () => {
const published: WorkflowSnapshot = {
id: 'snap-1',
tenantId: TEST_TENANT_ID,
Expand All @@ -1047,10 +1058,16 @@ describe('WorkflowEditor run tools', () => {
}),
)
fireEvent.click(runButton)

// The run form that picks the Project is not built yet, so the mutation refuses the run before
// the request is sent and this capture stays empty.
await waitFor(() => expect(runButton).toBeEnabled())
expect(bodies).toEqual([])
// The toolbar button opens the start dialog; the run is created by its own submit, once the
// member has seen — and answered — what the published version asks for. The Project is one of
// those answers: nothing else could name where the run's Workspace provisions.
await chooseProject()
fireEvent.click(await screen.findByRole('button', { name: '开始运行' }))

// The version's Start node declares nothing, so there is nothing to answer and the run carries
// the empty input the server reads as "use this version's own defaults".
await waitFor(() =>
expect(bodies).toEqual([{ projectId: TEST_PROJECT_ID, snapshotId: 'snap-1', input: {} }]),
)
})
})
Loading
Loading