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

Commit31d0c6f

Browse files
authored
feat: add better error display for workspace builds (#18518)
Classic parameters templates<img width="548" alt="Screenshot 2025-06-23 at 23 27 46"src="https://github.com/user-attachments/assets/e8e774bf-e201-4a80-a90c-3d6cc3658c20"/>Dynamic parameters templates<img width="541" alt="Screenshot 2025-06-23 at 23 52 05"src="https://github.com/user-attachments/assets/6a40f144-c0b2-4e16-8137-d31a52b71460"/>
1 parentbca5c35 commit31d0c6f

File tree

5 files changed

+184
-32
lines changed

5 files changed

+184
-32
lines changed

‎site/src/api/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export interface ApiErrorResponse {
1919
validations?:FieldError[];
2020
}
2121

22-
typeApiError=AxiosError<ApiErrorResponse>&{
22+
exporttypeApiError=AxiosError<ApiErrorResponse>&{
2323
response:AxiosResponse<ApiErrorResponse>;
2424
};
2525

‎site/src/components/Dialog/Dialog.tsx

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
*@see {@link https://ui.shadcn.com/docs/components/dialog}
44
*/
55
import*asDialogPrimitivefrom"@radix-ui/react-dialog";
6+
import{typeVariantProps,cva}from"class-variance-authority";
67
import{
78
typeComponentPropsWithoutRef,
89
typeElementRef,
@@ -36,25 +37,41 @@ const DialogOverlay = forwardRef<
3637
/>
3738
));
3839

40+
constdialogVariants=cva(
41+
`fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-6
42+
border border-solid bg-surface-primary p-8 shadow-lg duration-200 sm:rounded-lg
43+
translate-x-[-50%] translate-y-[-50%]
44+
data-[state=open]:animate-in data-[state=closed]:animate-out
45+
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
46+
data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
47+
data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
48+
data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`,
49+
{
50+
variants:{
51+
variant:{
52+
default:"border-border-primary",
53+
destructive:"border-border-destructive",
54+
},
55+
},
56+
defaultVariants:{
57+
variant:"default",
58+
},
59+
},
60+
);
61+
62+
interfaceDialogContentProps
63+
extendsComponentPropsWithoutRef<typeofDialogPrimitive.Content>,
64+
VariantProps<typeofdialogVariants>{}
65+
3966
exportconstDialogContent=forwardRef<
4067
ElementRef<typeofDialogPrimitive.Content>,
41-
ComponentPropsWithoutRef<typeofDialogPrimitive.Content>
42-
>(({ className, children, ...props},ref)=>(
68+
DialogContentProps
69+
>(({ className,variant,children, ...props},ref)=>(
4370
<DialogPortal>
4471
<DialogOverlay/>
4572
<DialogPrimitive.Content
4673
ref={ref}
47-
className={cn(
48-
`fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-6
49-
border border-solid border-border bg-surface-primary p-8 shadow-lg duration-200 sm:rounded-lg
50-
translate-x-[-50%] translate-y-[-50%]
51-
data-[state=open]:animate-in data-[state=closed]:animate-out
52-
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
53-
data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
54-
data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
55-
data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`,
56-
className,
57-
)}
74+
className={cn(dialogVariants({ variant}),className)}
5875
{...props}
5976
>
6077
{children}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import{getErrorDetail,getErrorMessage,isApiError}from"api/errors";
2+
import{Button}from"components/Button/Button";
3+
import{
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
}from"components/Dialog/Dialog";
11+
importtype{FC}from"react";
12+
import{useNavigate}from"react-router-dom";
13+
14+
interfaceWorkspaceErrorDialogProps{
15+
open:boolean;
16+
error?:unknown;
17+
onClose:()=>void;
18+
showDetail:boolean;
19+
workspaceOwner:string;
20+
workspaceName:string;
21+
templateVersionId:string;
22+
}
23+
24+
exportconstWorkspaceErrorDialog:FC<WorkspaceErrorDialogProps>=({
25+
open,
26+
error,
27+
onClose,
28+
showDetail,
29+
workspaceOwner,
30+
workspaceName,
31+
templateVersionId,
32+
})=>{
33+
constnavigate=useNavigate();
34+
35+
if(!error){
36+
returnnull;
37+
}
38+
39+
consthandleGoToParameters=()=>{
40+
onClose();
41+
navigate(
42+
`/@${workspaceOwner}/${workspaceName}/settings/parameters?templateVersionId=${templateVersionId}`,
43+
);
44+
};
45+
46+
consterrorDetail=getErrorDetail(error);
47+
constvalidations=isApiError(error)
48+
?error.response.data.validations
49+
:undefined;
50+
51+
return(
52+
<Dialogopen={open}onOpenChange={(isOpen)=>!isOpen&&onClose()}>
53+
<DialogContentvariant="destructive">
54+
<DialogHeader>
55+
<DialogTitle>Error building workspace</DialogTitle>
56+
<DialogDescriptionclassName="flex flex-row gap-4">
57+
<strongclassName="text-content-primary">Message</strong>{" "}
58+
<span>{getErrorMessage(error,"Failed to build workspace.")}</span>
59+
</DialogDescription>
60+
{errorDetail&&showDetail&&(
61+
<DialogDescriptionclassName="flex flex-row gap-9">
62+
<strongclassName="text-content-primary">Detail</strong>{" "}
63+
<span>{errorDetail}</span>
64+
</DialogDescription>
65+
)}
66+
{validations&&(
67+
<DialogDescriptionclassName="flex flex-row gap-4">
68+
<strongclassName="text-content-primary">Validations</strong>{" "}
69+
<span>
70+
{validations.map((validation)=>validation.detail).join(", ")}
71+
</span>
72+
</DialogDescription>
73+
)}
74+
</DialogHeader>
75+
<DialogFooter>
76+
<ButtononClick={handleGoToParameters}>
77+
Review workspace settings
78+
</Button>
79+
</DialogFooter>
80+
</DialogContent>
81+
</Dialog>
82+
);
83+
};

‎site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import{API}from"api/api";
2-
import{getErrorMessage}from"api/errors";
2+
import{typeApiError,getErrorMessage}from"api/errors";
3+
import{isApiError}from"api/errors";
34
import{templateVersion}from"api/queries/templates";
45
import{workspaceBuildTimings}from"api/queries/workspaceBuilds";
56
import{
@@ -15,9 +16,10 @@ import {
1516
ConfirmDialog,
1617
typeConfirmDialogProps,
1718
}from"components/Dialogs/ConfirmDialog/ConfirmDialog";
18-
import{EphemeralParametersDialog}from"components/EphemeralParametersDialog/EphemeralParametersDialog";
1919
import{displayError}from"components/GlobalSnackbar/utils";
2020
import{useWorkspaceBuildLogs}from"hooks/useWorkspaceBuildLogs";
21+
import{EphemeralParametersDialog}from"modules/workspaces/EphemeralParametersDialog/EphemeralParametersDialog";
22+
import{WorkspaceErrorDialog}from"modules/workspaces/ErrorDialog/WorkspaceErrorDialog";
2123
import{
2224
WorkspaceUpdateDialogs,
2325
useWorkspaceUpdate,
@@ -55,15 +57,35 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
5557
buildParameters?:TypesGen.WorkspaceBuildParameter[];
5658
}>({open:false});
5759

60+
const[workspaceErrorDialog,setWorkspaceErrorDialog]=useState<{
61+
open:boolean;
62+
error?:ApiError;
63+
}>({open:false});
64+
65+
consthandleError=(error:unknown)=>{
66+
if(isApiError(error)&&error.code==="ERR_BAD_REQUEST"){
67+
setWorkspaceErrorDialog({
68+
open:true,
69+
error:error,
70+
});
71+
}else{
72+
displayError(getErrorMessage(error,"Failed to build workspace."));
73+
}
74+
};
75+
5876
const[ephemeralParametersDialog,setEphemeralParametersDialog]=useState<{
5977
open:boolean;
6078
action:"start"|"restart";
6179
buildParameters?:TypesGen.WorkspaceBuildParameter[];
6280
ephemeralParameters:TypesGen.TemplateVersionParameter[];
6381
}>({open:false,action:"start",ephemeralParameters:[]});
82+
6483
const{mutate:mutateRestartWorkspace,isPending:isRestarting}=
6584
useMutation({
6685
mutationFn:API.restartWorkspace,
86+
onError:(error:unknown)=>{
87+
handleError(error);
88+
},
6789
});
6890

6991
// Favicon
@@ -92,32 +114,52 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
92114
});
93115

94116
// Delete workspace
95-
constdeleteWorkspaceMutation=useMutation(
96-
deleteWorkspace(workspace,queryClient),
97-
);
117+
constdeleteWorkspaceMutation=useMutation({
118+
...deleteWorkspace(workspace,queryClient),
119+
onError:(error:unknown)=>{
120+
handleError(error);
121+
},
122+
});
98123

99124
// Activate workspace
100-
constactivateWorkspaceMutation=useMutation(
101-
activate(workspace,queryClient),
102-
);
125+
constactivateWorkspaceMutation=useMutation({
126+
...activate(workspace,queryClient),
127+
onError:(error:unknown)=>{
128+
handleError(error);
129+
},
130+
});
103131

104132
// Stop workspace
105-
conststopWorkspaceMutation=useMutation(
106-
stopWorkspace(workspace,queryClient),
107-
);
133+
conststopWorkspaceMutation=useMutation({
134+
...stopWorkspace(workspace,queryClient),
135+
onError:(error:unknown)=>{
136+
handleError(error);
137+
},
138+
});
108139

109140
// Start workspace
110-
conststartWorkspaceMutation=useMutation(
111-
startWorkspace(workspace,queryClient),
112-
);
141+
conststartWorkspaceMutation=useMutation({
142+
...startWorkspace(workspace,queryClient),
143+
onError:(error:unknown)=>{
144+
handleError(error);
145+
},
146+
});
113147

114148
// Toggle workspace favorite
115-
consttoggleFavoriteMutation=useMutation(
116-
toggleFavorite(workspace,queryClient),
117-
);
149+
consttoggleFavoriteMutation=useMutation({
150+
...toggleFavorite(workspace,queryClient),
151+
onError:(error:unknown)=>{
152+
handleError(error);
153+
},
154+
});
118155

119156
// Cancel build
120-
constcancelBuildMutation=useMutation(cancelBuild(workspace,queryClient));
157+
constcancelBuildMutation=useMutation({
158+
...cancelBuild(workspace,queryClient),
159+
onError:(error:unknown)=>{
160+
handleError(error);
161+
},
162+
});
121163

122164
// Workspace Timings.
123165
consttimingsQuery=useQuery({
@@ -341,6 +383,16 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
341383
/>
342384

343385
<WorkspaceUpdateDialogs{...workspaceUpdate.dialogs}/>
386+
387+
<WorkspaceErrorDialog
388+
open={workspaceErrorDialog.open}
389+
error={workspaceErrorDialog.error}
390+
onClose={()=>setWorkspaceErrorDialog({open:false})}
391+
showDetail={workspace.template_use_classic_parameter_flow}
392+
workspaceOwner={workspace.owner_name}
393+
workspaceName={workspace.name}
394+
templateVersionId={workspace.latest_build.template_version_id}
395+
/>
344396
</>
345397
);
346398
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp