Check for deployment status while creating project using deployer API

This commit is contained in:
Shreerang Kale 2024-10-23 14:56:34 +05:30 committed by Adw8
parent b5e189d88d
commit 1c13bcf66d
3 changed files with 118 additions and 133 deletions

View File

@ -20,16 +20,6 @@
clientId = ""
clientSecret = ""
[google]
clientId = ""
clientSecret = ""
[turnkey]
apiBaseUrl = "https://api.turnkey.com"
apiPrivateKey = ""
apiPublicKey = ""
defaultOrganizationId = ""
[registryConfig]
fetchDeploymentRecordDelay = 5000
checkAuctionStatusDelay = 5000

View File

@ -1,6 +1,5 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import { Deployment, DeploymentStatus } from 'gql-client';
import { DeployStep, DeployStatus } from './DeployStep';
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
@ -12,6 +11,23 @@ import { useGQLClient } from 'context/GQLClientContext';
const FETCH_DEPLOYMENTS_INTERVAL = 5000;
type RequestState =
| 'SUBMITTED'
| 'DEPLOYING'
| 'DEPLOYED'
| 'REMOVED'
| 'CANCELLED'
| 'ERROR';
type Record = {
id: string;
createTime: string;
app: string;
lastState: RequestState;
lastUpdate: string;
logAvailable: boolean;
};
const Deploy = () => {
const client = useGQLClient();
@ -19,7 +35,7 @@ const Deploy = () => {
const projectId = searchParams.get('projectId');
const [open, setOpen] = React.useState(false);
const [deployments, setDeployments] = useState<Deployment[]>([]);
const [record, setRecord] = useState<Record>();
const handleOpen = () => setOpen(!open);
@ -30,35 +46,84 @@ const Deploy = () => {
navigate(`/${orgSlug}/projects/create`);
}, []);
const fetchDeployments = useCallback(async () => {
const showSteps = useMemo(() => {
if (!record) {
return true;
}
if (
record.lastState === 'CANCELLED' ||
record.lastState === 'REMOVED' ||
record.lastState === 'ERROR'
) {
return false;
} else {
return true;
}
}, [record]);
const showTimer = useMemo(() => {
if (!record) {
return true;
}
if (
record.lastState === 'CANCELLED' ||
record.lastState === 'REMOVED' ||
record.lastState === 'ERROR' ||
record.lastState === 'DEPLOYED'
) {
return false;
} else {
return true;
}
}, [record]);
const fetchDeploymentRecords = useCallback(async () => {
if (!projectId) {
return;
}
const { deployments } = await client.getDeployments(projectId);
setDeployments(deployments);
const deployment = deployments[0];
try {
const response = await fetch(
`${deployment.deployer.deployerApiUrl}/${deployment.applicationDeploymentRequestId}`,
);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const record: Record = await response.json();
setRecord(record);
} catch (err: any) {
console.log('Error fetching data from deployer', err);
}
}, [client, projectId]);
useEffect(() => {
fetchDeployments();
fetchDeploymentRecords();
const interval = setInterval(() => {
fetchDeployments();
fetchDeploymentRecords();
}, FETCH_DEPLOYMENTS_INTERVAL);
return () => {
clearInterval(interval);
};
}, [fetchDeployments]);
}, [fetchDeploymentRecords]);
useEffect(() => {
if (
deployments.length > 0 &&
deployments[0].status === DeploymentStatus.Ready
) {
if (!record) {
return;
}
if (record.lastState === 'DEPLOYED') {
navigate(`/${orgSlug}/projects/create/success/${projectId}`);
}
}, [deployments]);
}, [record]);
return (
<div className="space-y-7">
@ -67,12 +132,14 @@ const Deploy = () => {
<Heading as="h4" className="md:text-lg font-medium">
Deployment started ...
</Heading>
<div className="flex items-center gap-1.5">
<ClockOutlineIcon size={16} className="text-elements-mid-em" />
<Stopwatch
offsetTimestamp={setStopWatchOffset(Date.now().toString())}
/>
</div>
{showTimer && (
<div className="flex items-center gap-1.5">
<ClockOutlineIcon size={16} className="text-elements-mid-em" />
<Stopwatch
offsetTimestamp={setStopWatchOffset(Date.now().toString())}
/>
</div>
)}
</div>
<Button
onClick={handleOpen}
@ -89,30 +156,36 @@ const Deploy = () => {
/>
</div>
<div>
<DeployStep
title="Building"
status={DeployStatus.COMPLETE}
step="1"
processTime="72000"
/>
<DeployStep
title="Deployment summary"
status={DeployStatus.PROCESSING}
step="2"
startTime={Date.now().toString()}
/>
<DeployStep
title="Running checks"
status={DeployStatus.NOT_STARTED}
step="3"
/>
<DeployStep
title="Assigning domains"
status={DeployStatus.NOT_STARTED}
step="4"
/>
</div>
{showSteps ? (
<div>
<DeployStep
title={record ? 'Submitted' : 'Submitting'}
status={record ? DeployStatus.COMPLETE : DeployStatus.PROCESSING}
step="1"
/>
<DeployStep
title={
record && record.lastState === 'DEPLOYED'
? 'Deployed'
: 'Deploying'
}
status={
!record
? DeployStatus.NOT_STARTED
: record.lastState === 'DEPLOYED'
? DeployStatus.COMPLETE
: DeployStatus.PROCESSING
}
step="2"
startTime={Date.now().toString()}
/>
</div>
) : (
<div>
<DeployStep title={record!.lastState} status={DeployStatus.ERROR} />
</div>
)}
</div>
);
};

View File

@ -1,27 +1,16 @@
import { useState } from 'react';
import { Collapse } from '@snowballtools/material-tailwind-react-fork';
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
import FormatMillisecond from '../../FormatMilliSecond';
import processLogs from '../../../assets/process-logs.json';
import { cn } from 'utils/classnames';
import {
CheckRoundFilledIcon,
ClockOutlineIcon,
CopyIcon,
LoaderIcon,
MinusCircleIcon,
PlusIcon,
} from 'components/shared/CustomIcon';
import { Button } from 'components/shared/Button';
import { useToast } from 'components/shared/Toast';
import { useIntersectionObserver } from 'usehooks-ts';
enum DeployStatus {
PROCESSING = 'progress',
COMPLETE = 'complete',
NOT_STARTED = 'notStarted',
ERROR = 'error',
}
interface DeployStepsProps {
@ -37,30 +26,11 @@ const DeployStep = ({
status,
title,
startTime,
processTime,
}: DeployStepsProps) => {
const [isOpen, setIsOpen] = useState(false);
const { toast, dismiss } = useToast();
const { isIntersecting: hideGradientOverlay, ref } = useIntersectionObserver({
threshold: 1,
});
const disableCollapse = status !== DeployStatus.COMPLETE;
return (
<div className="border-b border-border-separator">
{/* Collapisble trigger */}
<button
className={cn(
'flex justify-between w-full py-5 gap-2',
disableCollapse && 'cursor-auto',
)}
tabIndex={disableCollapse ? -1 : undefined}
onClick={() => {
if (!disableCollapse) {
setIsOpen((val) => !val);
}
}}
className={cn('flex justify-between w-full py-5 gap-2', 'cursor-auto')}
>
<div className={cn('grow flex items-center gap-3')}>
{/* Icon */}
@ -73,12 +43,6 @@ const DeployStep = ({
{status === DeployStatus.PROCESSING && (
<LoaderIcon className="animate-spin text-elements-link" />
)}
{status === DeployStatus.COMPLETE && (
<div className="text-controls-primary">
{!isOpen && <PlusIcon size={24} />}
{isOpen && <MinusCircleIcon size={24} />}
</div>
)}
</div>
{/* Title */}
@ -107,51 +71,9 @@ const DeployStep = ({
size={15}
/>
</div>
<FormatMillisecond time={Number(processTime)} />{' '}
</div>
)}
</button>
{/* Collapsible */}
<Collapse open={isOpen}>
<div className="relative text-xs text-elements-low-em h-36 overflow-y-auto">
{/* Logs */}
{processLogs.map((log, key) => {
return (
<p className="font-mono" key={key}>
{log}
</p>
);
})}
{/* End of logs ref used for hiding gradient overlay */}
<div ref={ref} />
{/* Overflow gradient overlay */}
{!hideGradientOverlay && (
<div className="h-14 w-full sticky bottom-0 inset-x-0 bg-gradient-to-t from-white to-transparent" />
)}
{/* Copy log button */}
<div className={cn('sticky bottom-4 left-1/2 flex justify-center')}>
<Button
size="xs"
onClick={() => {
navigator.clipboard.writeText(processLogs.join('\n'));
toast({
title: 'Logs copied',
variant: 'success',
id: 'logs',
onDismiss: dismiss,
});
}}
leftIcon={<CopyIcon size={16} />}
>
Copy log
</Button>
</div>
</div>
</Collapse>
</div>
);
};