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

feat: add better error display for workspace builds#18518

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
jaaydenh merged 2 commits intomainfromjaaydenh/workspace-build-errors
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes fromall 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
2 changes: 1 addition & 1 deletionsite/src/api/errors.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -19,7 +19,7 @@ export interface ApiErrorResponse {
validations?: FieldError[];
}

type ApiError = AxiosError<ApiErrorResponse> & {
exporttype ApiError = AxiosError<ApiErrorResponse> & {
response: AxiosResponse<ApiErrorResponse>;
};

Expand Down
43 changes: 30 additions & 13 deletionssite/src/components/Dialog/Dialog.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
* @see {@link https://ui.shadcn.com/docs/components/dialog}
*/
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { type VariantProps, cva } from "class-variance-authority";
import {
type ComponentPropsWithoutRef,
type ElementRef,
Expand DownExpand Up@@ -36,25 +37,41 @@ const DialogOverlay = forwardRef<
/>
));

const dialogVariants = cva(
`fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-6
border border-solid bg-surface-primary p-8 shadow-lg duration-200 sm:rounded-lg
translate-x-[-50%] translate-y-[-50%]
data-[state=open]:animate-in data-[state=closed]:animate-out
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`,
{
variants: {
variant: {
default: "border-border-primary",
destructive: "border-border-destructive",
},
},
defaultVariants: {
variant: "default",
},
},
);

interface DialogContentProps
extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content>,
VariantProps<typeof dialogVariants> {}

export const DialogContent = forwardRef<
ElementRef<typeof DialogPrimitive.Content>,
ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
DialogContentProps
>(({ className,variant,children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
`fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg gap-6
border border-solid border-border bg-surface-primary p-8 shadow-lg duration-200 sm:rounded-lg
translate-x-[-50%] translate-y-[-50%]
data-[state=open]:animate-in data-[state=closed]:animate-out
data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0
data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95
data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%]
data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%]`,
className,
)}
className={cn(dialogVariants({ variant }), className)}
{...props}
>
{children}
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
import { getErrorDetail, getErrorMessage, isApiError } from "api/errors";
import { Button } from "components/Button/Button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "components/Dialog/Dialog";
import type { FC } from "react";
import { useNavigate } from "react-router-dom";

interface WorkspaceErrorDialogProps {
open: boolean;
error?: unknown;
onClose: () => void;
showDetail: boolean;
workspaceOwner: string;
workspaceName: string;
templateVersionId: string;
}

export const WorkspaceErrorDialog: FC<WorkspaceErrorDialogProps> = ({
open,
error,
onClose,
showDetail,
workspaceOwner,
workspaceName,
templateVersionId,
}) => {
const navigate = useNavigate();

if (!error) {
return null;
}

const handleGoToParameters = () => {
onClose();
navigate(
`/@${workspaceOwner}/${workspaceName}/settings/parameters?templateVersionId=${templateVersionId}`,
);
};

const errorDetail = getErrorDetail(error);
const validations = isApiError(error)
? error.response.data.validations
: undefined;

return (
<Dialog open={open} onOpenChange={(isOpen) => !isOpen && onClose()}>
<DialogContent variant="destructive">
<DialogHeader>
<DialogTitle>Error building workspace</DialogTitle>
<DialogDescription className="flex flex-row gap-4">
<strong className="text-content-primary">Message</strong>{" "}
<span>{getErrorMessage(error, "Failed to build workspace.")}</span>
</DialogDescription>
{errorDetail && showDetail && (
<DialogDescription className="flex flex-row gap-9">
<strong className="text-content-primary">Detail</strong>{" "}
<span>{errorDetail}</span>
</DialogDescription>
)}
{validations && (
<DialogDescription className="flex flex-row gap-4">
<strong className="text-content-primary">Validations</strong>{" "}
<span>
{validations.map((validation) => validation.detail).join(", ")}
</span>
</DialogDescription>
)}
</DialogHeader>
<DialogFooter>
<Button onClick={handleGoToParameters}>
Review workspace settings
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
88 changes: 70 additions & 18 deletionssite/src/pages/WorkspacePage/WorkspaceReadyPage.tsx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
import { API } from "api/api";
import { getErrorMessage } from "api/errors";
import { type ApiError, getErrorMessage } from "api/errors";
import { isApiError } from "api/errors";
import { templateVersion } from "api/queries/templates";
import { workspaceBuildTimings } from "api/queries/workspaceBuilds";
import {
Expand All@@ -15,9 +16,10 @@ import {
ConfirmDialog,
type ConfirmDialogProps,
} from "components/Dialogs/ConfirmDialog/ConfirmDialog";
import { EphemeralParametersDialog } from "components/EphemeralParametersDialog/EphemeralParametersDialog";
import { displayError } from "components/GlobalSnackbar/utils";
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
import { EphemeralParametersDialog } from "modules/workspaces/EphemeralParametersDialog/EphemeralParametersDialog";
import { WorkspaceErrorDialog } from "modules/workspaces/ErrorDialog/WorkspaceErrorDialog";
import {
WorkspaceUpdateDialogs,
useWorkspaceUpdate,
Expand DownExpand Up@@ -55,15 +57,35 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
buildParameters?: TypesGen.WorkspaceBuildParameter[];
}>({ open: false });

const [workspaceErrorDialog, setWorkspaceErrorDialog] = useState<{
open: boolean;
error?: ApiError;
}>({ open: false });

const handleError = (error: unknown) => {
if (isApiError(error) && error.code === "ERR_BAD_REQUEST") {
setWorkspaceErrorDialog({
open: true,
error: error,
});
} else {
displayError(getErrorMessage(error, "Failed to build workspace."));
}
};

const [ephemeralParametersDialog, setEphemeralParametersDialog] = useState<{
open: boolean;
action: "start" | "restart";
buildParameters?: TypesGen.WorkspaceBuildParameter[];
ephemeralParameters: TypesGen.TemplateVersionParameter[];
}>({ open: false, action: "start", ephemeralParameters: [] });

const { mutate: mutateRestartWorkspace, isPending: isRestarting } =
useMutation({
mutationFn: API.restartWorkspace,
onError: (error: unknown) => {
handleError(error);
},
});

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

// Delete workspace
const deleteWorkspaceMutation = useMutation(
deleteWorkspace(workspace, queryClient),
);
const deleteWorkspaceMutation = useMutation({
...deleteWorkspace(workspace, queryClient),
onError: (error: unknown) => {
handleError(error);
},
});

// Activate workspace
const activateWorkspaceMutation = useMutation(
activate(workspace, queryClient),
);
const activateWorkspaceMutation = useMutation({
...activate(workspace, queryClient),
onError: (error: unknown) => {
handleError(error);
},
});

// Stop workspace
const stopWorkspaceMutation = useMutation(
stopWorkspace(workspace, queryClient),
);
const stopWorkspaceMutation = useMutation({
...stopWorkspace(workspace, queryClient),
onError: (error: unknown) => {
handleError(error);
},
});

// Start workspace
const startWorkspaceMutation = useMutation(
startWorkspace(workspace, queryClient),
);
const startWorkspaceMutation = useMutation({
...startWorkspace(workspace, queryClient),
onError: (error: unknown) => {
handleError(error);
},
});

// Toggle workspace favorite
const toggleFavoriteMutation = useMutation(
toggleFavorite(workspace, queryClient),
);
const toggleFavoriteMutation = useMutation({
...toggleFavorite(workspace, queryClient),
onError: (error: unknown) => {
handleError(error);
},
});

// Cancel build
const cancelBuildMutation = useMutation(cancelBuild(workspace, queryClient));
const cancelBuildMutation = useMutation({
...cancelBuild(workspace, queryClient),
onError: (error: unknown) => {
handleError(error);
},
});

// Workspace Timings.
const timingsQuery = useQuery({
Expand DownExpand Up@@ -341,6 +383,16 @@ export const WorkspaceReadyPage: FC<WorkspaceReadyPageProps> = ({
/>

<WorkspaceUpdateDialogs {...workspaceUpdate.dialogs} />

<WorkspaceErrorDialog
open={workspaceErrorDialog.open}
error={workspaceErrorDialog.error}
onClose={() => setWorkspaceErrorDialog({ open: false })}
showDetail={workspace.template_use_classic_parameter_flow}
workspaceOwner={workspace.owner_name}
workspaceName={workspace.name}
templateVersionId={workspace.latest_build.template_version_id}
/>
</>
);
};
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp