From 5152952a45046c222cc208fd4fd105e722822e05 Mon Sep 17 00:00:00 2001 From: nabarun Date: Tue, 22 Oct 2024 09:12:59 +0000 Subject: [PATCH] Display deployment build logs (#8) Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75) Co-authored-by: Adw8 Co-authored-by: Neeraj Co-authored-by: IshaVenikar Reviewed-on: https://git.vdb.to/cerc-io/snowballtools-base/pulls/8 --- packages/backend/src/database.ts | 27 ++- packages/backend/src/entity/Deployer.ts | 20 ++ packages/backend/src/entity/Deployment.ts | 9 +- packages/backend/src/entity/Project.ts | 13 +- packages/backend/src/registry.ts | 43 ++-- packages/backend/src/schema.gql | 11 +- packages/backend/src/service.ts | 188 +++++++++++++----- packages/backend/src/types.ts | 17 ++ .../components/projects/create/Configure.tsx | 24 +-- .../deployments/DeploymentDetailsCard.tsx | 49 ++++- .../frontend/src/pages/components/modals.tsx | 7 +- .../frontend/src/stories/MockStoriesData.ts | 7 +- packages/gql-client/src/queries.ts | 7 +- packages/gql-client/src/types.ts | 9 +- 14 files changed, 316 insertions(+), 115 deletions(-) create mode 100644 packages/backend/src/entity/Deployer.ts diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 5f840da5..5b681c3f 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -23,6 +23,7 @@ import { EnvironmentVariable } from './entity/EnvironmentVariable'; import { Domain } from './entity/Domain'; import { getEntities, loadAndSaveData } from './utils'; import { UserOrganization } from './entity/UserOrganization'; +import { Deployer } from './entity/Deployer'; const ORGANIZATION_DATA_PATH = '../test/fixtures/organizations.json'; @@ -215,7 +216,8 @@ export class Database { relations: { project: true, domain: true, - createdBy: true + createdBy: true, + deployer: true, }, where: { project: { @@ -488,7 +490,13 @@ export class Database { return projectRepository.save(newProject); } - async updateProjectById( + async saveProject (project: Project): Promise { + const projectRepository = this.dataSource.getRepository(Project); + + return projectRepository.save(project); + } + + async updateProjectById ( projectId: string, data: DeepPartial ): Promise { @@ -573,4 +581,19 @@ export class Database { return domains; } + + async addDeployer (data: DeepPartial): Promise { + const deployerRepository = this.dataSource.getRepository(Deployer); + const newDomain = await deployerRepository.save(data); + + return newDomain; + } + + async getDeployerById (deployerId: string): Promise { + const deployerRepository = this.dataSource.getRepository(Deployer); + + const deployer = await deployerRepository.findOne({ where: { deployerId } }); + + return deployer; + } } diff --git a/packages/backend/src/entity/Deployer.ts b/packages/backend/src/entity/Deployer.ts new file mode 100644 index 00000000..bdb920e3 --- /dev/null +++ b/packages/backend/src/entity/Deployer.ts @@ -0,0 +1,20 @@ +import { Entity, PrimaryColumn, Column, ManyToMany } from 'typeorm'; +import { Project } from './Project'; + +@Entity() +export class Deployer { + @PrimaryColumn('varchar') + deployerId!: string; + + @Column('varchar') + deployerLrn!: string; + + @Column('varchar') + deployerApiUrl!: string; + + @Column('varchar') + baseDomain!: string; + + @ManyToMany(() => Project, (project) => project.deployers) + projects!: Project[]; +} diff --git a/packages/backend/src/entity/Deployment.ts b/packages/backend/src/entity/Deployment.ts index 0036aed9..21bfb4e4 100644 --- a/packages/backend/src/entity/Deployment.ts +++ b/packages/backend/src/entity/Deployment.ts @@ -13,6 +13,7 @@ import { import { Project } from './Project'; import { Domain } from './Domain'; import { User } from './User'; +import { Deployer } from './Deployer'; import { AppDeploymentRecordAttributes, AppDeploymentRemovalRecordAttributes } from '../types'; export enum Environment { @@ -127,8 +128,9 @@ export class Deployment { @Column('simple-json', { nullable: true }) applicationDeploymentRemovalRecordData!: AppDeploymentRemovalRecordAttributes | null; - @Column('varchar') - deployerLrn!: string; + @ManyToOne(() => Deployer) + @JoinColumn({ name: 'deployerId' }) + deployer!: Deployer; @Column({ enum: Environment @@ -138,9 +140,6 @@ export class Deployment { @Column('boolean', { default: false }) isCurrent!: boolean; - @Column('varchar', { nullable: true }) - baseDomain!: string | null; - @Column({ enum: DeploymentStatus }) diff --git a/packages/backend/src/entity/Project.ts b/packages/backend/src/entity/Project.ts index d9696767..9edfdfd5 100644 --- a/packages/backend/src/entity/Project.ts +++ b/packages/backend/src/entity/Project.ts @@ -7,13 +7,16 @@ import { ManyToOne, JoinColumn, OneToMany, - DeleteDateColumn + DeleteDateColumn, + JoinTable, + ManyToMany } from 'typeorm'; import { User } from './User'; import { Organization } from './Organization'; import { ProjectMember } from './ProjectMember'; import { Deployment } from './Deployment'; +import { Deployer } from './Deployer'; @Entity() export class Project { @@ -49,8 +52,9 @@ export class Project { @Column('varchar', { nullable: true }) auctionId!: string | null; - @Column({ type: 'simple-array', nullable: true }) - deployerLrns!: string[] | null; + @ManyToMany(() => Deployer, (deployer) => (deployer.projects)) + @JoinTable() + deployers!: Deployer[] @Column('boolean', { default: false, nullable: true }) fundsReleased!: boolean; @@ -70,9 +74,6 @@ export class Project { @Column('varchar') icon!: string; - @Column({ type: 'simple-array', nullable: true }) - baseDomains!: string[] | null; - @CreateDateColumn() createdAt!: Date; diff --git a/packages/backend/src/registry.ts b/packages/backend/src/registry.ts index 5368d36f..f4df1353 100644 --- a/packages/backend/src/registry.ts +++ b/packages/backend/src/registry.ts @@ -14,7 +14,7 @@ import { ApplicationDeploymentRequest, ApplicationDeploymentRemovalRequest } from './entity/Deployment'; -import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams } from './types'; +import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams, DeployerRecord } from './types'; import { getConfig, getRepoDetails, sleep } from './utils'; const log = debug('snowball:registry'); @@ -25,6 +25,7 @@ const APP_DEPLOYMENT_REQUEST_TYPE = 'ApplicationDeploymentRequest'; const APP_DEPLOYMENT_REMOVAL_REQUEST_TYPE = 'ApplicationDeploymentRemovalRequest'; const APP_DEPLOYMENT_RECORD_TYPE = 'ApplicationDeploymentRecord'; const APP_DEPLOYMENT_REMOVAL_RECORD_TYPE = 'ApplicationDeploymentRemovalRecord'; +const WEBAPP_DEPLOYER_RECORD_TYPE = 'WebappDeployer' const SLEEP_DURATION = 1000; // TODO: Move registry code to registry-sdk/watcher-ts @@ -291,32 +292,29 @@ export class Registry { }; } - async getAuctionWinningDeployers( + async getAuctionWinningDeployerRecords( auctionId: string - ): Promise { + ): Promise { const records = await this.registry.getAuctionsByIds([auctionId]); const auctionResult = records[0]; - let deployerLrns = []; + let deployerRecords = []; const { winnerAddresses } = auctionResult; for (const auctionWinner of winnerAddresses) { - const deployerRecords = await this.registry.queryRecords( - { - paymentAddress: auctionWinner, - }, - true - ); + const records = await this.getDeployerRecordsByFilter({ + paymentAddress: auctionWinner, + }); - for (const record of deployerRecords) { - if (record.names && record.names.length > 0) { - deployerLrns.push(record.names[0]); + for (const record of records) { + if (record.id) { + deployerRecords.push(record); break; } } } - return deployerLrns; + return deployerRecords; } async releaseDeployerFunds( @@ -359,6 +357,19 @@ export class Registry { ); } + /** + * Fetch WebappDeployer Records by filter + */ + async getDeployerRecordsByFilter(filter: { [key: string]: any }): Promise { + return this.registry.queryRecords( + { + type: WEBAPP_DEPLOYER_RECORD_TYPE, + ...filter + }, + true + ); + } + /** * Fetch ApplicationDeploymentRecords by filter */ @@ -439,8 +450,8 @@ export class Registry { const auctions = await this.registry.getAuctionsByIds(auctionIds); const completedAuctions = auctions - .filter((auction: { id: string, status: string }) => auction.status === 'completed') - .map((auction: { id: string, status: string }) => auction.id); + .filter((auction: { id: string, status: string }) => auction.status === 'completed') + .map((auction: { id: string, status: string }) => auction.id); return completedAuctions; } diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 8c222fb2..1d788b67 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -104,7 +104,8 @@ type Deployment { commitMessage: String! url: String environment: Environment! - deployerLrn: String + deployer: Deployer + applicationDeploymentRequestId: String isCurrent: Boolean! baseDomain: String status: DeploymentStatus! @@ -132,6 +133,14 @@ type EnvironmentVariable { updatedAt: String! } +type Deployer { + deployerId: String! + deployerLrn: String! + deployerApiUrl: String! + createdAt: String! + updatedAt: String! +} + type AuthResult { token: String! } diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 3f7e7f98..318e0c9b 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -14,6 +14,7 @@ import { Project } from './entity/Project'; import { Permission, ProjectMember } from './entity/ProjectMember'; import { User } from './entity/User'; import { Registry } from './registry'; +import { Deployer } from './entity/Deployer'; import { GitHubConfig, RegistryConfig } from './config'; import { AddProjectFromTemplateInput, @@ -174,6 +175,7 @@ export class Service { applicationDeploymentRequestId: record.attributes.request, })), relations: { + deployer: true, project: true, }, order: { @@ -193,37 +195,31 @@ export class Service { const deploymentUpdatePromises = records.map(async (record) => { const deployment = recordToDeploymentsMap[record.attributes.request]; - const parts = record.attributes.url.replace('https://', '').split('.'); - const baseDomain = parts.slice(1).join('.'); + if (!deployment.project) { + log(`Project ${deployment.projectId} not found`); + return; + } else { + deployment.applicationDeploymentRecordId = record.id; + deployment.applicationDeploymentRecordData = record.attributes; + deployment.url = record.attributes.url; + deployment.status = DeploymentStatus.Ready; + deployment.isCurrent = deployment.environment === Environment.Production; - deployment.applicationDeploymentRecordId = record.id; - deployment.applicationDeploymentRecordData = record.attributes; - deployment.url = record.attributes.url; - deployment.baseDomain = baseDomain; - deployment.status = DeploymentStatus.Ready; - deployment.isCurrent = deployment.environment === Environment.Production; + await this.db.updateDeploymentById(deployment.id, deployment); - await this.db.updateDeploymentById(deployment.id, deployment); + // Release deployer funds on successful deployment + if (!deployment.project.fundsReleased) { + const fundsReleased = await this.releaseDeployerFundsByProjectId(deployment.projectId); - const baseDomains = deployment.project.baseDomains || []; + await this.db.updateProjectById(deployment.projectId, { + fundsReleased, + }); + } - if (!baseDomains.includes(baseDomain)) { - baseDomains.push(baseDomain); + log( + `Updated deployment ${deployment.id} with URL ${record.attributes.url}`, + ); } - - // Release deployer funds on successful deployment - if (!deployment.project.fundsReleased) { - const fundsReleased = await this.releaseDeployerFundsByProjectId(deployment.projectId); - - await this.db.updateProjectById(deployment.projectId, { - baseDomains, - fundsReleased, - }); - } - - log( - `Updated deployment ${deployment.id} with URL ${record.attributes.url}`, - ); }); await Promise.all(deploymentUpdatePromises); @@ -235,7 +231,7 @@ export class Service { for (const deployment of prodDeployments) { const projectDeployments = await this.db.getDeploymentsByProjectId(deployment.projectId); const oldDeployments = projectDeployments - .filter(projectDeployment => projectDeployment.deployerLrn === deployment.deployerLrn && projectDeployment.id !== deployment.id); + .filter(projectDeployment => projectDeployment.deployer.deployerLrn === deployment.deployer.deployerLrn && projectDeployment.id !== deployment.id); for (const oldDeployment of oldDeployments) { await this.db.updateDeployment( { id: oldDeployment.id }, @@ -308,17 +304,37 @@ export class Service { ); for (const project of projectsToBedeployed) { - const deployerLrns = await this.laconicRegistry.getAuctionWinningDeployers(project.auctionId!); + const deployerRecords = await this.laconicRegistry.getAuctionWinningDeployerRecords(project!.auctionId!); - if (!deployerLrns) { + if (!deployerRecords) { log(`No winning deployer for auction ${project!.auctionId}`); } else { - // Update project with deployer LRNs - await this.db.updateProjectById(project.id!, { - deployerLrns - }); + const deployerIds = []; - for (const deployer of deployerLrns) { + for (const record of deployerRecords) { + const deployerId = record.id; + 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 + await this.updateProjectWithDeployer(project.id, deployer); + } + + for (const deployer of deployerIds) { log(`Creating deployment for deployer LRN ${deployer}`); await this.createDeploymentFromAuction(project, deployer); } @@ -587,7 +603,7 @@ export class Service { domain: prodBranchDomains[0], commitHash: oldDeployment.commitHash, commitMessage: oldDeployment.commitMessage, - deployerLrn: oldDeployment.deployerLrn + deployer: oldDeployment.deployer }); return newDeployment; @@ -596,7 +612,8 @@ export class Service { async createDeployment( userId: string, octokit: Octokit, - data: DeepPartial + data: DeepPartial, + deployerLrn?: string ): Promise { assert(data.project?.repository, 'Project repository not found'); log( @@ -625,7 +642,14 @@ export class Service { ); } - const newDeployment = await this.createDeploymentFromData(userId, data, data.deployerLrn!, applicationRecordId, applicationRecordData); + let deployer; + if (deployerLrn) { + deployer = await this.createDeployerFromLRN(deployerLrn); + } else { + deployer = data.deployer; + } + + const newDeployment = await this.createDeploymentFromData(userId, data, deployer!.deployerId!, applicationRecordId, applicationRecordData); const { repo, repoUrl } = await getRepoDetails(octokit, data.project.repository, data.commitHash); const environmentVariablesObj = await this.getEnvVariables(data.project!.id!); @@ -639,7 +663,7 @@ export class Service { repository: repoUrl, environmentVariables: environmentVariablesObj, dns: `${newDeployment.project.name}`, - lrn: data.deployerLrn! + lrn: deployer!.deployerLrn! }); } @@ -648,7 +672,7 @@ export class Service { deployment: newDeployment, appName: repo, repository: repoUrl, - lrn: data.deployerLrn!, + lrn: deployer!.deployerLrn!, environmentVariables: environmentVariablesObj, dns: `${newDeployment.project.name}-${newDeployment.id}`, }); @@ -663,7 +687,7 @@ export class Service { async createDeploymentFromAuction( project: DeepPartial, - deployerLrn: string + deployerId: string ): Promise { const octokit = await this.getOctokit(project.ownerId!); const [owner, repo] = project.repository!.split('/'); @@ -689,6 +713,9 @@ export class Service { const applicationRecordId = record.id; const applicationRecordData = record.attributes; + const deployer = await this.db.getDeployerById(deployerId); + const deployerLrn = deployer!.deployerLrn + // Create deployment with prod branch and latest commit const deploymentData = { project, @@ -699,7 +726,7 @@ export class Service { commitMessage: latestCommit.commit.message, }; - const newDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerLrn, applicationRecordId, applicationRecordData); + const newDeployment = await this.createDeploymentFromData(project.ownerId!, deploymentData, deployerId, applicationRecordId, applicationRecordData); const environmentVariablesObj = await this.getEnvVariables(project!.id!); // To set project DNS @@ -740,7 +767,7 @@ export class Service { async createDeploymentFromData( userId: string, data: DeepPartial, - deployerLrn: string, + deployerId: string, applicationRecordId: string, applicationRecordData: ApplicationRecord, ): Promise { @@ -757,7 +784,9 @@ export class Service { createdBy: Object.assign(new User(), { id: userId, }), - deployerLrn, + deployer: Object.assign(new Deployer(), { + deployerId, + }), }); log(`Created deployment ${newDeployment.id}`); @@ -765,6 +794,50 @@ export class Service { return newDeployment; } + async createDeployerFromLRN(deployerLrn: string): Promise { + 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( + projectId: string, + deployer: Deployer + ): Promise { + const deploymentProject = await this.db.getProjects({ + where: { id: projectId }, + relations: ['deployers'] + }); + + if (!deploymentProject[0].deployers) { + deploymentProject[0].deployers = []; + } + + deploymentProject[0].deployers.push(deployer); + + await this.db.saveProject(deploymentProject[0]); + + return deployer; + } + async addProjectFromTemplate( user: User, organizationSlug: string, @@ -828,6 +901,7 @@ export class Service { slug: organizationSlug, }, }); + if (!organization) { throw new Error('Organization does not exist'); } @@ -858,15 +932,15 @@ export class Service { domain: null, commitHash: latestCommit.sha, commitMessage: latestCommit.commit.message, - deployerLrn: lrn }; if (auctionParams) { const { applicationDeploymentAuctionId } = await this.laconicRegistry.createApplicationDeploymentAuction(repo, octokit, auctionParams!, deploymentData); - await this.updateProject(project.id, { auctionId: applicationDeploymentAuctionId }) + await this.updateProject(project.id, { auctionId: applicationDeploymentAuctionId }); } else { - await this.createDeployment(user.id, octokit, deploymentData); - await this.updateProject(project.id, { deployerLrns: [lrn!] }) + const newDeployment = await this.createDeployment(user.id, octokit, deploymentData, lrn); + // Update project with deployer + await this.updateProjectWithDeployer(newDeployment.projectId, newDeployment.deployer); } await this.createRepoHook(octokit, project); @@ -920,6 +994,9 @@ export class Service { ); const projects = await this.db.getProjects({ where: { repository: repository.full_name }, + relations: { + deployers: true, + } }); if (!projects.length) { @@ -936,7 +1013,7 @@ export class Service { branch, }); - const deployers = project.deployerLrns; + const deployers = project.deployers; if (!deployers) { log(`No deployer present for project ${project.id}`) return; @@ -955,7 +1032,7 @@ export class Service { domain, commitHash: headCommit.id, commitMessage: headCommit.message, - deployerLrn: deployer + deployer: deployer }, ); } @@ -995,6 +1072,7 @@ export class Service { relations: { project: true, domain: true, + deployer: true, createdBy: true, }, where: { @@ -1011,8 +1089,7 @@ export class Service { let newDeployment: Deployment; if (oldDeployment.project.auctionId) { - // TODO: Discuss creating applicationRecord for redeployments - newDeployment = await this.createDeploymentFromAuction(oldDeployment.project, oldDeployment.deployerLrn); + newDeployment = await this.createDeploymentFromAuction(oldDeployment.project, oldDeployment.deployer.deployerId); } else { newDeployment = await this.createDeployment(user.id, octokit, { @@ -1023,7 +1100,7 @@ export class Service { domain: oldDeployment.domain, commitHash: oldDeployment.commitHash, commitMessage: oldDeployment.commitMessage, - deployerLrn: oldDeployment.deployerLrn + deployer: oldDeployment.deployer } ); } @@ -1072,13 +1149,14 @@ export class Service { }, relations: { project: true, + deployer: true, }, }); if (deployment && deployment.applicationDeploymentRecordId) { // If deployment is current, remove deployment for project subdomain as well if (deployment.isCurrent) { - const currentDeploymentURL = `https://${(deployment.project.name).toLowerCase()}.${deployment.baseDomain}`; + const currentDeploymentURL = `https://${(deployment.project.name).toLowerCase()}.${deployment.deployer.baseDomain}`; // TODO: Store the latest DNS deployment record const deploymentRecords = @@ -1101,14 +1179,14 @@ export class Service { await this.laconicRegistry.createApplicationDeploymentRemovalRequest({ deploymentId: latestRecord.id, - deployerLrn: deployment.deployerLrn + deployerLrn: deployment.deployer.deployerLrn }); } const result = await this.laconicRegistry.createApplicationDeploymentRemovalRequest({ deploymentId: deployment.applicationDeploymentRecordId, - deployerLrn: deployment.deployerLrn + deployerLrn: deployment.deployer.deployerLrn }); await this.db.updateDeploymentById(deployment.id, { diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 0b6a3a1b..4ea4123c 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -82,3 +82,20 @@ export interface EnvironmentVariables { key: string, value: string, } + +export interface DeployerRecord { + id: string; + names: string[]; + owners: string[]; + bondId: string; + createTime: string; + expiryTime: string; + attributes: { + apiUrl: string; + name: string; + paymentAddress: string; + publicKey: string; + type: string; + version: string; + }; +} diff --git a/packages/frontend/src/components/projects/create/Configure.tsx b/packages/frontend/src/components/projects/create/Configure.tsx index 7d81ee77..09a0b6b4 100644 --- a/packages/frontend/src/components/projects/create/Configure.tsx +++ b/packages/frontend/src/components/projects/create/Configure.tsx @@ -139,30 +139,8 @@ const Configure = () => { const projectId = await createProject(createFormData, environmentVariables); - const { environmentVariables: isEnvironmentVariablesAdded } = - await client.getEnvironmentVariables(projectId); + await client.getEnvironmentVariables(projectId); - if (isEnvironmentVariablesAdded.length > 0) { - toast({ - id: - createFormData.variables.length > 1 - ? 'env_variable_added' - : 'env_variables_added', - title: - createFormData.variables.length > 1 - ? `${createFormData.variables.length} variables added` - : `Variable added`, - variant: 'success', - onDismiss: dismiss, - }); - } else { - toast({ - id: 'env_variables_not_added', - title: 'Environment variables not added', - variant: 'error', - onDismiss: dismiss, - }); - } if (templateId) { createFormData.option === 'Auction' ? navigate( diff --git a/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx b/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx index 2d36cdbe..499aed76 100644 --- a/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx +++ b/packages/frontend/src/components/projects/project/deployments/DeploymentDetailsCard.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { Deployment, DeploymentStatus, @@ -6,6 +6,14 @@ import { Environment, Project, } from 'gql-client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, +} from '@mui/material'; + import { Avatar } from 'components/shared/Avatar'; import { BranchStrokeIcon, @@ -18,12 +26,23 @@ import { import { Heading } from 'components/shared/Heading'; import { OverflownText } from 'components/shared/OverflownText'; import { Tag, TagTheme } from 'components/shared/Tag'; +import { Button } from 'components/shared/Button'; import { getInitials } from 'utils/geInitials'; import { relativeTimeMs } from 'utils/time'; import { SHORT_COMMIT_HASH_LENGTH } from '../../../../constants'; import { formatAddress } from '../../../../utils/format'; import { DeploymentMenu } from './DeploymentMenu'; +const DEPLOYMENT_LOGS_STYLE = { + backgroundColor: "rgba(0,0,0, .9)", + padding: "2em", + borderRadius: "0.5em", + marginLeft: "0.5em", + marginRight: "0.5em", + color: "gray", + fontSize: "small", +}; + interface DeployDetailsCardProps { deployment: Deployment; currentDeployment: Deployment; @@ -48,6 +67,12 @@ const DeploymentDetailsCard = ({ project, prodBranchDomains, }: DeployDetailsCardProps) => { + const [openDialog, setOpenDialog] = useState(false); + const [deploymentLogs, setDeploymentLogs] = useState(); + + const handleOpenDialog = () => setOpenDialog(true); + const handleCloseDialog = () => setOpenDialog(false); + const getIconByDeploymentStatus = (status: DeploymentStatus) => { if ( status === DeploymentStatus.Building || @@ -64,6 +89,14 @@ const DeploymentDetailsCard = ({ } }; + const fetchDeploymentLogs = useCallback(async () => { + let url = `${deployment.deployer.deployerApiUrl}/log/${deployment.applicationDeploymentRequestId}`; + const res = await fetch(url, { cache: 'no-store' }); + const logs = await res.text(); + setDeploymentLogs(logs); + handleOpenDialog(); + }, [deployment.deployer.deployerApiUrl, deployment.applicationDeploymentRequestId, handleOpenDialog]); + const renderDeploymentStatus = useCallback( (className?: string) => { return ( @@ -72,6 +105,7 @@ const DeploymentDetailsCard = ({ leftIcon={getIconByDeploymentStatus(deployment.status)} size="xs" type={STATUS_COLORS[deployment.status] ?? 'neutral'} + onClick={fetchDeploymentLogs} > {deployment.status} @@ -96,9 +130,9 @@ const DeploymentDetailsCard = ({ )} - {deployment.deployerLrn && ( + {deployment.deployer.deployerLrn && ( - Deployer LRN: {deployment.deployerLrn} + Deployer LRN: {deployment.deployer.deployerLrn} )} @@ -167,6 +201,15 @@ const DeploymentDetailsCard = ({ prodBranchDomains={prodBranchDomains} /> + + Deployment logs + + {deploymentLogs &&
{deploymentLogs}
} +
+ + + +
); }; diff --git a/packages/frontend/src/pages/components/modals.tsx b/packages/frontend/src/pages/components/modals.tsx index 1eece68c..8e333101 100644 --- a/packages/frontend/src/pages/components/modals.tsx +++ b/packages/frontend/src/pages/components/modals.tsx @@ -36,7 +36,11 @@ const deployment: Deployment = { url: 'https://deploy1.example.com', environment: Environment.Production, isCurrent: true, - deployerLrn: 'lrn://example/deployers/webapp-deployer-api.test.com', + deployer: { + deployerApiUrl: 'https://webapp-deployer-api.example.com', + deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', + deployerLrn:'lrn://example/deployers/webapp-deployer-api.example.com' + }, status: DeploymentStatus.Ready, createdBy: { id: 'user1', @@ -49,6 +53,7 @@ const deployment: Deployment = { }, createdAt: '1677676800', // 2023-03-01T12:00:00Z updatedAt: '1677680400', // 2023-03-01T13:00:00Z + applicationDeploymentRequestId: 'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize', }; const domains: Domain[] = [ diff --git a/packages/frontend/src/stories/MockStoriesData.ts b/packages/frontend/src/stories/MockStoriesData.ts index 5559cbed..3a64356f 100644 --- a/packages/frontend/src/stories/MockStoriesData.ts +++ b/packages/frontend/src/stories/MockStoriesData.ts @@ -102,7 +102,12 @@ export const deployment0: Deployment = { domain: domain0, commitMessage: 'Commit Message', createdBy: user, - deployerLrn: 'lrn://deployer.apps.snowballtools.com ', + deployer: { + deployerApiUrl: 'https://webapp-deployer-api.example.com', + deployerId: 'bafyreicrtgmkir4evvvysxdqxddf2ftdq2wrzuodgvwnxr4rmubi4obdfu', + deployerLrn:'lrn://deployer.apps.snowballtools.com ' + }, + applicationDeploymentRequestId: 'bafyreiaycvq6imoppnpwdve4smj6t6ql5svt5zl3x6rimu4qwyzgjorize', }; export const project: Project = { diff --git a/packages/gql-client/src/queries.ts b/packages/gql-client/src/queries.ts index a87a4cb8..c41c75a4 100644 --- a/packages/gql-client/src/queries.ts +++ b/packages/gql-client/src/queries.ts @@ -136,7 +136,11 @@ query ($projectId: String!) { commitHash commitMessage url - deployerLrn + deployer { + deployerId + deployerLrn, + deployerApiUrl, + } environment isCurrent baseDomain @@ -148,6 +152,7 @@ query ($projectId: String!) { name email } + applicationDeploymentRequestId } } `; diff --git a/packages/gql-client/src/types.ts b/packages/gql-client/src/types.ts index 15b9e073..af46bfa5 100644 --- a/packages/gql-client/src/types.ts +++ b/packages/gql-client/src/types.ts @@ -105,7 +105,7 @@ export type Deployment = { commitHash: string; commitMessage: string; url?: string; - deployerLrn: string; + deployer: Deployer; environment: Environment; isCurrent: boolean; baseDomain?: string; @@ -113,8 +113,15 @@ export type Deployment = { createdBy: User; createdAt: string; updatedAt: string; + applicationDeploymentRequestId: string; }; +export type Deployer = { + deployerApiUrl: string; + deployerId: string; + deployerLrn: string; +} + export type OrganizationMember = { id: string; member: User;