Skip to content

Commit

Permalink
refactor: simplify complete deployment logic
Browse files Browse the repository at this point in the history
  • Loading branch information
ilbertt committed Jun 23, 2024
1 parent 724f660 commit 39a12d5
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 152 deletions.
100 changes: 49 additions & 51 deletions frontend/app/dashboard/new-deployment/page.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
"use client";

import {BackButton} from "@/components/back-button";
import {useToast} from "@/components/ui/use-toast";
import {useDeploymentContext} from "@/contexts/DeploymentContext";
import { BackButton } from "@/components/back-button";
import { useToast } from "@/components/ui/use-toast";
import { useDeploymentContext } from "@/contexts/DeploymentContext";
import {
type OnWsErrorCallback,
type OnWsMessageCallback,
type OnWsOpenCallback,
useIcContext,
} from "@/contexts/IcContext";
import type {DeploymentParams, DeploymentState} from "@/declarations/backend.did";
import {extractOk} from "@/helpers/result";
import {useRouter} from "next/navigation";
import {useCallback, useEffect, useMemo, useState} from "react";
import {Alert, AlertDescription, AlertTitle} from "@/components/ui/alert";
import {AlertCircle} from "lucide-react";
import {displayE8sAsIcp, icpToE8s} from "@/helpers/ui";
import {Spinner} from "@/components/spinner";
import {NewDeploymentForm} from "@/components/new-deployment-form";
import {transferE8sToBackend} from "@/services/backend";
import {confirmDeployment, updateDeploymentState} from "@/services/deployment";
import type { DeploymentParams, DeploymentState } from "@/declarations/backend.did";
import { extractOk } from "@/helpers/result";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useMemo, useState } from "react";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react";
import { displayE8sAsIcp, icpToE8s } from "@/helpers/ui";
import { Spinner } from "@/components/spinner";
import { NewDeploymentForm } from "@/components/new-deployment-form";
import { transferE8sToBackend } from "@/services/backend";
import { completeDeployment, confirmDeployment, updateDeploymentState } from "@/services/deployment";

const FETCH_DEPLOYMENT_PRICE_INTERVAL_MS = 30_000; // 30 seconds

export default function NewDeployment() {
const router = useRouter();
const {backendActor, openWs, closeWs, setWsCallbacks, ledgerCanister, ledgerData, refreshLedgerData} = useIcContext();
const {tlsCertificateData, loadOrCreateCertificate, fetchDeployments} =
const { backendActor, openWs, closeWs, setWsCallbacks, ledgerCanister, ledgerData, refreshLedgerData } = useIcContext();
const { tlsCertificateData, loadOrCreateCertificate, fetchDeployments } =
useDeploymentContext();
const [isSubmitting, setIsSubmitting] = useState(false);
const [isDeploying, setIsDeploying] = useState(false);
Expand All @@ -37,12 +37,12 @@ export default function NewDeployment() {
const [deploymentE8sPrice, setDeploymentE8sPrice] = useState<bigint | null>(null);
const [fetchDeploymentPriceInterval, setFetchDeploymentPriceInterval] = useState<NodeJS.Timeout | null>(null);
const userHasEnoughBalance = useMemo(() =>
ledgerData.balanceE8s !== null && deploymentE8sPrice !== null && ledgerData.balanceE8s > deploymentE8sPrice,
ledgerData.balanceE8s !== null && deploymentE8sPrice !== null && ledgerData.balanceE8s > deploymentE8sPrice,
[ledgerData.balanceE8s, deploymentE8sPrice]
);
const [paymentStatus, setPaymentStatus] = useState<string | null>(null);
const [deploymentParams, setDeploymentParams] = useState<DeploymentParams | null>(null);
const {toast} = useToast();
const { toast } = useToast();

const toastError = useCallback(
(message: string) => {
Expand Down Expand Up @@ -97,6 +97,26 @@ export default function NewDeployment() {
}
}, [backendActor, deploymentE8sPrice, ledgerCanister, refreshLedgerData, toastError]);

const setDeploymentAsActive = useCallback(async (deploymentId: string) => {
try {
closeWs();

const stepActive = {
Active: null,
};
setDeploymentSteps((prev) => [...prev, stepActive]);

await completeDeployment(backendActor!, deploymentId);

setIsDeploying(false);
router.push("/dashboard");
} catch (e) {
console.error("Failed to complete deployment:", e);
setDeploymentError("Failed to complete deployment, see console for details");
setIsDeploying(false);
}
}, [backendActor, closeWs, router]);

const onWsOpen: OnWsOpenCallback = useCallback(async () => {
console.log("ws open");

Expand All @@ -112,7 +132,7 @@ export default function NewDeployment() {
const res = await backendActor.create_deployment(deploymentParams);
const deploymentId = extractOk(res);
console.log("deployment id", deploymentId);
setDeploymentSteps([{Initialized: null}]);
setDeploymentSteps([{ Initialized: null }]);
};

setIsDeploying(true);
Expand Down Expand Up @@ -152,8 +172,6 @@ export default function NewDeployment() {
return;
}

let leaseCreated = false;

try {
if ("LeaseCreated" in deploymentUpdate.update) {
const deploymentCreatedState = deploymentSteps.find((el) =>
Expand All @@ -166,7 +184,9 @@ export default function NewDeployment() {
tlsCertificateData!
);

leaseCreated = true;
await setDeploymentAsActive(deploymentUpdate.id);

await fetchDeployments(backendActor!);
}
} catch (e) {
console.error(e);
Expand All @@ -190,37 +210,15 @@ export default function NewDeployment() {
);
setIsDeploying(false);
}

try {
if (leaseCreated) {
closeWs();

const stepActive = {
Active: null,
};
setDeploymentSteps((prev) => [...prev, stepActive]);

await updateDeploymentState(backendActor!, deploymentUpdate.id, stepActive);

await fetchDeployments(backendActor!);

setIsDeploying(false);
router.push("/dashboard");
}
} catch (e) {
console.error("Failed to complete deployment:", e);
setDeploymentError("Failed to complete deployment, see console for details");
setIsDeploying(false);
}
},
[
tlsCertificateData,
setDeploymentSteps,
deploymentSteps,
backendActor,
fetchDeployments,
router,
closeWs,
setDeploymentAsActive,
]
);

Expand Down Expand Up @@ -332,7 +330,7 @@ export default function NewDeployment() {
return (
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex items-center justify-start">
<BackButton/>
<BackButton />
<h2 className="ml-4 text-3xl font-bold tracking-tight">
Create Deployment
</h2>
Expand All @@ -351,13 +349,13 @@ export default function NewDeployment() {
Price (est.):
</h5>
{deploymentE8sPrice !== null ? (
<pre>~{displayE8sAsIcp(deploymentE8sPrice, {maximumFractionDigits: 6})}</pre>
<pre>~{displayE8sAsIcp(deploymentE8sPrice, { maximumFractionDigits: 6 })}</pre>
) : (
<Spinner/>
<Spinner />
)}
{(!(isSubmitting || isDeploying) && (deploymentE8sPrice !== null) && !userHasEnoughBalance) && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4"/>
<AlertCircle className="h-4 w-4" />
<AlertTitle>Insufficient balance</AlertTitle>
<AlertDescription>
<p>Please top up your account.</p>
Expand Down Expand Up @@ -390,13 +388,13 @@ export default function NewDeployment() {
{idx + 1}. {el}
</p>
))}
{isDeploying && <Spinner/>}
{isDeploying && <Spinner />}
</div>
</div>
)}
{Boolean(deploymentError) && (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4"/>
<AlertCircle className="h-4 w-4" />
<AlertTitle>Deployment Error</AlertTitle>
<AlertDescription>
<p>{deploymentError}</p>
Expand Down
106 changes: 26 additions & 80 deletions frontend/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use client";

import Tier from "@/components/Tier";
import {LoadingButton} from "@/components/loading-button";
import {Spinner} from "@/components/spinner";
import {Button} from "@/components/ui/button";
import {Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle,} from "@/components/ui/card";
import {Collapsible, CollapsibleContent, CollapsibleTrigger,} from "@/components/ui/collapsible";
import { LoadingButton } from "@/components/loading-button";
import { Spinner } from "@/components/spinner";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card";
import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@/components/ui/collapsible";
import {
Dialog,
DialogContent,
Expand All @@ -15,10 +15,10 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {useToast} from "@/components/ui/use-toast";
import {useDeploymentContext} from "@/contexts/DeploymentContext";
import {useIcContext} from "@/contexts/IcContext";
import {type Deployment} from "@/declarations/backend.did";
import { useToast } from "@/components/ui/use-toast";
import { useDeploymentContext } from "@/contexts/DeploymentContext";
import { useIcContext } from "@/contexts/IcContext";
import { type Deployment } from "@/declarations/backend.did";
import {
extractDeploymentCreated,
extractLeaseCreated,
Expand All @@ -30,25 +30,25 @@ import {
isDeploymentClosed,
isDeploymentFailed,
} from "@/helpers/deployment";
import {displayIcp} from "@/helpers/ui";
import {confirmDeployment, queryLeaseStatus, updateDeploymentState} from "@/services/deployment";
import {DeploymentTier} from "@/types/deployment";
import {ChevronsUpDown} from "lucide-react";
import {useRouter} from "next/navigation";
import {useCallback, useEffect, useState} from "react";
import { displayIcp } from "@/helpers/ui";
import { queryLeaseStatus } from "@/services/deployment";
import { DeploymentTier } from "@/types/deployment";
import { ChevronsUpDown } from "lucide-react";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";

export default function Dashboard() {
const router = useRouter();
const {isLoggedIn, backendActor} = useIcContext();
const {deployments, fetchDeployments, loadOrCreateCertificate} =
const { isLoggedIn, backendActor } = useIcContext();
const { deployments, fetchDeployments, loadOrCreateCertificate } =
useDeploymentContext();
const [isClosingDeployment, setIsClosingDeployment] = useState(false);
const [dialogDeploymentId, setDialogDeploymentId] = useState<string>();
const [isStatusDialogOpen, setIsStatusDialogOpen] = useState(false);
const [isFetchingStatus, setIsFetchingStatus] = useState(false);
const [leaseStatusData, setLeaseStatusData] =
useState<Record<string, unknown>>();
const {toast} = useToast();
const { toast } = useToast();

const handleNewDeployment = useCallback(async () => {
router.push("/dashboard/new-deployment");
Expand Down Expand Up @@ -81,10 +81,10 @@ export default function Dashboard() {
try {
const certData = await loadOrCreateCertificate(backendActor!);
const updates = deployment.state_history.map(([_, d]) => d);
const {dseq} = extractDeploymentCreated(
const { dseq } = extractDeploymentCreated(
updates.find((el) => el.hasOwnProperty("DeploymentCreated"))!
);
const {provider_url} = extractLeaseCreated(
const { provider_url } = extractLeaseCreated(
updates.find((el) => el.hasOwnProperty("LeaseCreated"))!
);

Expand Down Expand Up @@ -118,60 +118,6 @@ export default function Dashboard() {
}
}, [isLoggedIn, fetchDeployments, backendActor]);

useEffect(() => {
checkDeploymentState();
}, [deployments]);

const checkDeploymentState = useCallback(async () => {
for (const deployment of deployments) {
const lastState = deployment.deployment.state_history[deployment.deployment.state_history.length - 1][1];
const deploymentCreatedState = deployment.deployment.state_history.find(([_, state]) => "DeploymentCreated" in state)![1];
if ("LeaseCreated" in lastState) {
let leaseCreated = false;

try {
const cert = await loadOrCreateCertificate(backendActor!);

await confirmDeployment(
lastState,
deploymentCreatedState,
cert!
);

leaseCreated = true;
} catch (e) {
console.error("Failed to update deployment:", e);

try {
const stepFailed = {
FailedOnClient: {
reason: JSON.stringify(e),
},
};
await updateDeploymentState(backendActor!, deployment.id, stepFailed);
} catch (e) {
console.error("Failed to update deployment:", e);
}
}

try {
if (leaseCreated) {

const stepActive = {
Active: null,
};

await updateDeploymentState(backendActor!, deployment.id, stepActive);

await fetchDeployments(backendActor!);
}
} catch (e) {
console.error("Failed to complete deployment:", e);
}
}
}
}, [backendActor, deployments, loadOrCreateCertificate]);

return (
<div className="flex-1 space-y-4 p-8 pt-6">
<div className="flex items-center justify-between space-y-2">
Expand Down Expand Up @@ -208,17 +154,17 @@ export default function Dashboard() {
Tier:
{/* TODO: display the actual deployment tier */}
<div className="border rounded-md px-3 py-2">
<Tier tier={DeploymentTier.SMALL}/>
<Tier tier={DeploymentTier.SMALL} />
</div>
</div>
<div className="flex flex-row gap-1">
Price:
<pre>{displayIcp(el.deployment.icp_price, {maximumFractionDigits: 6})}</pre>
<pre>{displayIcp(el.deployment.icp_price, { maximumFractionDigits: 6 })}</pre>
</div>
<Collapsible>
<CollapsibleTrigger className="flex items-center gap-4 w-full mt-4">
Status history
<ChevronsUpDown className="h-4 w-4"/>
<ChevronsUpDown className="h-4 w-4" />
</CollapsibleTrigger>
<CollapsibleContent className="flex flex-col gap-1">
{el.deployment.state_history.map((item, i) => (
Expand Down Expand Up @@ -254,7 +200,7 @@ export default function Dashboard() {
<DialogTitle>Deployment status</DialogTitle>
<DialogDescription>
{isFetchingStatus ? (
<Spinner/>
<Spinner />
) : (
Boolean(leaseStatusData) && (
<span className="font-mono">
Expand Down Expand Up @@ -282,9 +228,9 @@ export default function Dashboard() {
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>
Deployment id to close:
<br/>
<br />
<span className="font-mono text-nowrap">{el.id}</span>
<br/>
<br />
<b>This action cannot be undone.</b>
</DialogDescription>
</DialogHeader>
Expand Down
Loading

0 comments on commit 39a12d5

Please sign in to comment.