Skip to content

Commit 0004f1d

Browse files
committed
add deploy error
1 parent 9d788fc commit 0004f1d

File tree

13 files changed

+182
-79
lines changed

13 files changed

+182
-79
lines changed

packages/renderer/src/components/Button/styles.css

-8
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,5 @@
4747
}
4848

4949
.Button.MuiButton-outlined {
50-
color: var(--dcl) !important;
5150
background-color: transparent;
52-
border-color: var(--dcl);
53-
}
54-
55-
.Button.MuiButton-outlined:hover {
56-
color: var(--dcl) !important;
57-
background-color: transparent;
58-
border-color: var(--dcl-dark);
5951
}

packages/renderer/src/components/Modals/PublishProject/steps/Deploy/component.tsx

+78-43
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import {
3131
checkDeploymentStatus,
3232
fetchDeploymentStatus,
3333
deriveOverallStatus,
34+
cleanPendingsFromDeploymentStatus,
35+
isDeployFinishing,
3436
} from './utils';
3537
import {
3638
type File,
@@ -126,7 +128,7 @@ export function Deploy(props: Props) {
126128
const identity = localStorageGetIdentity(wallet);
127129
if (identity && chainId) {
128130
const authChain = Authenticator.signPayload(identity, info.rootCID);
129-
void deploy({ address: wallet, authChain, chainId: ChainId.ETHEREUM_SEPOLIA })
131+
void deploy({ address: wallet, authChain, chainId })
130132
.then(() => {
131133
if (!isMounted()) return;
132134
})
@@ -196,9 +198,12 @@ export function Deploy(props: Props) {
196198
}
197199
}, [jumpInUrl]);
198200

199-
const handleDeployFinish = useCallback((status: Status, error?: string) => {
200-
setDeployStatus(status);
201-
setError(error ?? null);
201+
const handleDeploySuccess = useCallback(() => {
202+
setDeployStatus('complete');
203+
}, []);
204+
205+
const handleDeployRetry = useCallback(() => {
206+
props.onBack && props.onBack();
202207
}, []);
203208

204209
return (
@@ -240,6 +245,7 @@ export function Deploy(props: Props) {
240245
</label>
241246
<span className="buttons">
242247
<Button
248+
color="secondary"
243249
variant="outlined"
244250
size="medium"
245251
onClick={handleBack}
@@ -321,8 +327,9 @@ export function Deploy(props: Props) {
321327
<Deploying
322328
info={info}
323329
url={jumpInUrl}
324-
onFinish={handleDeployFinish}
330+
onSuccess={handleDeploySuccess}
325331
onClick={handleJumpIn}
332+
onRetry={handleDeployRetry}
326333
/>
327334
)}
328335
{deployStatus === 'complete' && (
@@ -393,11 +400,12 @@ function Idle({ files, error, onClick }: IdleProps) {
393400
type DeployingProps = {
394401
info: Info;
395402
url: string | null;
396-
onFinish: (status: Status, error?: string) => void;
403+
onSuccess: () => void;
397404
onClick: () => void;
405+
onRetry: () => void;
398406
};
399407

400-
function Deploying({ info, url, onFinish, onClick }: DeployingProps) {
408+
function Deploying({ info, url, onSuccess, onClick, onRetry }: DeployingProps) {
401409
const { wallet } = useAuth();
402410
const [deployState, setDeployState] = useState<DeploymentStatus>(getInitialDeploymentStatus());
403411

@@ -406,41 +414,37 @@ function Deploying({ info, url, onFinish, onClick }: DeployingProps) {
406414
const identity = localStorageGetIdentity(wallet);
407415
if (!identity) throw new Error(`No identity found for wallet ${wallet}`);
408416
return fetchDeploymentStatus(info.rootCID, identity);
409-
}, [info]);
417+
}, [wallet, info]);
418+
419+
const onReportIssue = useCallback(() => {
420+
void misc.openExternal('https://decentraland.canny.io');
421+
}, []);
410422

411423
useEffect(
412424
() => {
413425
let isCancelled = false;
414426

415427
const handleUpdate = (status: DeploymentStatus) => {
416-
// if catalyst deploy fails, we abort the deployment completely
417-
if (status.catalyst === 'failed') {
418-
isCancelled = true;
419-
return onFinish(
420-
'failed',
421-
t('modal.publish_project.deploy.deploying.errors.catalyst_deploy'),
422-
);
428+
if (!isCancelled) {
429+
if (deriveOverallStatus(status) === 'failed') {
430+
isCancelled = true;
431+
setDeployState(cleanPendingsFromDeploymentStatus(status));
432+
} else {
433+
setDeployState(status);
434+
}
423435
}
424-
if (!isCancelled) setDeployState(status);
425436
};
426437

427-
const handleSuccess = (status: DeploymentStatus) => {
428-
if (!isCancelled) onFinish(deriveOverallStatus(status));
438+
const handleSuccess = () => {
439+
if (!isCancelled) onSuccess();
429440
};
430441

431442
const handleFailure = (error: any) => {
432443
if (!isCancelled) {
433-
// if we know the error, we can translate it
444+
// info: if we know the error, we can translate it
434445
if (isDeploymentError(error, 'MAX_RETRIES')) {
435-
return onFinish(
436-
'failed',
437-
t('modal.publish_project.deploy.deploying.errors.max_retries', {
438-
error: error.message,
439-
}),
440-
);
446+
setDeployState(cleanPendingsFromDeploymentStatus(error.status));
441447
}
442-
443-
onFinish('failed', error.message);
444448
}
445449
};
446450

@@ -465,48 +469,79 @@ function Deploying({ info, url, onFinish, onClick }: DeployingProps) {
465469
[] /* no deps, want this to run ONLY on mount */,
466470
);
467471

468-
const text = useMemo(() => t('modal.publish_project.deploy.deploying.step.loading'), []);
469-
const is = useCallback((status: Status, status2: Status) => status === status2, []);
472+
const getStepDescription = useCallback((status: Status) => {
473+
switch (status) {
474+
case 'pending':
475+
return t('modal.publish_project.deploy.deploying.step.loading');
476+
case 'failed':
477+
return t('modal.publish_project.deploy.deploying.step.failed');
478+
default:
479+
return undefined;
480+
}
481+
}, []);
470482

471483
const steps: Step[] = useMemo(() => {
472484
const { catalyst, assetBundle, lods } = deployState;
473-
474485
return [
475486
{
476487
bulletText: '1',
477488
name: t('modal.publish_project.deploy.deploying.step.catalyst'),
478-
text: is('pending', catalyst) ? text : undefined,
489+
description: getStepDescription(catalyst),
479490
state: catalyst,
480491
},
481492
{
482493
bulletText: '2',
483494
name: t('modal.publish_project.deploy.deploying.step.asset_bundle'),
484-
text: is('pending', assetBundle) ? text : undefined,
495+
description: getStepDescription(assetBundle),
485496
state: assetBundle,
486497
},
487498
{
488499
bulletText: '3',
489500
name: t('modal.publish_project.deploy.deploying.step.lods'),
490-
text: is('pending', lods) ? text : undefined,
501+
description: getStepDescription(lods),
491502
state: lods,
492503
},
493504
];
494-
}, [deployState]);
505+
}, [deployState, getStepDescription]);
495506

496-
const lastStep = steps[steps.length - 1];
507+
const isFinishing = useMemo(() => isDeployFinishing(deployState), [deployState]);
508+
const overallStatus = useMemo(() => deriveOverallStatus(deployState), [deployState]);
509+
const title = useMemo(() => {
510+
if (overallStatus === 'failed') return t('modal.publish_project.deploy.deploying.failed');
511+
if (isFinishing) return t('modal.publish_project.deploy.deploying.finishing');
512+
return t('modal.publish_project.deploy.deploying.publish');
513+
}, [overallStatus, isFinishing]);
497514

498515
return (
499516
<div className="Deploying">
500-
<div className="title">
501-
<Loader />
502-
<Typography variant="h5">
503-
{lastStep.state === 'pending'
504-
? t('modal.publish_project.deploy.deploying.finishing')
505-
: t('modal.publish_project.deploy.deploying.publish')}
506-
</Typography>
517+
<div className="header">
518+
<div className="title">
519+
{overallStatus === 'failed' ? <div className="Warning" /> : <Loader />}
520+
<Typography variant="h5">{title}</Typography>
521+
</div>
522+
{overallStatus === 'failed' && (
523+
<span>{t('modal.publish_project.deploy.deploying.try_again')}</span>
524+
)}
507525
</div>
508526
<ConnectedSteps steps={steps} />
509-
{lastStep.state === 'pending' ? (
527+
{overallStatus === 'failed' ? (
528+
<div className="actions">
529+
<Button
530+
size="large"
531+
variant="outlined"
532+
color="secondary"
533+
onClick={onReportIssue}
534+
>
535+
{t('modal.publish_project.deploy.deploying.actions.report_issue')}
536+
</Button>
537+
<Button
538+
size="large"
539+
onClick={onRetry}
540+
>
541+
{t('modal.publish_project.deploy.deploying.actions.retry')}
542+
</Button>
543+
</div>
544+
) : isFinishing ? (
510545
<>
511546
<div className="jump">
512547
<JumpUrl

packages/renderer/src/components/Modals/PublishProject/steps/Deploy/styles.css

+16-4
Original file line numberDiff line numberDiff line change
@@ -229,19 +229,31 @@
229229
padding: 10px;
230230
}
231231

232-
.Deploy .scene .Deploying .title {
232+
.Deploy .scene .Deploying .header {
233+
color: var(--dcl-silver);
233234
display: flex;
234-
align-items: center;
235235
justify-content: flex-start;
236-
text-transform: uppercase;
237236
margin-bottom: 20px;
237+
flex-direction: column;
238+
}
239+
240+
.Deploy .scene .Deploying .header .title {
241+
display: flex;
242+
align-items: center;
243+
text-transform: uppercase;
238244
}
239245

240-
.Deploy .scene .Deploying .title .Loader {
246+
.Deploy .scene .Deploying .header .Loader {
241247
width: inherit;
242248
margin-right: 12px;
243249
}
244250

251+
.Deploy .scene .Deploying .header .Warning {
252+
background-size: contain;
253+
width: 40px;
254+
margin-right: 12px;
255+
}
256+
245257
.Deploy .scene .Deploying .ConnectedSteps {
246258
margin-bottom: 30px;
247259
}

packages/renderer/src/components/Modals/PublishProject/steps/Deploy/types.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,17 @@ export type AssetBundleRegistryResponse = {
4242

4343
export type Error = 'MAX_RETRIES' | 'FETCH';
4444

45-
export class DeploymentError extends ErrorBase<Error> {}
45+
export class DeploymentError extends ErrorBase<Error> {
46+
constructor(
47+
public name: Error,
48+
public message: string = '',
49+
public status: DeploymentStatus,
50+
public cause?: any,
51+
) {
52+
super(name, message, cause);
53+
this.status = status;
54+
}
55+
}
4656

4757
export const isDeploymentError = (error: unknown, type: Error): error is DeploymentError =>
4858
error instanceof DeploymentError && error.name === type;

packages/renderer/src/components/Modals/PublishProject/steps/Deploy/utils.ts

+39-3
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,28 @@ export function validateStatus(status: string): Status {
6767
*/
6868
export function deriveOverallStatus(statuses: Record<string, string>): Status {
6969
const _statuses: Set<Status> = new Set(Object.values(statuses) as Status[]);
70+
if (_statuses.has('failed')) return 'failed';
7071
if (_statuses.has('pending')) return 'pending';
7172
if (_statuses.has('complete')) return 'complete';
72-
return 'failed';
73+
return 'idle';
74+
}
75+
76+
/**
77+
* Cleans up a `DeploymentStatus` object by resetting any 'pending' statuses to 'idle'.
78+
*
79+
* This function ensures that any deployment step stuck in a 'pending' state is treated
80+
* as 'idle' to indicate that it hasn't started or needs to be retried.
81+
*
82+
* @param status - The `DeploymentStatus` object containing the current statuses of deployment steps.
83+
* @returns A new `DeploymentStatus` object where all 'pending' statuses are replaced with 'idle'.
84+
*/
85+
export function cleanPendingsFromDeploymentStatus(status: DeploymentStatus): DeploymentStatus {
86+
return Object.fromEntries(
87+
Object.entries(status).map(([step, currentStatus]) => [
88+
step,
89+
currentStatus === 'pending' ? 'idle' : currentStatus,
90+
]),
91+
) as DeploymentStatus;
7392
}
7493

7594
/**
@@ -96,8 +115,6 @@ export async function fetchDeploymentStatus(
96115

97116
const json = (await response.json()) as AssetBundleRegistryResponse;
98117

99-
console.log('json: ', json);
100-
101118
return {
102119
catalyst: validateStatus(json.catalyst),
103120
assetBundle: deriveOverallStatus(json.assetBundles),
@@ -169,8 +186,27 @@ export async function checkDeploymentStatus(
169186
const maxRetriesError = new DeploymentError(
170187
'MAX_RETRIES',
171188
'Max retries reached. Deployment failed.',
189+
currentStatus,
172190
error,
173191
);
174192
console.error(maxRetriesError);
175193
throw maxRetriesError;
176194
}
195+
196+
/**
197+
* Checks if the deployment is nearing completion based on a given percentage threshold.
198+
*
199+
* This function evaluates the `DeploymentStatus` object to determine whether the proportion
200+
* of steps with a 'complete' status meets or exceeds the specified threshold (default: 60%).
201+
*
202+
* @param status - The `DeploymentStatus` object containing the current statuses of deployment steps.
203+
* @param percentage - The completion threshold as a decimal (e.g., `0.6` for 60%). Defaults to 0.6.
204+
* @returns `true` if the proportion of completed steps is greater than or equal to the threshold; otherwise, `false`.
205+
*/
206+
export function isDeployFinishing(status: DeploymentStatus, percentage: number = 0.6): boolean {
207+
const statuses = Object.values(status);
208+
const total = statuses.length;
209+
if (total === 0) return false;
210+
const completedCount = statuses.filter(value => value === 'complete').length;
211+
return completedCount / total >= percentage;
212+
}

packages/renderer/src/components/Modals/PublishProject/steps/PublishToLand/component.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export function PublishToLand(props: Props) {
6363
updatedAt: Date.now(),
6464
});
6565
void publishScene({
66-
target: import.meta.env.VITE_CATALYST_SERVER || DEPLOY_URLS.DEV_CATALYST_SERVER,
66+
target: import.meta.env.VITE_CATALYST_SERVER || DEPLOY_URLS.CATALYST_SERVER,
6767
});
6868
props.onStep('deploy');
6969
}, [placement, props.onStep]);

packages/renderer/src/components/Navbar/styles.css

-11
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,6 @@
3434
border-bottom: 2px solid white;
3535
}
3636

37-
.Navbar .feedback {
38-
border-radius: 6px;
39-
padding: 8px;
40-
text-transform: uppercase;
41-
border: 1px solid var(--text);
42-
font-size: 12px;
43-
font-weight: 600;
44-
text-align: center;
45-
cursor: pointer;
46-
}
47-
4837
.Navbar .actions {
4938
display: flex;
5039
align-items: center;

0 commit comments

Comments
 (0)