List deployer LRNs in deployment configuration step (#11)
All checks were successful
Lint / lint (20.x) (push) Successful in 4m56s

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: #11
This commit is contained in:
nabarun 2024-10-23 15:36:19 +00:00
parent 096318cf13
commit 3d9aedeb7e
17 changed files with 273 additions and 161 deletions

View File

@ -34,9 +34,8 @@ const nanoid = customAlphabet(lowercase + numbers, 8);
// TODO: Fix order of methods // TODO: Fix order of methods
export class Database { export class Database {
private dataSource: DataSource; private dataSource: DataSource;
private projectDomain: string;
constructor({ dbPath }: DatabaseConfig, { projectDomain }: MiscConfig) { constructor({ dbPath }: DatabaseConfig) {
this.dataSource = new DataSource({ this.dataSource = new DataSource({
type: 'better-sqlite3', type: 'better-sqlite3',
database: dbPath, database: dbPath,
@ -44,8 +43,6 @@ export class Database {
synchronize: true, synchronize: true,
logging: false logging: false
}); });
this.projectDomain = projectDomain;
} }
async init(): Promise<void> { async init(): Promise<void> {
@ -491,13 +488,13 @@ export class Database {
return projectRepository.save(newProject); return projectRepository.save(newProject);
} }
async saveProject (project: Project): Promise<Project> { async saveProject(project: Project): Promise<Project> {
const projectRepository = this.dataSource.getRepository(Project); const projectRepository = this.dataSource.getRepository(Project);
return projectRepository.save(project); return projectRepository.save(project);
} }
async updateProjectById ( async updateProjectById(
projectId: string, projectId: string,
data: DeepPartial<Project> data: DeepPartial<Project>
): Promise<boolean> { ): Promise<boolean> {
@ -583,17 +580,22 @@ export class Database {
return domains; return domains;
} }
async addDeployer (data: DeepPartial<Deployer>): Promise<Deployer> { async addDeployer(data: DeepPartial<Deployer>): Promise<Deployer> {
const deployerRepository = this.dataSource.getRepository(Deployer); const deployerRepository = this.dataSource.getRepository(Deployer);
const newDomain = await deployerRepository.save(data); const newDomain = await deployerRepository.save(data);
return newDomain; return newDomain;
} }
async getDeployerById (deployerId: string): Promise<Deployer | null> { async getDeployers(): Promise<Deployer[]> {
const deployerRepository = this.dataSource.getRepository(Deployer); const deployerRepository = this.dataSource.getRepository(Deployer);
const deployers = await deployerRepository.find();
return deployers;
}
const deployer = await deployerRepository.findOne({ where: { deployerId } }); async getDeployerByLRN(deployerLrn: string): Promise<Deployer | null> {
const deployerRepository = this.dataSource.getRepository(Deployer);
const deployer = await deployerRepository.findOne({ where: { deployerLrn } });
return deployer; return deployer;
} }

View File

@ -4,10 +4,10 @@ import { Project } from './Project';
@Entity() @Entity()
export class Deployer { export class Deployer {
@PrimaryColumn('varchar') @PrimaryColumn('varchar')
deployerId!: string; deployerLrn!: string;
@Column('varchar') @Column('varchar')
deployerLrn!: string; deployerId!: string;
@Column('varchar') @Column('varchar')
deployerApiUrl!: string; deployerApiUrl!: string;

View File

@ -129,7 +129,7 @@ export class Deployment {
applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null; applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null;
@ManyToOne(() => Deployer) @ManyToOne(() => Deployer)
@JoinColumn({ name: 'deployerId' }) @JoinColumn({ name: 'deployerLrn' })
deployer!: Deployer; deployer!: Deployer;
@Column({ @Column({

View File

@ -25,7 +25,7 @@ export const main = async (): Promise<void> => {
clientSecret: gitHub.oAuth.clientSecret, clientSecret: gitHub.oAuth.clientSecret,
}); });
const db = new Database(database, misc); const db = new Database(database);
await db.init(); await db.init();
const registry = new Registry(registryConfig); const registry = new Registry(registryConfig);

View File

@ -283,6 +283,7 @@ export class Registry {
this.registryConfig.privateKey, this.registryConfig.privateKey,
fee fee
); );
log(`Application deployment request record published: ${result.id}`); log(`Application deployment request record published: ${result.id}`);
log('Application deployment request data:', applicationDeploymentRequest); log('Application deployment request data:', applicationDeploymentRequest);

View File

@ -76,6 +76,10 @@ export const createResolvers = async (service: Service): Promise<any> => {
) => { ) => {
return service.getAuctionData(auctionId); return service.getAuctionData(auctionId);
}, },
deployers: async (_: any, __: any, context: any) => {
return service.getDeployers();
},
}, },
// TODO: Return error in GQL response // TODO: Return error in GQL response

View File

@ -134,8 +134,8 @@ type EnvironmentVariable {
} }
type Deployer { type Deployer {
deployerId: String!
deployerLrn: String! deployerLrn: String!
deployerId: String!
deployerApiUrl: String! deployerApiUrl: String!
createdAt: String! createdAt: String!
updatedAt: String! updatedAt: String!
@ -257,6 +257,7 @@ type Query {
searchProjects(searchText: String!): [Project!] searchProjects(searchText: String!): [Project!]
getAuctionData(auctionId: String!): Auction! getAuctionData(auctionId: String!): Auction!
domains(projectId: String!, filter: FilterDomainsInput): [Domain] domains(projectId: String!, filter: FilterDomainsInput): [Domain]
deployers: [Deployer]
} }
type Mutation { type Mutation {

View File

@ -21,6 +21,7 @@ import {
AppDeploymentRecord, AppDeploymentRecord,
AppDeploymentRemovalRecord, AppDeploymentRemovalRecord,
AuctionParams, AuctionParams,
DeployerRecord,
EnvironmentVariables, EnvironmentVariables,
GitPushEventPayload, GitPushEventPayload,
} from './types'; } from './types';
@ -309,35 +310,13 @@ export class Service {
if (!deployerRecords) { if (!deployerRecords) {
log(`No winning deployer for auction ${project!.auctionId}`); log(`No winning deployer for auction ${project!.auctionId}`);
} else { } else {
const deployerIds = []; const deployers = await this.saveDeployersByDeployerRecords(deployerRecords);
for (const deployer of deployers) {
for (const record of deployerRecords) { log(`Creating deployment for deployer ${deployer.deployerLrn}`);
const deployerId = record.id; await this.createDeploymentFromAuction(project, deployer);
const deployerLrn = record.names[0];
deployerIds.push(deployerId);
const deployerApiUrl = record.attributes.apiUrl;
const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
const deployerData = {
deployerId,
deployerLrn,
deployerApiUrl,
baseDomain
};
// Store the deployer in the DB
const deployer = await this.db.addDeployer(deployerData);
// Update project with deployer // Update project with deployer
await this.updateProjectWithDeployer(project.id, deployer); await this.updateProjectWithDeployer(project.id, deployer);
} }
for (const deployer of deployerIds) {
log(`Creating deployment for deployer LRN ${deployer}`);
await this.createDeploymentFromAuction(project, deployer);
}
} }
} }
@ -644,12 +623,12 @@ export class Service {
let deployer; let deployer;
if (deployerLrn) { if (deployerLrn) {
deployer = await this.createDeployerFromLRN(deployerLrn); deployer = await this.db.getDeployerByLRN(deployerLrn);
} else { } else {
deployer = data.deployer; deployer = data.deployer;
} }
const newDeployment = await this.createDeploymentFromData(userId, data, deployer!.deployerId!, applicationRecordId, applicationRecordData); const newDeployment = await this.createDeploymentFromData(userId, data, deployer!.deployerLrn!, applicationRecordId, applicationRecordData);
const { repo, repoUrl } = await getRepoDetails(octokit, data.project.repository, data.commitHash); const { repo, repoUrl } = await getRepoDetails(octokit, data.project.repository, data.commitHash);
const environmentVariablesObj = await this.getEnvVariables(data.project!.id!); const environmentVariablesObj = await this.getEnvVariables(data.project!.id!);
@ -687,7 +666,7 @@ export class Service {
async createDeploymentFromAuction( async createDeploymentFromAuction(
project: DeepPartial<Project>, project: DeepPartial<Project>,
deployerId: string deployer: Deployer
): Promise<Deployment> { ): Promise<Deployment> {
const octokit = await this.getOctokit(project.ownerId!); const octokit = await this.getOctokit(project.ownerId!);
const [owner, repo] = project.repository!.split('/'); const [owner, repo] = project.repository!.split('/');
@ -713,7 +692,6 @@ export class Service {
const applicationRecordId = record.id; const applicationRecordId = record.id;
const applicationRecordData = record.attributes; const applicationRecordData = record.attributes;
const deployer = await this.db.getDeployerById(deployerId);
const deployerLrn = deployer!.deployerLrn const deployerLrn = deployer!.deployerLrn
// Create deployment with prod branch and latest commit // Create deployment with prod branch and latest commit
@ -726,7 +704,7 @@ export class Service {
commitMessage: latestCommit.commit.message, commitMessage: latestCommit.commit.message,
}; };
const newDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerId, applicationRecordId, applicationRecordData); const newDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerLrn, applicationRecordId, applicationRecordData);
const environmentVariablesObj = await this.getEnvVariables(project!.id!); const environmentVariablesObj = await this.getEnvVariables(project!.id!);
// To set project DNS // To set project DNS
@ -767,7 +745,7 @@ export class Service {
async createDeploymentFromData( async createDeploymentFromData(
userId: string, userId: string,
data: DeepPartial<Deployment>, data: DeepPartial<Deployment>,
deployerId: string, deployerLrn: string,
applicationRecordId: string, applicationRecordId: string,
applicationRecordData: ApplicationRecord, applicationRecordData: ApplicationRecord,
): Promise<Deployment> { ): Promise<Deployment> {
@ -785,7 +763,7 @@ export class Service {
id: userId, id: userId,
}), }),
deployer: Object.assign(new Deployer(), { deployer: Object.assign(new Deployer(), {
deployerId, deployerLrn,
}), }),
}); });
@ -794,30 +772,6 @@ export class Service {
return newDeployment; return newDeployment;
} }
async createDeployerFromLRN(deployerLrn: string): Promise<Deployer | null> {
const records = await this.laconicRegistry.getRecordsByName(deployerLrn);
if (records.length === 0) {
log('No records found for deployer LRN:', deployerLrn);
return null;
}
const deployerId = records[0].id;
const deployerApiUrl = records[0].attributes.apiUrl;
const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
const deployerData = {
deployerId,
deployerLrn,
deployerApiUrl,
baseDomain
};
const deployer = await this.db.addDeployer(deployerData);
return deployer;
}
async updateProjectWithDeployer( async updateProjectWithDeployer(
projectId: string, projectId: string,
deployer: Deployer deployer: Deployer
@ -1089,7 +1043,7 @@ export class Service {
let newDeployment: Deployment; let newDeployment: Deployment;
if (oldDeployment.project.auctionId) { if (oldDeployment.project.auctionId) {
newDeployment = await this.createDeploymentFromAuction(oldDeployment.project, oldDeployment.deployer.deployerId); newDeployment = await this.createDeploymentFromAuction(oldDeployment.project, oldDeployment.deployer);
} else { } else {
newDeployment = await this.createDeployment(user.id, octokit, newDeployment = await this.createDeployment(user.id, octokit,
{ {
@ -1369,4 +1323,50 @@ export class Service {
return false; return false;
} }
async getDeployers(): Promise<Deployer[]> {
const dbDeployers = await this.db.getDeployers();
if (dbDeployers.length > 0) {
// Call asynchronously to fetch the records from the registry and update the DB
this.updateDeployersFromRegistry();
return dbDeployers;
} else {
// Fetch from the registry and populate empty DB
return await this.updateDeployersFromRegistry();
}
}
async updateDeployersFromRegistry(): Promise<Deployer[]> {
const deployerRecords = await this.laconicRegistry.getDeployerRecordsByFilter({});
await this.saveDeployersByDeployerRecords(deployerRecords);
return await this.db.getDeployers();
}
async saveDeployersByDeployerRecords(deployerRecords: DeployerRecord[]): Promise<Deployer[]> {
const deployers: Deployer[] = [];
for (const record of deployerRecords) {
if (record.names.length > 0) {
const deployerId = record.id;
const deployerLrn = record.names[0];
const deployerApiUrl = record.attributes.apiUrl;
const baseDomain = deployerApiUrl.substring(deployerApiUrl.indexOf('.') + 1);
const deployerData = {
deployerLrn,
deployerId,
deployerApiUrl,
baseDomain
};
// TODO: Update deployers table in a separate job
const deployer = await this.db.addDeployer(deployerData);
deployers.push(deployer);
}
}
return deployers;
}
} }

View File

@ -38,7 +38,7 @@ async function main() {
}); });
for await (const deployment of deployments) { for await (const deployment of deployments) {
const url = `https://${deployment.project.name}-${deployment.id}.${misc.projectDomain}`; const url = `https://${(deployment.project.name).toLowerCase()}-${deployment.id}.${deployment.deployer.baseDomain}`;
const applicationDeploymentRecord = { const applicationDeploymentRecord = {
type: 'ApplicationDeploymentRecord', type: 'ApplicationDeploymentRecord',
@ -73,7 +73,7 @@ async function main() {
// Remove deployment for project subdomain if deployment is for production environment // Remove deployment for project subdomain if deployment is for production environment
if (deployment.environment === Environment.Production) { if (deployment.environment === Environment.Production) {
applicationDeploymentRecord.url = `https://${deployment.project.name}.${deployment.baseDomain}`; applicationDeploymentRecord.url = `https://${deployment.project.name}.${deployment.deployer.baseDomain}`;
await registry.setRecord( await registry.setRecord(
{ {

View File

@ -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>
);
};

View File

@ -1,9 +1,11 @@
import { useCallback, useState } from 'react'; import { useCallback, useState, useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import { FormProvider, FieldValues } from 'react-hook-form'; import { FormProvider, FieldValues } from 'react-hook-form';
import { useNavigate, useSearchParams } from 'react-router-dom'; import { useNavigate, useSearchParams } from 'react-router-dom';
import { useMediaQuery } from 'usehooks-ts'; 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 { import {
ArrowRightCircleFilledIcon, ArrowRightCircleFilledIcon,
@ -11,7 +13,6 @@ import {
} from 'components/shared/CustomIcon'; } from 'components/shared/CustomIcon';
import { Heading } from '../../shared/Heading'; import { Heading } from '../../shared/Heading';
import { Button } from '../../shared/Button'; import { Button } from '../../shared/Button';
import { Select, SelectOption } from 'components/shared/Select';
import { Input } from 'components/shared/Input'; import { Input } from 'components/shared/Input';
import { useToast } from 'components/shared/Toast'; import { useToast } from 'components/shared/Toast';
import { useGQLClient } from '../../../context/GQLClientContext'; import { useGQLClient } from '../../../context/GQLClientContext';
@ -30,6 +31,8 @@ type ConfigureFormValues = ConfigureDeploymentFormValues &
const Configure = () => { const Configure = () => {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [deployers, setDeployers] = useState<Deployer[]>([]);
const [searchParams] = useSearchParams(); const [searchParams] = useSearchParams();
const templateId = searchParams.get('templateId'); const templateId = searchParams.get('templateId');
const queryParams = new URLSearchParams(location.search); const queryParams = new URLSearchParams(location.search);
@ -48,7 +51,7 @@ const Configure = () => {
const client = useGQLClient(); const client = useGQLClient();
const methods = useForm<ConfigureFormValues>({ const methods = useForm<ConfigureFormValues>({
defaultValues: { option: 'LRN' }, defaultValues: { option: 'Auction' },
}); });
const selectedOption = methods.watch('option'); const selectedOption = methods.watch('option');
@ -171,6 +174,15 @@ const Configure = () => {
[client, createProject, dismiss, toast], [client, createProject, dismiss, toast],
); );
const fetchDeployers = useCallback(async () => {
const res = await client.getDeployers()
setDeployers(res.deployers)
}, [client])
useEffect(() => {
fetchDeployers()
}, [])
return ( return (
<div className="space-y-7 px-4 py-6"> <div className="space-y-7 px-4 py-6">
<div className="flex justify-between mb-6"> <div className="flex justify-between mb-6">
@ -195,24 +207,14 @@ const Configure = () => {
control={methods.control} control={methods.control}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<Select <Select
label="Configuration Options" value={value}
value={ onChange={(event) => onChange(event.target.value)}
{ size='small'
value: value || 'LRN', displayEmpty
label: >
value === 'Auction' <MenuItem value="LRN">Deployer LRN</MenuItem>
? 'Create Auction' <MenuItem value="Auction">Create Auction</MenuItem>
: 'Deployer LRN', </Select>
} as SelectOption
}
onChange={(value) =>
onChange((value as SelectOption).value)
}
options={[
{ value: 'LRN', label: 'Deployer LRN' },
{ value: 'Auction', label: 'Create Auction' },
]}
/>
)} )}
/> />
</div> </div>
@ -225,15 +227,29 @@ const Configure = () => {
> >
The app will be deployed by the configured deployer The app will be deployed by the configured deployer
</Heading> </Heading>
<span className="text-sm text-elements-high-em">
Enter LRN for deployer
</span>
<Controller <Controller
name="lrn" name="lrn"
control={methods.control} control={methods.control}
rules={{ required: true }} rules={{ required: true }}
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange }, fieldState }) => (
<Input value={value} onChange={onChange} /> <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> </div>

View File

@ -91,7 +91,7 @@ const DeploymentDetailsCard = ({
} }
}; };
const fetchDeploymentLogs = useCallback(async () => { const fetchDeploymentLogs = async () => {
let url = `${deployment.deployer.deployerApiUrl}/log/${deployment.applicationDeploymentRequestId}`; let url = `${deployment.deployer.deployerApiUrl}/log/${deployment.applicationDeploymentRequestId}`;
const res = await fetch(url, { cache: 'no-store' }); const res = await fetch(url, { cache: 'no-store' });
handleOpenDialog(); handleOpenDialog();
@ -99,11 +99,7 @@ const DeploymentDetailsCard = ({
const logs = await res.text(); const logs = await res.text();
setDeploymentLogs(logs); setDeploymentLogs(logs);
} }
}, [ };
deployment.deployer.deployerApiUrl,
deployment.applicationDeploymentRequestId,
handleOpenDialog,
]);
const renderDeploymentStatus = useCallback( const renderDeploymentStatus = useCallback(
(className?: string) => { (className?: string) => {

View File

@ -23,6 +23,7 @@ import { useGQLClient } from 'context/GQLClientContext';
import { cn } from 'utils/classnames'; import { cn } from 'utils/classnames';
import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog'; import { ChangeStateToProductionDialog } from 'components/projects/Dialog/ChangeStateToProductionDialog';
import { useToast } from 'components/shared/Toast'; import { useToast } from 'components/shared/Toast';
import { DeleteDeploymentDialog } from 'components/projects/Dialog/DeleteDeploymentDialog';
interface DeploymentMenuProps extends ComponentPropsWithRef<'div'> { interface DeploymentMenuProps extends ComponentPropsWithRef<'div'> {
deployment: Deployment; deployment: Deployment;
@ -46,6 +47,8 @@ export const DeploymentMenu = ({
const [changeToProduction, setChangeToProduction] = useState(false); const [changeToProduction, setChangeToProduction] = useState(false);
const [redeployToProduction, setRedeployToProduction] = useState(false); const [redeployToProduction, setRedeployToProduction] = useState(false);
const [deleteDeploymentDialog, setDeleteDeploymentDialog] = useState(false);
const [isConfirmDeleteLoading, setIsConfirmDeleteLoading] = useState(false);
const [rollbackDeployment, setRollbackDeployment] = useState(false); const [rollbackDeployment, setRollbackDeployment] = useState(false);
const [assignDomainDialog, setAssignDomainDialog] = useState(false); const [assignDomainDialog, setAssignDomainDialog] = useState(false);
const [isConfirmButtonLoading, setConfirmButtonLoadingLoading] = const [isConfirmButtonLoading, setConfirmButtonLoadingLoading] =
@ -116,14 +119,11 @@ export const DeploymentMenu = ({
}; };
const deleteDeployment = async () => { const deleteDeployment = async () => {
toast({
id: 'deleting_deployment',
title: 'Deleting deployment....',
variant: 'success',
onDismiss: dismiss,
});
const isDeleted = await client.deleteDeployment(deployment.id); const isDeleted = await client.deleteDeployment(deployment.id);
setIsConfirmDeleteLoading(false);
setDeleteDeploymentDialog((preVal) => !preVal);
if (isDeleted) { if (isDeleted) {
await onUpdate(); await onUpdate();
toast({ toast({
@ -212,7 +212,7 @@ export const DeploymentMenu = ({
</MenuItem> </MenuItem>
<MenuItem <MenuItem
className="hover:bg-base-bg-emphasized flex items-center gap-3" className="hover:bg-base-bg-emphasized flex items-center gap-3"
onClick={() => deleteDeployment()} onClick={() => setDeleteDeploymentDialog((preVal) => !preVal)}
> >
<CrossCircleIcon /> Delete deployment <CrossCircleIcon /> Delete deployment
</MenuItem> </MenuItem>
@ -265,6 +265,15 @@ export const DeploymentMenu = ({
open={assignDomainDialog} open={assignDomainDialog}
handleOpen={() => setAssignDomainDialog(!assignDomainDialog)} handleOpen={() => setAssignDomainDialog(!assignDomainDialog)}
/> />
<DeleteDeploymentDialog
open={deleteDeploymentDialog}
handleConfirm={async () => {
setIsConfirmDeleteLoading(true);
await deleteDeployment();
}}
handleCancel={() => setDeleteDeploymentDialog((preVal) => !preVal)}
isConfirmButtonLoading={isConfirmDeleteLoading}
/>
</> </>
); );
}; };

View File

@ -17,6 +17,16 @@ import { Button, Heading, Tag } from 'components/shared';
const WAIT_DURATION = 5000; 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 }) => { export const AuctionCard = ({ project }: { project: Project }) => {
const [auctionStatus, setAuctionStatus] = useState<string>(''); const [auctionStatus, setAuctionStatus] = useState<string>('');
const [deployers, setDeployers] = useState<Deployer[]>([]); const [deployers, setDeployers] = useState<Deployer[]>([]);
@ -86,13 +96,6 @@ export const AuctionCard = ({ project }: { project: Project }) => {
</Button> </Button>
</div> </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"> <div className="flex justify-between items-center mt-2">
<span className="text-elements-high-em text-sm font-medium tracking-tight"> <span className="text-elements-high-em text-sm font-medium tracking-tight">
Auction Id Auction Id
@ -102,8 +105,18 @@ export const AuctionCard = ({ project }: { project: Project }) => {
</span> </span>
</div> </div>
{deployers?.length > 0 && (
<div className="mt-3"> <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>
{auctionStatus === 'completed' && (
<>
{deployers?.length > 0 ? (
<div>
<span className="text-elements-high-em text-sm font-medium tracking-tight"> <span className="text-elements-high-em text-sm font-medium tracking-tight">
Deployer LRNs Deployer LRNs
</span> </span>
@ -112,8 +125,6 @@ export const AuctionCard = ({ project }: { project: Project }) => {
{'\u2022'} {deployer.deployerLrn} {'\u2022'} {deployer.deployerLrn}
</p> </p>
))} ))}
</div>
)}
<div className="flex justify-between items-center mt-1"> <div className="flex justify-between items-center mt-1">
<span className="text-elements-high-em text-sm font-medium tracking-tight"> <span className="text-elements-high-em text-sm font-medium tracking-tight">
@ -126,6 +137,16 @@ export const AuctionCard = ({ project }: { project: Project }) => {
</div> </div>
</div> </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 <Dialog
open={openDialog} open={openDialog}
@ -134,7 +155,7 @@ export const AuctionCard = ({ project }: { project: Project }) => {
maxWidth="md" maxWidth="md"
> >
<DialogTitle>Auction Details</DialogTitle> <DialogTitle>Auction Details</DialogTitle>
<DialogContent> <DialogContent style={DIALOG_STYLE}>
{auctionDetails && ( {auctionDetails && (
<pre>{JSON.stringify(auctionDetails, null, 2)}</pre> <pre>{JSON.stringify(auctionDetails, null, 2)}</pre>
)} )}

View File

@ -424,4 +424,12 @@ export class GQLClient {
return data.getAuctionData; return data.getAuctionData;
} }
async getDeployers(): Promise<types.GetDeployersResponse> {
const { data } = await this.client.query({
query: queries.getDeployers,
});
return data;
}
} }

View File

@ -25,9 +25,9 @@ query ($projectId: String!) {
prodBranch prodBranch
auctionId auctionId
deployers { deployers {
deployerApiUrl
deployerId
deployerLrn deployerLrn
deployerId
deployerApiUrl
} }
fundsReleased fundsReleased
framework framework
@ -81,9 +81,9 @@ query ($organizationSlug: String!) {
framework framework
auctionId auctionId
deployers { deployers {
deployerApiUrl
deployerId
deployerLrn deployerLrn
deployerId
deployerApiUrl
} }
fundsReleased fundsReleased
prodBranch prodBranch
@ -145,9 +145,9 @@ query ($projectId: String!) {
commitMessage commitMessage
url url
deployer { deployer {
deployerLrn
deployerId deployerId
deployerLrn, deployerApiUrl
deployerApiUrl,
} }
environment environment
isCurrent isCurrent
@ -208,9 +208,9 @@ query ($searchText: String!) {
framework framework
auctionId auctionId
deployers { deployers {
deployerApiUrl
deployerId
deployerLrn deployerLrn
deployerId
deployerApiUrl
} }
fundsReleased fundsReleased
prodBranch prodBranch
@ -307,3 +307,13 @@ query ($auctionId: String!) {
} }
} }
`; `;
export const getDeployers = gql`
query {
deployers {
deployerLrn
deployerId
deployerApiUrl
}
}
`;

View File

@ -117,9 +117,9 @@ export type Deployment = {
}; };
export type Deployer = { export type Deployer = {
deployerApiUrl: string;
deployerId: string;
deployerLrn: string; deployerLrn: string;
deployerId: string;
deployerApiUrl: string;
} }
export type OrganizationMember = { export type OrganizationMember = {
@ -233,6 +233,10 @@ export type GetDomainsResponse = {
domains: Domain[]; domains: Domain[];
}; };
export type GetDeployersResponse = {
deployers: Deployer[];
};
export type SearchProjectsResponse = { export type SearchProjectsResponse = {
searchProjects: Project[]; searchProjects: Project[];
}; };