1
1
import { API } from "api/api" ;
2
- import { getErrorDetail , getErrorMessage } from "api/errors" ;
2
+ import {
3
+ type ApiError ,
4
+ getErrorDetail ,
5
+ getErrorMessage ,
6
+ isApiError ,
7
+ } from "api/errors" ;
3
8
import { template as templateQueryOptions } from "api/queries/templates" ;
9
+ import { startWorkspace } from "api/queries/workspaces" ;
4
10
import type {
5
11
Workspace ,
6
12
WorkspaceAgent ,
7
13
WorkspaceStatus ,
8
14
} from "api/typesGenerated" ;
9
15
import isChromatic from "chromatic/isChromatic" ;
10
16
import { Button } from "components/Button/Button" ;
17
+ import { displayError } from "components/GlobalSnackbar/utils" ;
11
18
import { Loader } from "components/Loader/Loader" ;
12
19
import { Margins } from "components/Margins/Margins" ;
13
20
import { ScrollArea } from "components/ScrollArea/ScrollArea" ;
@@ -16,10 +23,17 @@ import { ArrowLeftIcon, RotateCcwIcon } from "lucide-react";
16
23
import { AgentLogs } from "modules/resources/AgentLogs/AgentLogs" ;
17
24
import { useAgentLogs } from "modules/resources/useAgentLogs" ;
18
25
import { AI_PROMPT_PARAMETER_NAME , type Task } from "modules/tasks/tasks" ;
26
+ import { WorkspaceErrorDialog } from "modules/workspaces/ErrorDialog/WorkspaceErrorDialog" ;
19
27
import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs" ;
20
- import { type FC , type ReactNode , useLayoutEffect , useRef } from "react" ;
28
+ import {
29
+ type FC ,
30
+ type ReactNode ,
31
+ useLayoutEffect ,
32
+ useRef ,
33
+ useState ,
34
+ } from "react" ;
21
35
import { Helmet } from "react-helmet-async" ;
22
- import { useQuery } from "react-query" ;
36
+ import { useMutation , useQuery , useQueryClient } from "react-query" ;
23
37
import { Panel , PanelGroup , PanelResizeHandle } from "react-resizable-panels" ;
24
38
import { Link as RouterLink , useParams } from "react-router" ;
25
39
import type { FixedSizeList } from "react-window" ;
@@ -119,27 +133,7 @@ const TaskPage = () => {
119
133
</ div >
120
134
) ;
121
135
} else if ( task . workspace . latest_build . status !== "running" ) {
122
- content = (
123
- < Margins >
124
- < div className = "w-full min-h-80 flex items-center justify-center" >
125
- < div className = "flex flex-col items-center" >
126
- < h3 className = "m-0 font-medium text-content-primary text-base" >
127
- Workspace is not running
128
- </ h3 >
129
- < span className = "text-content-secondary text-sm" >
130
- Apps and previous statuses are not available
131
- </ span >
132
- < Button size = "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
- ) ;
136
+ content = < WorkspaceNotRunning task = { task } /> ;
143
137
} else if ( agent && [ "created" , "starting" ] . includes ( agent . lifecycle_state ) ) {
144
138
content = < TaskStartingAgent agent = { agent } /> ;
145
139
} else {
@@ -174,6 +168,80 @@ const TaskPage = () => {
174
168
175
169
export default TaskPage ;
176
170
171
+ type WorkspaceNotRunningProps = {
172
+ task :Task ;
173
+ } ;
174
+
175
+ const WorkspaceNotRunning :FC < WorkspaceNotRunningProps > = ( { task} ) => {
176
+ const queryClient = useQueryClient ( ) ;
177
+
178
+ const { data :parameters } = useQuery ( {
179
+ queryKey :[ "workspace" , task . workspace . id , "parameters" ] ,
180
+ queryFn :( ) => API . getWorkspaceParameters ( task . workspace ) ,
181
+ } ) ;
182
+
183
+ const [ workspaceErrorDialog , setWorkspaceErrorDialog ] = useState < {
184
+ open :boolean ;
185
+ error ?:ApiError ;
186
+ } > ( { open :false } ) ;
187
+
188
+ const handleError = ( error :unknown ) => {
189
+ if ( isApiError ( error ) && error . code === "ERR_BAD_REQUEST" ) {
190
+ setWorkspaceErrorDialog ( {
191
+ open :true ,
192
+ error :error ,
193
+ } ) ;
194
+ } else {
195
+ displayError ( getErrorMessage ( error , "Failed to build workspace." ) ) ;
196
+ }
197
+ } ;
198
+
199
+ const mutateStartWorkspace = useMutation ( {
200
+ ...startWorkspace ( task ?. workspace , queryClient ) ,
201
+ onError :( error :unknown ) => {
202
+ handleError ( error ) ;
203
+ } ,
204
+ } ) ;
205
+
206
+ return (
207
+ < Margins >
208
+ < div className = "w-full min-h-80 flex items-center justify-center" >
209
+ < div className = "flex flex-col items-center" >
210
+ < h3 className = "m-0 font-medium text-content-primary text-base" >
211
+ Workspace is not running
212
+ </ h3 >
213
+ < span className = "text-content-secondary text-sm" >
214
+ Apps and previous statuses are not available
215
+ </ span >
216
+ < div className = "flex flex-row mt-4 gap-4" >
217
+ < Button
218
+ size = "sm"
219
+ onClick = { ( ) => {
220
+ mutateStartWorkspace . mutate ( {
221
+ buildParameters :parameters ?. buildParameters ,
222
+ } ) ;
223
+ } }
224
+ >
225
+ Start workspace
226
+ </ Button >
227
+ </ div >
228
+ </ div >
229
+ </ div >
230
+
231
+ < WorkspaceErrorDialog
232
+ open = { workspaceErrorDialog . open }
233
+ error = { workspaceErrorDialog . error }
234
+ onClose = { ( ) => setWorkspaceErrorDialog ( { open :false } ) }
235
+ showDetail = { task . workspace . template_use_classic_parameter_flow }
236
+ workspaceOwner = { task . workspace . owner_name }
237
+ workspaceName = { task . workspace . name }
238
+ templateVersionId = { task . workspace . latest_build . template_version_id }
239
+ isDeleting = { false }
240
+ />
241
+ </ Margins >
242
+ ) ;
243
+ } ;
244
+
177
245
type TaskBuildingWorkspaceProps = { task :Task } ;
178
246
179
247
const TaskBuildingWorkspace :FC < TaskBuildingWorkspaceProps > = ( { task} ) => {