Skip to content

feat: Deploy errors #598

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 12, 2025
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
59 changes: 59 additions & 0 deletions packages/renderer/src/components/ExpandMore/component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { useState } from 'react';
import { Collapse, IconButton, Typography, Box } from '@mui/material';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';

import type { Props } from './types';

import './styles.css';

export function ExpandMore({ title, text }: Props) {
const [expanded, setExpanded] = useState(false);

const handleToggle = () => {
setExpanded(!expanded);
};

return (
<Box className="ExpandMore">
<Box
display="flex"
alignItems="center"
>
<IconButton
onClick={handleToggle}
aria-expanded={expanded}
aria-label={title || 'show more'}
sx={{
transform: !expanded ? 'rotate(-90deg)' : 'rotate(0deg)',
transition: theme =>
theme.transitions.create('transform', {
duration: theme.transitions.duration.shortest,
}),
}}
>
<ExpandMoreIcon />
</IconButton>
{title && (
<Typography
className="title"
variant="subtitle1"
>
{title}
</Typography>
)}
</Box>
<Collapse
in={expanded}
timeout="auto"
unmountOnExit
>
<Typography
variant="body2"
sx={{ mt: 1 }}
>
{text}
</Typography>
</Collapse>
</Box>
);
}
1 change: 1 addition & 0 deletions packages/renderer/src/components/ExpandMore/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ExpandMore } from './component';
15 changes: 15 additions & 0 deletions packages/renderer/src/components/ExpandMore/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.ExpandMore .MuiButtonBase-root {
padding: 0;
margin-right: 4px;
}

.ExpandMore .title {
color: var(--dcl-silver);
text-transform: none !important;
}

.ExpandMore p {
margin: 0;
font-size: 11px;
color: var(--dcl-silver);
}
4 changes: 4 additions & 0 deletions packages/renderer/src/components/ExpandMore/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Props = {
title?: string;
text: string;
};
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { useCallback, useMemo, useState } from 'react';
import cx from 'classnames';
import { ChainId } from '@dcl/schemas';
import { Typography, Checkbox } from 'decentraland-ui2';
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';

import { misc } from '#preload';

import type { File, Info, Status, DeploymentComponentsStatus } from '/shared/types/deploy';
import type { File, Info, Status } from '/shared/types/deploy';

import { useAuth } from '/@/hooks/useAuth';
import { useWorkspace } from '/@/hooks/useWorkspace';
import { useEditor } from '/@/hooks/useEditor';
import { useSnackbar } from '/@/hooks/useSnackbar';
import { useDeploy } from '/@/hooks/useDeploy';

import { type Deployment } from '/@/modules/store/deployment/slice';
import { getInvalidFiles, MAX_FILE_SIZE_BYTES } from '/@/modules/store/deployment/utils';
import { t } from '/@/modules/store/translation/utils';
import { addBase64ImagePrefix } from '/@/modules/image';
import { REPORT_ISSUES_URL } from '/@/modules/utils';
Expand All @@ -21,6 +24,7 @@ import { PublishModal } from '/@/components/Modals/PublishProject/PublishModal';
import { ConnectedSteps } from '/@/components/Step';
import { Button } from '/@/components/Button';
import { Loader } from '/@/components/Loader';
import { ExpandMore } from '/@/components/ExpandMore';

import type { Step } from '/@/components/Step/types';
import type { Props } from '/@/components/Modals/PublishProject/types';
Expand Down Expand Up @@ -59,8 +63,7 @@ export function Deploy(props: Props) {
const { chainId, wallet, avatar } = useAuth();
const { updateProjectInfo } = useWorkspace();
const { loadingPublish } = useEditor();
const { getDeployment, overallStatus, isDeployFinishing, executeDeploymentWithRetry } =
useDeploy();
const { getDeployment, executeDeployment } = useDeploy();
const { pushCustom } = useSnackbar();
const [showWarning, setShowWarning] = useState(false);
const [skipWarning, setSkipWarning] = useState(project.info.skipPublishWarning ?? false);
Expand All @@ -69,7 +72,7 @@ export function Deploy(props: Props) {
const handlePublish = useCallback(() => {
setShowWarning(false);
updateProjectInfo(project.path, { skipPublishWarning: skipWarning }); // write skip warning flag
executeDeploymentWithRetry(project.path);
executeDeployment(project.path);
}, [skipWarning, project]);

const handleBack = useCallback(() => {
Expand Down Expand Up @@ -202,7 +205,7 @@ export function Deploy(props: Props) {
</Typography>
</div>
</div>
{deployment.status === 'idle' && deployment.retryAttempt === 0 && (
{deployment.status === 'idle' && (
<Idle
files={deployment.files}
error={deployment.error}
Expand All @@ -211,11 +214,8 @@ export function Deploy(props: Props) {
)}
{(deployment.status === 'pending' || deployment.status === 'failed') && (
<Deploying
info={deployment.info}
deployment={deployment}
url={jumpInUrl}
componentsStatus={deployment.componentsStatus}
isFinishing={isDeployFinishing(deployment)}
overallStatus={overallStatus(deployment)}
onClick={handleJumpIn}
onRetry={handleDeployRetry}
/>
Expand All @@ -237,11 +237,19 @@ export function Deploy(props: Props) {

type IdleProps = {
files: File[];
error?: string;
error?: Deployment['error'];
onClick: () => void;
};

function Idle({ files, error, onClick }: IdleProps) {
const invalidFiles = getInvalidFiles(files);
const errorMessage =
invalidFiles.length > 0
? t('modal.publish_project.deploy.deploying.errors.max_file_size_exceeded', {
maxFileSizeInMb: MAX_FILE_SIZE_BYTES / 1e6,
})
: error?.message;

return (
<div className="files">
<div className="filters">
Expand All @@ -251,14 +259,18 @@ function Idle({ files, error, onClick }: IdleProps) {
<div className="size">
{t('modal.publish_project.deploy.files.size', {
size: getSize(files.reduce((total, file) => total + file.size, 0)),
b: (child: string) => <b>{child}</b>,
b: (child: string) => (
<b>
{child}/{MAX_FILE_SIZE_BYTES / 1e6}MB
</b>
),
})}
</div>
</div>
<div className="list">
{files.map(file => (
<div
className="file"
className={cx('file', { invalid: file.size > MAX_FILE_SIZE_BYTES })}
key={file.name}
>
<div
Expand All @@ -272,7 +284,7 @@ function Idle({ files, error, onClick }: IdleProps) {
))}
</div>
<div className="actions">
<p className="error">{error}</p>
<p className="error">{errorMessage}</p>
<Button
size="large"
onClick={onClick}
Expand All @@ -287,24 +299,18 @@ function Idle({ files, error, onClick }: IdleProps) {
}

type DeployingProps = {
info: Info;
deployment: Deployment;
url: string;
componentsStatus: DeploymentComponentsStatus;
isFinishing: boolean;
overallStatus: Status;
onClick: () => void;
onRetry: () => void;
};

function Deploying({
info,
componentsStatus,
url,
onClick,
onRetry,
isFinishing,
overallStatus,
}: DeployingProps) {
function Deploying({ deployment, url, onClick, onRetry }: DeployingProps) {
const { isDeployFinishing, deriveOverallStatus } = useDeploy();
const { info, componentsStatus, error } = deployment;
const isFinishing = isDeployFinishing(deployment);
const overallStatus = deriveOverallStatus(deployment);

const onReportIssue = useCallback(() => {
void misc.openExternal(REPORT_ISSUES_URL);
}, []);
Expand Down Expand Up @@ -363,9 +369,20 @@ function Deploying({
{overallStatus === 'failed' ? <div className="Warning" /> : <Loader />}
<Typography variant="h5">{title}</Typography>
</div>
{overallStatus === 'failed' && (
{overallStatus === 'failed' && !error && (
<span>{t('modal.publish_project.deploy.deploying.try_again')}</span>
)}
{error && (
<span className="error">
{error.message}
{error.cause && (
<ExpandMore
title={t('modal.publish_project.deploy.deploying.errors.details')}
text={error.cause}
/>
)}
</span>
)}
</div>
<ConnectedSteps steps={steps} />
{overallStatus === 'failed' ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,10 @@
color: #a09ba8;
}

.Deploy .scene .files .list .file.invalid {
color: var(--error);
}

.Deploy .scene .files .list .file:last-child {
border-bottom: none;
}
Expand Down Expand Up @@ -254,6 +258,17 @@
margin-right: 12px;
}

.Deploy .scene .Deploying .header .error {
color: var(--error);
display: flex;
flex-direction: column;
gap: 8px;
}

.Deploy .scene .Deploying .header .error .ExpandMore {
margin-left: -4px;
}

.Deploy .scene .Deploying .ConnectedSteps {
margin-bottom: 30px;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function PublishToLand(props: Props) {
worldConfiguration: undefined, // Cannot deploy to a LAND with a world configuration
updatedAt: Date.now(),
});
void publishScene({
publishScene({
target: import.meta.env.VITE_CATALYST_SERVER || DEPLOY_URLS.CATALYST_SERVER,
});
props.onStep('deploy');
Expand Down
25 changes: 11 additions & 14 deletions packages/renderer/src/hooks/useDeploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import type { ChainId } from '@dcl/schemas';
import { useDispatch, useSelector } from '#store';

import { actions, type Deployment } from '/@/modules/store/deployment';
import { deriveOverallStatus, checkDeploymentCompletion } from '/@/modules/store/deployment/utils';
import {
deriveOverallStatus as _deriveOverallStatus,
checkDeploymentCompletion,
} from '/@/modules/store/deployment/utils';

export const useDeploy = () => {
const dispatch = useDispatch();
Expand All @@ -30,24 +33,19 @@ export const useDeploy = () => {
[dispatch],
);

const executeDeploymentWithRetry = useCallback(
(path: string) => {
dispatch(actions.executeDeploymentWithRetry(path));
},
[dispatch],
);

const removeDeployment = useCallback(
(path: string) => {
dispatch(actions.removeDeployment({ path }));
},
[dispatch],
);

const overallStatus = useCallback(
(deployment: Deployment) => deriveOverallStatus(deployment.componentsStatus),
[],
);
const deriveOverallStatus = useCallback((deployment: Deployment) => {
if (deployment.status === 'failed') {
return 'failed';
}
return _deriveOverallStatus(deployment.componentsStatus);
}, []);

const isDeployFinishing = useCallback(
(deployment: Deployment) => checkDeploymentCompletion(deployment.componentsStatus),
Expand All @@ -59,8 +57,7 @@ export const useDeploy = () => {
getDeployment,
initializeDeployment,
executeDeployment,
executeDeploymentWithRetry,
overallStatus,
deriveOverallStatus,
isDeployFinishing,
removeDeployment,
};
Expand Down
15 changes: 4 additions & 11 deletions packages/renderer/src/hooks/useEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,11 @@ import type { RPCInfo } from '/@/modules/rpc';
import { bufferToScene } from '/@/modules/buffer';
import { useInspector } from '/@/hooks/useInspector';
import { useAuth } from '/@/hooks/useAuth';
import { useDeploy } from '/@/hooks/useDeploy';
import { stripBase64ImagePrefix } from '/@/modules/image';

export const useEditor = () => {
const dispatch = useDispatch();
const { chainId, wallet } = useAuth();
const { initializeDeployment } = useDeploy();
const { generateThumbnail } = useInspector();
const editor = useSelector(state => state.editor);
const { project } = editor;
Expand All @@ -29,17 +27,12 @@ export const useEditor = () => {
}, [dispatch, editorActions.startInspector]);

const publishScene = useCallback(
async (opts: Omit<DeployOptions, 'path'> = {}) => {
if (project) {
const port = await dispatch(
editorActions.publishScene({ ...opts, path: project.path }),
).unwrap();
if (chainId && wallet) {
initializeDeployment(project.path, port, chainId, wallet);
}
(opts: Omit<DeployOptions, 'path'> = {}) => {
if (project && chainId && wallet) {
dispatch(editorActions.publishScene({ ...opts, path: project.path, chainId, wallet }));
}
},
[project, editorActions.publishScene, chainId, wallet, initializeDeployment],
[project, editorActions.publishScene, chainId, wallet],
);

const openPreview = useCallback(
Expand Down
Loading
Loading