Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitb71d671

Browse files
feat(site): allow starting task workspace from task page (#19790)
Closes#19622When viewing a task with a stopped workspace, instead show a 'Startworkspace' button instead of a 'View workspace'. The user can still viewthe workspace by clicking the workspace button at the top right of thepage.https://github.com/user-attachments/assets/4424c251-5f20-4e82-9ee0-c87a0b30a193
1 parentfb0ce38 commitb71d671

File tree

2 files changed

+195
-25
lines changed

2 files changed

+195
-25
lines changed

‎site/src/pages/TaskPage/TaskPage.stories.tsx‎

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,20 @@ import {
1111
MockWorkspaceResource,
1212
mockApiError,
1313
}from"testHelpers/entities";
14-
import{withProxyProvider,withWebSocket}from"testHelpers/storybook";
14+
import{
15+
withGlobalSnackbar,
16+
withProxyProvider,
17+
withWebSocket,
18+
}from"testHelpers/storybook";
1519
importtype{Meta,StoryObj}from"@storybook/react-vite";
20+
import{API}from"api/api";
1621
importtype{
1722
Workspace,
1823
WorkspaceApp,
1924
WorkspaceResource,
2025
}from"api/typesGenerated";
21-
import{expect,spyOn,within}from"storybook/test";
26+
import{expect,spyOn,userEvent,waitFor,within}from"storybook/test";
27+
import{reactRouterParameters}from"storybook-addon-remix-react-router";
2228
importTaskPage,{data,WorkspaceDoesNotHaveAITaskError}from"./TaskPage";
2329

2430
constmeta:Meta<typeofTaskPage>={
@@ -351,3 +357,117 @@ export const ActivePreview: Story = {
351357
});
352358
},
353359
};
360+
361+
exportconstWorkspaceStartFailure:Story={
362+
decorators:[withGlobalSnackbar],
363+
beforeEach:()=>{
364+
spyOn(API,"startWorkspace").mockRejectedValue(
365+
newError("Some unexpected error"),
366+
);
367+
},
368+
parameters:{
369+
reactRouter:reactRouterParameters({
370+
location:{
371+
pathParams:{
372+
username:MockStoppedWorkspace.owner_name,
373+
workspace:MockStoppedWorkspace.name,
374+
},
375+
},
376+
routing:{
377+
path:"/tasks/:username/:workspace",
378+
},
379+
}),
380+
queries:[
381+
{
382+
key:[
383+
"tasks",
384+
MockStoppedWorkspace.owner_name,
385+
MockStoppedWorkspace.name,
386+
],
387+
data:{
388+
prompt:"Create competitors page",
389+
workspace:MockStoppedWorkspace,
390+
},
391+
},
392+
{
393+
key:["workspace",MockStoppedWorkspace.id,"parameters"],
394+
data:{
395+
templateVersionRichParameters:[],
396+
buildParameters:[],
397+
},
398+
},
399+
],
400+
},
401+
play:async({ canvasElement})=>{
402+
constcanvas=within(canvasElement);
403+
404+
conststartButton=awaitcanvas.findByText("Start workspace");
405+
expect(startButton).toBeInTheDocument();
406+
407+
awaituserEvent.click(startButton);
408+
409+
awaitwaitFor(async()=>{
410+
consterrorMessage=awaitcanvas.findByText("Some unexpected error");
411+
expect(errorMessage).toBeInTheDocument();
412+
});
413+
},
414+
};
415+
416+
exportconstWorkspaceStartFailureWithDialog:Story={
417+
beforeEach:()=>{
418+
spyOn(API,"startWorkspace").mockRejectedValue({
419+
...mockApiError({
420+
message:"Bad Request",
421+
detail:"Invalid build parameters provided",
422+
}),
423+
code:"ERR_BAD_REQUEST",
424+
});
425+
},
426+
parameters:{
427+
reactRouter:reactRouterParameters({
428+
location:{
429+
pathParams:{
430+
username:MockStoppedWorkspace.owner_name,
431+
workspace:MockStoppedWorkspace.name,
432+
},
433+
},
434+
routing:{
435+
path:"/tasks/:username/:workspace",
436+
},
437+
}),
438+
queries:[
439+
{
440+
key:[
441+
"tasks",
442+
MockStoppedWorkspace.owner_name,
443+
MockStoppedWorkspace.name,
444+
],
445+
data:{
446+
prompt:"Create competitors page",
447+
workspace:MockStoppedWorkspace,
448+
},
449+
},
450+
{
451+
key:["workspace",MockStoppedWorkspace.id,"parameters"],
452+
data:{
453+
templateVersionRichParameters:[],
454+
buildParameters:[],
455+
},
456+
},
457+
],
458+
},
459+
play:async({ canvasElement})=>{
460+
constcanvas=within(canvasElement);
461+
462+
conststartButton=awaitcanvas.findByText("Start workspace");
463+
expect(startButton).toBeInTheDocument();
464+
465+
awaituserEvent.click(startButton);
466+
467+
awaitwaitFor(async()=>{
468+
constbody=within(canvasElement.ownerDocument.body);
469+
constdialogTitle=awaitbody.findByText("Error building workspace");
470+
expect(dialogTitle).toBeInTheDocument();
471+
});
472+
},
473+
};

‎site/src/pages/TaskPage/TaskPage.tsx‎

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import{API}from"api/api";
2-
import{getErrorDetail,getErrorMessage}from"api/errors";
2+
import{getErrorDetail,getErrorMessage,isApiError}from"api/errors";
33
import{templateastemplateQueryOptions}from"api/queries/templates";
4+
import{startWorkspace}from"api/queries/workspaces";
45
importtype{
56
Workspace,
67
WorkspaceAgent,
78
WorkspaceStatus,
89
}from"api/typesGenerated";
910
importisChromaticfrom"chromatic/isChromatic";
1011
import{Button}from"components/Button/Button";
12+
import{displayError}from"components/GlobalSnackbar/utils";
1113
import{Loader}from"components/Loader/Loader";
1214
import{Margins}from"components/Margins/Margins";
1315
import{ScrollArea}from"components/ScrollArea/ScrollArea";
@@ -16,10 +18,11 @@ import { ArrowLeftIcon, RotateCcwIcon } from "lucide-react";
1618
import{AgentLogs}from"modules/resources/AgentLogs/AgentLogs";
1719
import{useAgentLogs}from"modules/resources/useAgentLogs";
1820
import{AI_PROMPT_PARAMETER_NAME,typeTask}from"modules/tasks/tasks";
21+
import{WorkspaceErrorDialog}from"modules/workspaces/ErrorDialog/WorkspaceErrorDialog";
1922
import{WorkspaceBuildLogs}from"modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs";
2023
import{typeFC,typeReactNode,useLayoutEffect,useRef}from"react";
2124
import{Helmet}from"react-helmet-async";
22-
import{useQuery}from"react-query";
25+
import{useMutation,useQuery,useQueryClient}from"react-query";
2326
import{Panel,PanelGroup,PanelResizeHandle}from"react-resizable-panels";
2427
import{LinkasRouterLink,useParams}from"react-router";
2528
importtype{FixedSizeList}from"react-window";
@@ -119,27 +122,7 @@ const TaskPage = () => {
119122
</div>
120123
);
121124
}elseif(task.workspace.latest_build.status!=="running"){
122-
content=(
123-
<Margins>
124-
<divclassName="w-full min-h-80 flex items-center justify-center">
125-
<divclassName="flex flex-col items-center">
126-
<h3className="m-0 font-medium text-content-primary text-base">
127-
Workspace is not running
128-
</h3>
129-
<spanclassName="text-content-secondary text-sm">
130-
Apps and previous statuses are not available
131-
</span>
132-
<Buttonsize="sm"className="mt-4"asChild>
133-
<RouterLink
134-
to={`/@${task.workspace.owner_name}/${task.workspace.name}`}
135-
>
136-
View workspace
137-
</RouterLink>
138-
</Button>
139-
</div>
140-
</div>
141-
</Margins>
142-
);
125+
content=<WorkspaceNotRunningtask={task}/>;
143126
}elseif(agent&&["created","starting"].includes(agent.lifecycle_state)){
144127
content=<TaskStartingAgentagent={agent}/>;
145128
}else{
@@ -174,6 +157,73 @@ const TaskPage = () => {
174157

175158
exportdefaultTaskPage;
176159

160+
typeWorkspaceNotRunningProps={
161+
task:Task;
162+
};
163+
164+
constWorkspaceNotRunning:FC<WorkspaceNotRunningProps>=({ task})=>{
165+
constqueryClient=useQueryClient();
166+
167+
const{data:parameters}=useQuery({
168+
queryKey:["workspace",task.workspace.id,"parameters"],
169+
queryFn:()=>API.getWorkspaceParameters(task.workspace),
170+
});
171+
172+
constmutateStartWorkspace=useMutation({
173+
...startWorkspace(task?.workspace,queryClient),
174+
onError:(error:unknown)=>{
175+
if(!isApiError(error)){
176+
displayError(getErrorMessage(error,"Failed to build workspace."));
177+
}
178+
},
179+
});
180+
181+
constapiError=isApiError(mutateStartWorkspace.error)
182+
?mutateStartWorkspace.error
183+
:undefined;
184+
185+
return(
186+
<Margins>
187+
<divclassName="w-full min-h-80 flex items-center justify-center">
188+
<divclassName="flex flex-col items-center">
189+
<h3className="m-0 font-medium text-content-primary text-base">
190+
Workspace is not running
191+
</h3>
192+
<spanclassName="text-content-secondary text-sm">
193+
Apps and previous statuses are not available
194+
</span>
195+
<divclassName="flex flex-row mt-4 gap-4">
196+
<Button
197+
size="sm"
198+
disabled={mutateStartWorkspace.isPending}
199+
onClick={()=>{
200+
mutateStartWorkspace.mutate({
201+
buildParameters:parameters?.buildParameters,
202+
});
203+
}}
204+
>
205+
{mutateStartWorkspace.isPending
206+
?"Starting workspace..."
207+
:"Start workspace"}
208+
</Button>
209+
</div>
210+
</div>
211+
</div>
212+
213+
<WorkspaceErrorDialog
214+
open={apiError!==undefined}
215+
error={apiError}
216+
onClose={mutateStartWorkspace.reset}
217+
showDetail={true}
218+
workspaceOwner={task.workspace.owner_name}
219+
workspaceName={task.workspace.name}
220+
templateVersionId={task.workspace.latest_build.template_version_id}
221+
isDeleting={false}
222+
/>
223+
</Margins>
224+
);
225+
};
226+
177227
typeTaskBuildingWorkspaceProps={task:Task};
178228

179229
constTaskBuildingWorkspace:FC<TaskBuildingWorkspaceProps>=({ task})=>{

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp