diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index 1a48c35d..35c3aa65 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -33,7 +33,7 @@ export class Database { private dataSource: DataSource; private projectDomain: string; - constructor ({ dbPath } : DatabaseConfig, { projectDomain } : MiscConfig) { + constructor({ dbPath }: DatabaseConfig, { projectDomain }: MiscConfig) { this.dataSource = new DataSource({ type: 'better-sqlite3', database: dbPath, @@ -45,7 +45,7 @@ export class Database { this.projectDomain = projectDomain; } - async init (): Promise { + async init(): Promise { await this.dataSource.initialize(); log('database initialized'); @@ -58,21 +58,21 @@ export class Database { } } - async getUser (options: FindOneOptions): Promise { + async getUser(options: FindOneOptions): Promise { const userRepository = this.dataSource.getRepository(User); const user = await userRepository.findOne(options); return user; } - async addUser (data: DeepPartial): Promise { + async addUser(data: DeepPartial): Promise { const userRepository = this.dataSource.getRepository(User); const user = await userRepository.save(data); return user; } - async updateUser (user: User, data: DeepPartial): Promise { + async updateUser(user: User, data: DeepPartial): Promise { const userRepository = this.dataSource.getRepository(User); const updateResult = await userRepository.update({ id: user.id }, data); assert(updateResult.affected); @@ -80,7 +80,7 @@ export class Database { return updateResult.affected > 0; } - async getOrganizations ( + async getOrganizations( options: FindManyOptions ): Promise { const organizationRepository = this.dataSource.getRepository(Organization); @@ -89,7 +89,7 @@ export class Database { return organizations; } - async getOrganization ( + async getOrganization( options: FindOneOptions ): Promise { const organizationRepository = this.dataSource.getRepository(Organization); @@ -98,7 +98,7 @@ export class Database { return organization; } - async getOrganizationsByUserId (userId: string): Promise { + async getOrganizationsByUserId(userId: string): Promise { const organizationRepository = this.dataSource.getRepository(Organization); const userOrgs = await organizationRepository.find({ @@ -114,21 +114,21 @@ export class Database { return userOrgs; } - async addUserOrganization (data: DeepPartial): Promise { + async addUserOrganization(data: DeepPartial): Promise { const userOrganizationRepository = this.dataSource.getRepository(UserOrganization); const newUserOrganization = await userOrganizationRepository.save(data); return newUserOrganization; } - async getProjects (options: FindManyOptions): Promise { + async getProjects(options: FindManyOptions): Promise { const projectRepository = this.dataSource.getRepository(Project); const projects = await projectRepository.find(options); return projects; } - async getProjectById (projectId: string): Promise { + async getProjectById(projectId: string): Promise { const projectRepository = this.dataSource.getRepository(Project); const project = await projectRepository @@ -150,7 +150,20 @@ export class Database { return project; } - async getProjectsInOrganization ( + async allProjectsWithoutDeployments(): Promise { + const projectRepository = this.dataSource.getRepository(Project); + + const projects = await projectRepository + .createQueryBuilder('project') + .leftJoinAndSelect('project.deployments', 'deployment', 'deployment.deletedAt IS NULL') // Join only non-soft-deleted deployments + .where('deployment.id IS NULL') // Get projects where no deployments are present + .andWhere('project.auctionId IS NOT NULL') // Ensure auctionId is not null + .getMany(); + + return projects; + } + + async getProjectsInOrganization( userId: string, organizationSlug: string ): Promise { @@ -181,7 +194,7 @@ export class Database { /** * Get deployments with specified filter */ - async getDeployments ( + async getDeployments( options: FindManyOptions ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); @@ -190,7 +203,7 @@ export class Database { return deployments; } - async getDeploymentsByProjectId (projectId: string): Promise { + async getDeploymentsByProjectId(projectId: string): Promise { return this.getDeployments({ relations: { project: true, @@ -208,7 +221,7 @@ export class Database { }); } - async getDeployment ( + async getDeployment( options: FindOneOptions ): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); @@ -217,14 +230,14 @@ export class Database { return deployment; } - async getDomains (options: FindManyOptions): Promise { + async getDomains(options: FindManyOptions): Promise { const domainRepository = this.dataSource.getRepository(Domain); const domains = await domainRepository.find(options); return domains; } - async addDeployment (data: DeepPartial): Promise { + async addDeployment(data: DeepPartial): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const id = nanoid(); @@ -238,7 +251,7 @@ export class Database { return deployment; } - async getProjectMembersByProjectId ( + async getProjectMembersByProjectId( projectId: string ): Promise { const projectMemberRepository = @@ -259,7 +272,7 @@ export class Database { return projectMembers; } - async getEnvironmentVariablesByProjectId ( + async getEnvironmentVariablesByProjectId( projectId: string, filter?: FindOptionsWhere ): Promise { @@ -278,7 +291,7 @@ export class Database { return environmentVariables; } - async removeProjectMemberById (projectMemberId: string): Promise { + async removeProjectMemberById(projectMemberId: string): Promise { const projectMemberRepository = this.dataSource.getRepository(ProjectMember); @@ -293,7 +306,7 @@ export class Database { } } - async updateProjectMemberById ( + async updateProjectMemberById( projectMemberId: string, data: DeepPartial ): Promise { @@ -307,7 +320,7 @@ export class Database { return Boolean(updateResult.affected); } - async addProjectMember ( + async addProjectMember( data: DeepPartial ): Promise { const projectMemberRepository = @@ -317,7 +330,7 @@ export class Database { return newProjectMember; } - async addEnvironmentVariables ( + async addEnvironmentVariables( data: DeepPartial[] ): Promise { const environmentVariableRepository = @@ -328,7 +341,7 @@ export class Database { return savedEnvironmentVariables; } - async updateEnvironmentVariable ( + async updateEnvironmentVariable( environmentVariableId: string, data: DeepPartial ): Promise { @@ -342,7 +355,7 @@ export class Database { return Boolean(updateResult.affected); } - async deleteEnvironmentVariable ( + async deleteEnvironmentVariable( environmentVariableId: string ): Promise { const environmentVariableRepository = @@ -358,7 +371,7 @@ export class Database { } } - async getProjectMemberById (projectMemberId: string): Promise { + async getProjectMemberById(projectMemberId: string): Promise { const projectMemberRepository = this.dataSource.getRepository(ProjectMember); @@ -381,7 +394,7 @@ export class Database { return projectMemberWithProject[0]; } - async getProjectsBySearchText ( + async getProjectsBySearchText( userId: string, searchText: string ): Promise { @@ -403,14 +416,14 @@ export class Database { return projects; } - async updateDeploymentById ( + async updateDeploymentById( deploymentId: string, data: DeepPartial ): Promise { return this.updateDeployment({ id: deploymentId }, data); } - async updateDeployment ( + async updateDeployment( criteria: FindOptionsWhere, data: DeepPartial ): Promise { @@ -420,7 +433,7 @@ export class Database { return Boolean(updateResult.affected); } - async updateDeploymentsByProjectIds ( + async updateDeploymentsByProjectIds( projectIds: string[], data: DeepPartial ): Promise { @@ -436,7 +449,7 @@ export class Database { return Boolean(updateResult.affected); } - async deleteDeploymentById (deploymentId: string): Promise { + async deleteDeploymentById(deploymentId: string): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); const deployment = await deploymentRepository.findOneOrFail({ where: { @@ -449,7 +462,7 @@ export class Database { return Boolean(deleteResult); } - async addProject (user: User, organizationId: string, data: DeepPartial): Promise { + async addProject(user: User, organizationId: string, data: DeepPartial): Promise { const projectRepository = this.dataSource.getRepository(Project); // TODO: Check if organization exists @@ -468,7 +481,7 @@ export class Database { return projectRepository.save(newProject); } - async updateProjectById ( + async updateProjectById( projectId: string, data: DeepPartial ): Promise { @@ -481,7 +494,7 @@ export class Database { return Boolean(updateResult.affected); } - async deleteProjectById (projectId: string): Promise { + async deleteProjectById(projectId: string): Promise { const projectRepository = this.dataSource.getRepository(Project); const project = await projectRepository.findOneOrFail({ where: { @@ -497,7 +510,7 @@ export class Database { return Boolean(deleteResult); } - async deleteDomainById (domainId: string): Promise { + async deleteDomainById(domainId: string): Promise { const domainRepository = this.dataSource.getRepository(Domain); const deleteResult = await domainRepository.softDelete({ id: domainId }); @@ -509,21 +522,21 @@ export class Database { } } - async addDomain (data: DeepPartial): Promise { + async addDomain(data: DeepPartial): Promise { const domainRepository = this.dataSource.getRepository(Domain); const newDomain = await domainRepository.save(data); return newDomain; } - async getDomain (options: FindOneOptions): Promise { + async getDomain(options: FindOneOptions): Promise { const domainRepository = this.dataSource.getRepository(Domain); const domain = await domainRepository.findOne(options); return domain; } - async updateDomainById ( + async updateDomainById( domainId: string, data: DeepPartial ): Promise { @@ -533,7 +546,7 @@ export class Database { return Boolean(updateResult.affected); } - async getDomainsByProjectId ( + async getDomainsByProjectId( projectId: string, filter?: FindOptionsWhere ): Promise { diff --git a/packages/backend/src/registry.ts b/packages/backend/src/registry.ts index 904376ed..89f53ebc 100644 --- a/packages/backend/src/registry.ts +++ b/packages/backend/src/registry.ts @@ -417,13 +417,7 @@ export class Registry { } async getCompletedAuctionIds(auctionIds: string[]): Promise { - const validAuctionIds = auctionIds.filter((id): id is string => id !== ''); - - if (!validAuctionIds.length) { - return []; - } - - const auctions = await this.registry.getAuctionsByIds(validAuctionIds); + const auctions = await this.registry.getAuctionsByIds(auctionIds); const completedAuctions = auctions .filter((auction: { id: string, status: string }) => auction.status === 'completed') diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index dbe65f3b..067a9f72 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -6,7 +6,7 @@ import { Permission } from './entity/ProjectMember'; import { Domain } from './entity/Domain'; import { Project } from './entity/Project'; import { EnvironmentVariable } from './entity/EnvironmentVariable'; -import { AddProjectFromTemplateInput, AuctionParams } from './types'; +import { AddProjectFromTemplateInput, AuctionParams, EnvironmentVariables } from './types'; const log = debug('snowball:resolver'); @@ -218,7 +218,7 @@ export const createResolvers = async (service: Service): Promise => { data: AddProjectFromTemplateInput; lrn: string; auctionParams: AuctionParams, - environmentVariables: { environments: string[]; key: string; value: string }[]; + environmentVariables: EnvironmentVariables[]; }, context: any, ) => { @@ -250,7 +250,7 @@ export const createResolvers = async (service: Service): Promise => { data: DeepPartial; lrn: string; auctionParams: AuctionParams, - environmentVariables: { environments: string[]; key: string; value: string }[]; + environmentVariables: EnvironmentVariables[]; }, context: any, ) => { diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index 5b127712..57ad9ef5 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -20,6 +20,7 @@ import { AppDeploymentRecord, AppDeploymentRemovalRecord, AuctionParams, + EnvironmentVariables, GitPushEventPayload, } from './types'; import { Role } from './entity/UserOrganization'; @@ -167,7 +168,7 @@ export class Service { async updateDeploymentsWithRecordData( records: AppDeploymentRecord[], ): Promise { - // get and update deployments to be updated using request id + // Fetch the deployments to be updated using deployment requestId const deployments = await this.db.getDeployments({ where: records.map((record) => ({ applicationDeploymentRequestId: record.attributes.request, @@ -220,10 +221,10 @@ export class Service { await Promise.all(deploymentUpdatePromises); - // if iscurrent is true for this deployment then update the old ones + // Get deployments that are in production environment const prodDeployments = Object.values(recordToDeploymentsMap).filter(deployment => deployment.isCurrent); - // Get deployment IDs of deployments that are in production environment + // Set the isCurrent state to false for the old deployments for (const deployment of prodDeployments) { const projectDeployments = await this.db.getDeploymentsByProjectId(deployment.projectId); const oldDeployments = projectDeployments @@ -236,15 +237,6 @@ export class Service { } } - // Get old deployments for ApplicationDeploymentRecords - // flter out deps with is current false - - // loop over these deps - // get the project - // get all the deployemnts in that proj with the same deployer lrn (query filter not above updated dep) - // set is current to false - - await Promise.all(deploymentUpdatePromises); } @@ -298,43 +290,30 @@ export class Service { * Calls the createDeploymentFromAuction method for deployments with completed auctions */ async checkAuctionStatus(): Promise { - const allProjects = await this.db.getProjects({ - where: { - auctionId: Not(IsNull()), - }, - relations: ['deployments'], - withDeleted: true, - }); + const projects = await this.db.allProjectsWithoutDeployments(); - // Should only check on the first deployment - const projects = allProjects.filter(project => { - if (project.deletedAt !== null) return false; + const validAuctionIds = projects.map((project) => project.auctionId!) + .filter((id): id is string => Boolean(id)); + const completedAuctionIds = await this.laconicRegistry.getCompletedAuctionIds(validAuctionIds); - return project.deployments.length === 0; - }); + const projectsToBedeployed = projects.filter((project) => + completedAuctionIds.includes(project.auctionId!) + ); - if (projects.length > 0) { - const auctionIds = projects.map((project) => project.auctionId!); - const completedAuctionIds = await this.laconicRegistry.getCompletedAuctionIds(auctionIds); - const projectsToBedeployed = projects.filter((project) => - completedAuctionIds.includes(project.auctionId!) - ); + for (const project of projectsToBedeployed) { + const deployerLrns = await this.laconicRegistry.getAuctionWinningDeployers(project!.auctionId!); - for (const project of projectsToBedeployed) { - const deployerLrns = await this.laconicRegistry.getAuctionWinningDeployers(project!.auctionId!); + if (!deployerLrns) { + log(`No winning deployer for auction ${project!.auctionId}`); + } else { + // Update project with deployer LRNs + await this.db.updateProjectById(project.id!, { + deployerLrns + }); - if (!deployerLrns) { - log(`No winning deployer for auction ${project!.auctionId}`); - } else { - // Update project with deployer LRNs - await this.db.updateProjectById(project.id!, { - deployerLrns - }); - - for (const deployer of deployerLrns) { - log(`Creating deployment for deployer LRN ${deployer}`); - await this.createDeploymentFromAuction(project, deployer); - } + for (const deployer of deployerLrns) { + log(`Creating deployment for deployer LRN ${deployer}`); + await this.createDeploymentFromAuction(project, deployer); } } } @@ -785,7 +764,7 @@ export class Service { data: AddProjectFromTemplateInput, lrn?: string, auctionParams?: AuctionParams, - environmentVariables?: { environments: string[]; key: string; value: string }[], + environmentVariables?: EnvironmentVariables[], ): Promise { try { const octokit = await this.getOctokit(user.id); @@ -835,7 +814,7 @@ export class Service { data: DeepPartial, lrn?: string, auctionParams?: AuctionParams, - environmentVariables?: { environments: string[]; key: string; value: string }[], + environmentVariables?: EnvironmentVariables[], ): Promise { const organization = await this.db.getOrganization({ where: { @@ -846,9 +825,9 @@ export class Service { throw new Error('Organization does not exist'); } - const project = await this.db.addProject(user, organization.id, data);4 + const project = await this.db.addProject(user, organization.id, data); - if(environmentVariables) { + if (environmentVariables) { await this.addEnvironmentVariables(project.id, environmentVariables); } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 33cf0108..0b6a3a1b 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -76,3 +76,9 @@ export interface AuctionParams { maxPrice: string, numProviders: number, } + +export interface EnvironmentVariables { + environments: string[], + key: string, + value: string, +} diff --git a/packages/frontend/src/components/projects/create/Configure.tsx b/packages/frontend/src/components/projects/create/Configure.tsx index 7f920c0a..7d81ee77 100644 --- a/packages/frontend/src/components/projects/create/Configure.tsx +++ b/packages/frontend/src/components/projects/create/Configure.tsx @@ -1,6 +1,6 @@ import { useCallback, useState } from 'react'; 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 { useMediaQuery } from 'usehooks-ts'; import { AddEnvironmentVariableInput, AuctionParams } from 'gql-client'; @@ -59,18 +59,18 @@ const Configure = () => { setIsLoading(true); let projectId: string | null = null; - try { - let lrn: string | undefined; - let auctionParams: AuctionParams | undefined; + try { + let lrn: string | undefined; + let auctionParams: AuctionParams | undefined; - if (data.option === 'LRN') { - lrn = data.lrn; - } else if (data.option === 'Auction') { - auctionParams = { - numProviders: Number(data.numProviders!), - maxPrice: (data.maxPrice!).toString(), - }; - } + if (data.option === 'LRN') { + lrn = data.lrn; + } else if (data.option === 'Auction') { + auctionParams = { + numProviders: Number(data.numProviders!), + maxPrice: (data.maxPrice!).toString(), + }; + } if (templateId) { const projectData: any = { @@ -125,7 +125,7 @@ const Configure = () => { } }; - const createEnvironmentVariablesHandler = useCallback( + const handleFormSubmit = useCallback( async (createFormData: FieldValues) => { const environmentVariables = createFormData.variables.map((variable: any) => { return { @@ -165,20 +165,20 @@ const Configure = () => { } if (templateId) { createFormData.option === 'Auction' - ? navigate( - `/${orgSlug}/projects/create/success/${projectId}?isAuction=true`, - ) - : navigate( - `/${orgSlug}/projects/create/template/deploy?projectId=${projectId}&templateId=${templateId}` - ); + ? navigate( + `/${orgSlug}/projects/create/success/${projectId}?isAuction=true`, + ) + : navigate( + `/${orgSlug}/projects/create/template/deploy?projectId=${projectId}&templateId=${templateId}` + ); } else { createFormData.option === 'Auction' - ? navigate( - `/${orgSlug}/projects/create/success/${projectId}?isAuction=true` - ) - : navigate( - `/${orgSlug}/projects/create/deploy?projectId=${projectId}` - ); + ? navigate( + `/${orgSlug}/projects/create/success/${projectId}?isAuction=true` + ) + : navigate( + `/${orgSlug}/projects/create/deploy?projectId=${projectId}` + ); } }, [client, createProject, dismiss, toast] @@ -201,7 +201,7 @@ const Configure = () => {
-
+
{ ( )} @@ -256,7 +256,7 @@ const Configure = () => { ( )} @@ -269,7 +269,7 @@ const Configure = () => { ( )} diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx index 459c4f15..d284a4a6 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariables.tsx @@ -8,7 +8,6 @@ import { useGQLClient } from 'context/GQLClientContext'; import { EnvironmentVariablesFormValues } from '../../../../../types'; import HorizontalLine from 'components/HorizontalLine'; import { Heading } from 'components/shared/Heading'; -// import { Checkbox } from 'components/shared/Checkbox'; import { PlusIcon } from 'components/shared/CustomIcon'; import { ProjectSettingContainer } from 'components/projects/project/settings/ProjectSettingContainer'; import { useToast } from 'components/shared/Toast'; @@ -79,7 +78,7 @@ export const EnvironmentVariablesTabPanel = () => { await client.addEnvironmentVariables(id!, environmentVariables); if (isEnvironmentVariablesAdded) { - // reset(); + methods.reset(); setCreateNewVariable((cur) => !cur); fetchEnvironmentVariables(id); diff --git a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariablesForm.tsx b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariablesForm.tsx index 6b14b181..ebe9bdc2 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariablesForm.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/settings/EnvironmentVariablesForm.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo } from 'react'; import { useFieldArray, useFormContext } from 'react-hook-form'; +// TODO: Use custom checkbox component import { Checkbox } from '@snowballtools/material-tailwind-react-fork'; import { Button } from 'components/shared/Button';