List deployer LRNs in deployment configuration step (#11)

Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

- Fix request Id being set to `null` while fetching build logs
- Populate deployer LRNs dropdown with LRNs fetched from registry in configure delpoyment step

![image](/attachments/ff421bdf-6e0b-443e-9dc8-455bde481b4f)

![image](/attachments/87c9bce3-3743-4f4a-a997-a02a3504e61e)

![image](/attachments/dd442fe6-ad30-4723-a2bb-0723ad3eb3c9)

![image](/attachments/37f0da01-671f-4e3a-92e4-b34e25566a0d)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Neeraj <neeraj.rtly@gmail.com>
Reviewed-on: cerc-io/snowballtools-base#11
This commit is contained in:
2024-10-23 15:36:19 +00:00
co-authored by IshaVenikar Neeraj
parent 096318cf13
commit 3d9aedeb7e
17 changed files with 273 additions and 161 deletions
@@ -0,0 +1,40 @@
import ConfirmDialog, {
ConfirmDialogProps,
} from 'components/shared/ConfirmDialog';
import { ArrowRightCircleFilledIcon, LoadingIcon } from 'components/shared/CustomIcon';
interface DeleteDeploymentDialogProps extends ConfirmDialogProps {
isConfirmButtonLoading?: boolean;
}
export const DeleteDeploymentDialog = ({
open,
handleCancel,
handleConfirm,
isConfirmButtonLoading,
...props
}: DeleteDeploymentDialogProps) => {
return (
<ConfirmDialog
{...props}
dialogTitle="Delete deployment?"
handleCancel={handleCancel}
open={open}
confirmButtonTitle={isConfirmButtonLoading ? "Deleting deployment" : "Yes, delete deployment"}
handleConfirm={handleConfirm}
confirmButtonProps={{
variant: 'danger',
disabled: isConfirmButtonLoading,
rightIcon: isConfirmButtonLoading ? (
<LoadingIcon className="animate-spin" />
) : (
<ArrowRightCircleFilledIcon />
),
}}
>
<p className="text-sm text-elements-high-em">
Once deleted, the deployment will not be accessible.
</p>
</ConfirmDialog>
);
};
@@ -1,9 +1,11 @@
import { useCallback, useState } from 'react';
import { useCallback, useState, useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { FormProvider, FieldValues } from 'react-hook-form';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useMediaQuery } from 'usehooks-ts';
import { AddEnvironmentVariableInput, AuctionParams } from 'gql-client';
import { AddEnvironmentVariableInput, AuctionParams, Deployer } from 'gql-client';
import { Select, MenuItem, FormControl, FormHelperText } from '@mui/material';
import {
ArrowRightCircleFilledIcon,
@@ -11,7 +13,6 @@ import {
} from 'components/shared/CustomIcon';
import { Heading } from '../../shared/Heading';
import { Button } from '../../shared/Button';
import { Select, SelectOption } from 'components/shared/Select';
import { Input } from 'components/shared/Input';
import { useToast } from 'components/shared/Toast';
import { useGQLClient } from '../../../context/GQLClientContext';
@@ -30,6 +31,8 @@ type ConfigureFormValues = ConfigureDeploymentFormValues &
const Configure = () => {
const [isLoading, setIsLoading] = useState(false);
const [deployers, setDeployers] = useState<Deployer[]>([]);
const [searchParams] = useSearchParams();
const templateId = searchParams.get('templateId');
const queryParams = new URLSearchParams(location.search);
@@ -48,7 +51,7 @@ const Configure = () => {
const client = useGQLClient();
const methods = useForm<ConfigureFormValues>({
defaultValues: { option: 'LRN' },
defaultValues: { option: 'Auction' },
});
const selectedOption = methods.watch('option');
@@ -153,24 +156,33 @@ const Configure = () => {
if (templateId) {
createFormData.option === 'Auction'
? navigate(
`/${orgSlug}/projects/create/success/${projectId}?isAuction=true`,
)
`/${orgSlug}/projects/create/success/${projectId}?isAuction=true`,
)
: navigate(
`/${orgSlug}/projects/create/template/deploy?projectId=${projectId}&templateId=${templateId}`,
);
`/${orgSlug}/projects/create/template/deploy?projectId=${projectId}&templateId=${templateId}`,
);
} else {
createFormData.option === 'Auction'
? navigate(
`/${orgSlug}/projects/create/success/${projectId}?isAuction=true`,
)
`/${orgSlug}/projects/create/success/${projectId}?isAuction=true`,
)
: navigate(
`/${orgSlug}/projects/create/deploy?projectId=${projectId}`,
);
`/${orgSlug}/projects/create/deploy?projectId=${projectId}`,
);
}
},
[client, createProject, dismiss, toast],
);
const fetchDeployers = useCallback(async () => {
const res = await client.getDeployers()
setDeployers(res.deployers)
}, [client])
useEffect(() => {
fetchDeployers()
}, [])
return (
<div className="space-y-7 px-4 py-6">
<div className="flex justify-between mb-6">
@@ -195,24 +207,14 @@ const Configure = () => {
control={methods.control}
render={({ field: { value, onChange } }) => (
<Select
label="Configuration Options"
value={
{
value: value || 'LRN',
label:
value === 'Auction'
? 'Create Auction'
: 'Deployer LRN',
} as SelectOption
}
onChange={(value) =>
onChange((value as SelectOption).value)
}
options={[
{ value: 'LRN', label: 'Deployer LRN' },
{ value: 'Auction', label: 'Create Auction' },
]}
/>
value={value}
onChange={(event) => onChange(event.target.value)}
size='small'
displayEmpty
>
<MenuItem value="LRN">Deployer LRN</MenuItem>
<MenuItem value="Auction">Create Auction</MenuItem>
</Select>
)}
/>
</div>
@@ -225,15 +227,29 @@ const Configure = () => {
>
The app will be deployed by the configured deployer
</Heading>
<span className="text-sm text-elements-high-em">
Enter LRN for deployer
</span>
<Controller
name="lrn"
control={methods.control}
rules={{ required: true }}
render={({ field: { value, onChange } }) => (
<Input value={value} onChange={onChange} />
render={({ field: { value, onChange }, fieldState }) => (
<FormControl fullWidth error={Boolean(fieldState.error)}>
<span className="text-sm text-elements-high-em mb-4">
Select deployer LRN
</span>
<Select
value={value}
onChange={(event) => onChange(event.target.value)}
displayEmpty
size='small'
>
{deployers.map((deployer) => (
<MenuItem key={deployer.deployerLrn} value={deployer.deployerLrn}>
{deployer.deployerLrn}
</MenuItem>
))}
</Select>
{fieldState.error && <FormHelperText>{fieldState.error.message}</FormHelperText>}
</FormControl>
)}
/>
</div>
@@ -91,7 +91,7 @@ const DeploymentDetailsCard = ({
}
};
const fetchDeploymentLogs = useCallback(async () => {
const fetchDeploymentLogs = async () => {
let url = `${deployment.deployer.deployerApiUrl}/log/${deployment.applicationDeploymentRequestId}`;
const res = await fetch(url, { cache: 'no-store' });
handleOpenDialog();
@@ -99,11 +99,7 @@ const DeploymentDetailsCard = ({
const logs = await res.text();
setDeploymentLogs(logs);
}
}, [
deployment.deployer.deployerApiUrl,
deployment.applicationDeploymentRequestId,
handleOpenDialog,
]);
};
const renderDeploymentStatus = useCallback(
(className?: string) => {
@@ -189,8 +185,8 @@ const DeploymentDetailsCard = ({
type="orange"
initials={getInitials(deployment.createdBy.name ?? '')}
className="lg:size-5 2xl:size-6"
// TODO: Add avatarUrl
// imageSrc={deployment.createdBy.avatarUrl}
// TODO: Add avatarUrl
// imageSrc={deployment.createdBy.avatarUrl}
></Avatar>
</div>
<OverflownText
@@ -23,6 +23,7 @@ import { useGQLClient } from 'context/GQLClientContext';
import { cn } from 'utils/classnames';
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
import { useToast } from 'components/shared/Toast';
import { DeleteDeploymentDialog } from 'components/projects/Dialog/DeleteDeploymentDialog';
interface DeploymentMenuProps extends ComponentPropsWithRef<'div'> {
deployment: Deployment;
@@ -46,6 +47,8 @@ export const DeploymentMenu = ({
const [changeToProduction, setChangeToProduction] = useState(false);
const [redeployToProduction, setRedeployToProduction] = useState(false);
const [deleteDeploymentDialog, setDeleteDeploymentDialog] = useState(false);
const [isConfirmDeleteLoading, setIsConfirmDeleteLoading] = useState(false);
const [rollbackDeployment, setRollbackDeployment] = useState(false);
const [assignDomainDialog, setAssignDomainDialog] = useState(false);
const [isConfirmButtonLoading, setConfirmButtonLoadingLoading] =
@@ -116,14 +119,11 @@ export const DeploymentMenu = ({
};
const deleteDeployment = async () => {
toast({
id: 'deleting_deployment',
title: 'Deleting deployment....',
variant: 'success',
onDismiss: dismiss,
});
const isDeleted = await client.deleteDeployment(deployment.id);
setIsConfirmDeleteLoading(false);
setDeleteDeploymentDialog((preVal) => !preVal);
if (isDeleted) {
await onUpdate();
toast({
@@ -212,7 +212,7 @@ export const DeploymentMenu = ({
</MenuItem>
<MenuItem
className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => deleteDeployment()}
onClick={() => setDeleteDeploymentDialog((preVal) => !preVal)}
>
<CrossCircleIcon /> Delete deployment
</MenuItem>
@@ -265,6 +265,15 @@ export const DeploymentMenu = ({
open={assignDomainDialog}
handleOpen={() => setAssignDomainDialog(!assignDomainDialog)}
/>
<DeleteDeploymentDialog
open={deleteDeploymentDialog}
handleConfirm={async () => {
setIsConfirmDeleteLoading(true);
await deleteDeployment();
}}
handleCancel={() => setDeleteDeploymentDialog((preVal) => !preVal)}
isConfirmButtonLoading={isConfirmDeleteLoading}
/>
</>
);
};
@@ -17,6 +17,16 @@ import { Button, Heading, Tag } from 'components/shared';
const WAIT_DURATION = 5000;
const DIALOG_STYLE = {
backgroundColor: 'rgba(0,0,0, .9)',
padding: '2em',
borderRadius: '0.5em',
marginLeft: '0.5em',
marginRight: '0.5em',
color: 'gray',
fontSize: 'small',
};
export const AuctionCard = ({ project }: { project: Project }) => {
const [auctionStatus, setAuctionStatus] = useState<string>('');
const [deployers, setDeployers] = useState<Deployer[]>([]);
@@ -86,13 +96,6 @@ export const AuctionCard = ({ project }: { project: Project }) => {
</Button>
</div>
<div className="flex justify-between items-center mt-1">
<span className="text-elements-high-em text-sm font-medium tracking-tight">
Auction Status
</span>
<div className="ml-2">{renderAuctionStatus()}</div>
</div>
<div className="flex justify-between items-center mt-2">
<span className="text-elements-high-em text-sm font-medium tracking-tight">
Auction Id
@@ -102,29 +105,47 @@ export const AuctionCard = ({ project }: { project: Project }) => {
</span>
</div>
{deployers?.length > 0 && (
<div className="mt-3">
<span className="text-elements-high-em text-sm font-medium tracking-tight">
Deployer LRNs
</span>
{deployers.map((deployer, index) => (
<p key={index} className="text-elements-mid-em text-sm">
{'\u2022'} {deployer.deployerLrn}
</p>
))}
</div>
)}
<div className="flex justify-between items-center mt-1">
<span className="text-elements-high-em text-sm font-medium tracking-tight">
Deployer Funds Status
Auction Status
</span>
<div className="ml-2">
<Tag size="xs" type={fundsStatus ? 'positive' : 'emphasized'}>
{fundsStatus ? 'RELEASED' : 'LOCKED'}
</Tag>
</div>
<div className="ml-2">{renderAuctionStatus()}</div>
</div>
{auctionStatus === 'completed' && (
<>
{deployers?.length > 0 ? (
<div>
<span className="text-elements-high-em text-sm font-medium tracking-tight">
Deployer LRNs
</span>
{deployers.map((deployer, index) => (
<p key={index} className="text-elements-mid-em text-sm">
{'\u2022'} {deployer.deployerLrn}
</p>
))}
<div className="flex justify-between items-center mt-1">
<span className="text-elements-high-em text-sm font-medium tracking-tight">
Deployer Funds Status
</span>
<div className="ml-2">
<Tag size="xs" type={fundsStatus ? 'positive' : 'emphasized'}>
{fundsStatus ? 'RELEASED' : 'LOCKED'}
</Tag>
</div>
</div>
</div>
) : (
<div className="mt-3">
<span className="text-elements-high-em text-sm font-medium tracking-tight">
No winning deployers
</span>
</div>
)}
</>
)}
</div>
<Dialog
@@ -134,7 +155,7 @@ export const AuctionCard = ({ project }: { project: Project }) => {
maxWidth="md"
>
<DialogTitle>Auction Details</DialogTitle>
<DialogContent>
<DialogContent style={DIALOG_STYLE}>
{auctionDetails && (
<pre>{JSON.stringify(auctionDetails, null, 2)}</pre>
)}