Implement polling for project deployment updates (#87)
* Populate organization and user if db is empty * Use hardcoded user id from fixtures * Implement polling for get deployments query * Handle review changes --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
		
							parent
							
								
									fc240c93d8
								
							
						
					
					
						commit
						8ca55cd888
					
				| @ -5,6 +5,6 @@ export const DEFAULT_CONFIG_FILE_PATH = process.env.SNOWBALL_BACKEND_CONFIG_FILE | ||||
| export const DEFAULT_GQL_PATH = '/graphql'; | ||||
| 
 | ||||
| // Note: temporary hardcoded user, later to be derived from auth token
 | ||||
| export const USER_ID = process.env.SNOWBALL_BACKEND_USER_ID || '60f4355d-9549-4aac-9b54-eeefceeabef0'; | ||||
| export const USER_ID = process.env.SNOWBALL_BACKEND_USER_ID || '59f4355d-9549-4aac-9b54-eeefceeabef0'; | ||||
| 
 | ||||
| export const PROJECT_DOMAIN = process.env.SNOWBALL_BACKEND_PROJECT_DOMAIN || 'snowball.xyz'; | ||||
|  | ||||
| @ -14,6 +14,12 @@ import { ProjectMember } from './entity/ProjectMember'; | ||||
| import { EnvironmentVariable } from './entity/EnvironmentVariable'; | ||||
| import { Domain } from './entity/Domain'; | ||||
| import { PROJECT_DOMAIN } from './constants'; | ||||
| import { getEntities, loadAndSaveData } from './utils'; | ||||
| import { UserOrganization } from './entity/UserOrganization'; | ||||
| 
 | ||||
| const ORGANIZATION_DATA_PATH = '../test/fixtures/organizations.json'; | ||||
| const USER_DATA_PATH = '../test/fixtures/users.json'; | ||||
| const USER_ORGANIZATION_DATA_PATH = '../test/fixtures/user-organizations.json'; | ||||
| 
 | ||||
| const log = debug('snowball:database'); | ||||
| 
 | ||||
| @ -36,6 +42,25 @@ export class Database { | ||||
|   async init (): Promise<void> { | ||||
|     await this.dataSource.initialize(); | ||||
|     log('database initialized'); | ||||
| 
 | ||||
|     const organizations = await this.getOrganizations({}); | ||||
| 
 | ||||
|     if (!organizations.length) { | ||||
|       const orgEntities = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH)); | ||||
|       const savedOrgs = await loadAndSaveData(Organization, this.dataSource, [orgEntities[0]]); | ||||
| 
 | ||||
|       // TODO: Remove user once authenticated
 | ||||
|       const userEntities = await getEntities(path.resolve(__dirname, USER_DATA_PATH)); | ||||
|       const savedUsers = await loadAndSaveData(User, this.dataSource, [userEntities[0]]); | ||||
| 
 | ||||
|       const userOrganizationRelations = { | ||||
|         member: savedUsers, | ||||
|         organization: savedOrgs | ||||
|       }; | ||||
| 
 | ||||
|       const userOrgEntities = await getEntities(path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH)); | ||||
|       await loadAndSaveData(UserOrganization, this.dataSource, [userOrgEntities[0]], userOrganizationRelations); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   async getUser (options: FindOneOptions<User>): Promise<User | null> { | ||||
| @ -60,6 +85,13 @@ export class Database { | ||||
|     return updateResult.affected > 0; | ||||
|   } | ||||
| 
 | ||||
|   async getOrganizations (options: FindManyOptions<Organization>): Promise<Organization[]> { | ||||
|     const organizationRepository = this.dataSource.getRepository(Organization); | ||||
|     const organizations = await organizationRepository.find(options); | ||||
| 
 | ||||
|     return organizations; | ||||
|   } | ||||
| 
 | ||||
|   async getOrganization (options: FindOneOptions<Organization>): Promise<Organization | null> { | ||||
|     const organizationRepository = this.dataSource.getRepository(Organization); | ||||
|     const organization = await organizationRepository.findOne(options); | ||||
|  | ||||
| @ -2,6 +2,7 @@ import fs from 'fs-extra'; | ||||
| import path from 'path'; | ||||
| import toml from 'toml'; | ||||
| import debug from 'debug'; | ||||
| import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm'; | ||||
| 
 | ||||
| const log = debug('snowball:utils'); | ||||
| 
 | ||||
| @ -19,3 +20,44 @@ export const getConfig = async <ConfigType>( | ||||
| 
 | ||||
|   return config; | ||||
| }; | ||||
| 
 | ||||
| export const checkFileExists = async (filePath: string): Promise<boolean> => { | ||||
|   try { | ||||
|     await fs.access(filePath, fs.constants.F_OK); | ||||
|     return true; | ||||
|   } catch (err) { | ||||
|     log(err); | ||||
|     return false; | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export const getEntities = async (filePath: string): Promise<any> => { | ||||
|   const entitiesData = await fs.readFile(filePath, 'utf-8'); | ||||
|   const entities = JSON.parse(entitiesData); | ||||
|   return entities; | ||||
| }; | ||||
| 
 | ||||
| export const loadAndSaveData = async <Entity extends ObjectLiteral>(entityType: EntityTarget<Entity>, dataSource: DataSource, entities: any, relations?: any | undefined): Promise<Entity[]> => { | ||||
|   const entityRepository = dataSource.getRepository(entityType); | ||||
| 
 | ||||
|   const savedEntity:Entity[] = []; | ||||
| 
 | ||||
|   for (const entityData of entities) { | ||||
|     let entity = entityRepository.create(entityData as DeepPartial<Entity>); | ||||
| 
 | ||||
|     if (relations) { | ||||
|       for (const field in relations) { | ||||
|         const valueIndex = String(field + 'Index'); | ||||
| 
 | ||||
|         entity = { | ||||
|           ...entity, | ||||
|           [field]: relations[field][entityData[valueIndex]] | ||||
|         }; | ||||
|       } | ||||
|     } | ||||
|     const dbEntity = await entityRepository.save(entity); | ||||
|     savedEntity.push(dbEntity); | ||||
|   } | ||||
| 
 | ||||
|   return savedEntity; | ||||
| }; | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm'; | ||||
| import * as fs from 'fs/promises'; | ||||
| import { DataSource } from 'typeorm'; | ||||
| import debug from 'debug'; | ||||
| import path from 'path'; | ||||
| 
 | ||||
| @ -11,7 +10,7 @@ import { EnvironmentVariable } from '../src/entity/EnvironmentVariable'; | ||||
| import { Domain } from '../src/entity/Domain'; | ||||
| import { ProjectMember } from '../src/entity/ProjectMember'; | ||||
| import { Deployment } from '../src/entity/Deployment'; | ||||
| import { getConfig } from '../src/utils'; | ||||
| import { checkFileExists, getConfig, getEntities, loadAndSaveData } from '../src/utils'; | ||||
| import { Config } from '../src/config'; | ||||
| import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants'; | ||||
| 
 | ||||
| @ -27,43 +26,20 @@ const DEPLOYMENT_DATA_PATH = './fixtures/deployments.json'; | ||||
| const ENVIRONMENT_VARIABLE_DATA_PATH = './fixtures/environment-variables.json'; | ||||
| const REDIRECTED_DOMAIN_DATA_PATH = './fixtures/redirected-domains.json'; | ||||
| 
 | ||||
| const loadAndSaveData = async <Entity extends ObjectLiteral>(entityType: EntityTarget<Entity>, dataSource: DataSource, filePath: string, relations?: any | undefined) => { | ||||
|   const entitiesData = await fs.readFile(filePath, 'utf-8'); | ||||
|   const entities = JSON.parse(entitiesData); | ||||
|   const entityRepository = dataSource.getRepository(entityType); | ||||
| 
 | ||||
|   const savedEntity:Entity[] = []; | ||||
| 
 | ||||
|   for (const entityData of entities) { | ||||
|     let entity = entityRepository.create(entityData as DeepPartial<Entity>); | ||||
| 
 | ||||
|     if (relations) { | ||||
|       for (const field in relations) { | ||||
|         const valueIndex = String(field + 'Index'); | ||||
| 
 | ||||
|         entity = { | ||||
|           ...entity, | ||||
|           [field]: relations[field][entityData[valueIndex]] | ||||
|         }; | ||||
|       } | ||||
|     } | ||||
|     const dbEntity = await entityRepository.save(entity); | ||||
|     savedEntity.push(dbEntity); | ||||
|   } | ||||
| 
 | ||||
|   return savedEntity; | ||||
| }; | ||||
| 
 | ||||
| const generateTestData = async (dataSource: DataSource) => { | ||||
|   const savedUsers = await loadAndSaveData(User, dataSource, path.resolve(__dirname, USER_DATA_PATH)); | ||||
|   const savedOrgs = await loadAndSaveData(Organization, dataSource, path.resolve(__dirname, ORGANIZATION_DATA_PATH)); | ||||
|   const userEntities = await getEntities(path.resolve(__dirname, USER_DATA_PATH)); | ||||
|   const savedUsers = await loadAndSaveData(User, dataSource, userEntities); | ||||
| 
 | ||||
|   const orgEntities = await getEntities(path.resolve(__dirname, ORGANIZATION_DATA_PATH)); | ||||
|   const savedOrgs = await loadAndSaveData(Organization, dataSource, orgEntities); | ||||
| 
 | ||||
|   const projectRelations = { | ||||
|     owner: savedUsers, | ||||
|     organization: savedOrgs | ||||
|   }; | ||||
| 
 | ||||
|   const savedProjects = await loadAndSaveData(Project, dataSource, path.resolve(__dirname, PROJECT_DATA_PATH), projectRelations); | ||||
|   const projectEntities = await getEntities(path.resolve(__dirname, PROJECT_DATA_PATH)); | ||||
|   const savedProjects = await loadAndSaveData(Project, dataSource, projectEntities, projectRelations); | ||||
| 
 | ||||
|   const domainRepository = dataSource.getRepository(Domain); | ||||
| 
 | ||||
| @ -71,14 +47,16 @@ const generateTestData = async (dataSource: DataSource) => { | ||||
|     project: savedProjects | ||||
|   }; | ||||
| 
 | ||||
|   const savedPrimaryDomains = await loadAndSaveData(Domain, dataSource, path.resolve(__dirname, PRIMARY_DOMAIN_DATA_PATH), domainPrimaryRelations); | ||||
|   const primaryDomainsEntities = await getEntities(path.resolve(__dirname, PRIMARY_DOMAIN_DATA_PATH)); | ||||
|   const savedPrimaryDomains = await loadAndSaveData(Domain, dataSource, primaryDomainsEntities, domainPrimaryRelations); | ||||
| 
 | ||||
|   const domainRedirectedRelations = { | ||||
|     project: savedProjects, | ||||
|     redirectTo: savedPrimaryDomains | ||||
|   }; | ||||
| 
 | ||||
|   await loadAndSaveData(Domain, dataSource, path.resolve(__dirname, REDIRECTED_DOMAIN_DATA_PATH), domainRedirectedRelations); | ||||
|   const redirectDomainsEntities = await getEntities(path.resolve(__dirname, REDIRECTED_DOMAIN_DATA_PATH)); | ||||
|   await loadAndSaveData(Domain, dataSource, redirectDomainsEntities, domainRedirectedRelations); | ||||
| 
 | ||||
|   const savedDomains = await domainRepository.find(); | ||||
| 
 | ||||
| @ -87,14 +65,16 @@ const generateTestData = async (dataSource: DataSource) => { | ||||
|     organization: savedOrgs | ||||
|   }; | ||||
| 
 | ||||
|   await loadAndSaveData(UserOrganization, dataSource, path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH), userOrganizationRelations); | ||||
|   const userOrganizationsEntities = await getEntities(path.resolve(__dirname, USER_ORGANIZATION_DATA_PATH)); | ||||
|   await loadAndSaveData(UserOrganization, dataSource, userOrganizationsEntities, userOrganizationRelations); | ||||
| 
 | ||||
|   const projectMemberRelations = { | ||||
|     member: savedUsers, | ||||
|     project: savedProjects | ||||
|   }; | ||||
| 
 | ||||
|   await loadAndSaveData(ProjectMember, dataSource, path.resolve(__dirname, PROJECT_MEMBER_DATA_PATH), projectMemberRelations); | ||||
|   const projectMembersEntities = await getEntities(path.resolve(__dirname, PROJECT_MEMBER_DATA_PATH)); | ||||
|   await loadAndSaveData(ProjectMember, dataSource, projectMembersEntities, projectMemberRelations); | ||||
| 
 | ||||
|   const deploymentRelations = { | ||||
|     project: savedProjects, | ||||
| @ -102,23 +82,15 @@ const generateTestData = async (dataSource: DataSource) => { | ||||
|     createdBy: savedUsers | ||||
|   }; | ||||
| 
 | ||||
|   await loadAndSaveData(Deployment, dataSource, path.resolve(__dirname, DEPLOYMENT_DATA_PATH), deploymentRelations); | ||||
|   const deploymentsEntities = await getEntities(path.resolve(__dirname, DEPLOYMENT_DATA_PATH)); | ||||
|   await loadAndSaveData(Deployment, dataSource, deploymentsEntities, deploymentRelations); | ||||
| 
 | ||||
|   const environmentVariableRelations = { | ||||
|     project: savedProjects | ||||
|   }; | ||||
| 
 | ||||
|   await loadAndSaveData(EnvironmentVariable, dataSource, path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH), environmentVariableRelations); | ||||
| }; | ||||
| 
 | ||||
| const checkFileExists = async (filePath: string) => { | ||||
|   try { | ||||
|     await fs.access(filePath, fs.constants.F_OK); | ||||
|     return true; | ||||
|   } catch (err) { | ||||
|     log(err); | ||||
|     return false; | ||||
|   } | ||||
|   const environmentVariablesEntities = await getEntities(path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH)); | ||||
|   await loadAndSaveData(EnvironmentVariable, dataSource, environmentVariablesEntities, environmentVariableRelations); | ||||
| }; | ||||
| 
 | ||||
| const main = async () => { | ||||
|  | ||||
| @ -16,6 +16,7 @@ const DEFAULT_FILTER_VALUE: FilterValue = { | ||||
|   searchedBranch: '', | ||||
|   status: StatusOptions.ALL_STATUS, | ||||
| }; | ||||
| const FETCH_DEPLOYMENTS_INTERVAL = 5000; | ||||
| 
 | ||||
| const DeploymentsTabPanel = () => { | ||||
|   const client = useGQLClient(); | ||||
| @ -26,22 +27,30 @@ const DeploymentsTabPanel = () => { | ||||
|   const [deployments, setDeployments] = useState<Deployment[]>([]); | ||||
|   const [prodBranchDomains, setProdBranchDomains] = useState<Domain[]>([]); | ||||
| 
 | ||||
|   const fetchDeployments = async () => { | ||||
|   const fetchDeployments = useCallback(async () => { | ||||
|     const { deployments } = await client.getDeployments(project.id); | ||||
|     setDeployments(deployments); | ||||
|   }; | ||||
|   }, [client]); | ||||
| 
 | ||||
|   const fetchProductionBranchDomains = useCallback(async () => { | ||||
|     const { domains } = await client.getDomains(project.id, { | ||||
|       branch: project.prodBranch, | ||||
|     }); | ||||
|     setProdBranchDomains(domains); | ||||
|   }, []); | ||||
|   }, [client]); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     fetchDeployments(); | ||||
|     fetchProductionBranchDomains(); | ||||
|   }, []); | ||||
|     fetchDeployments(); | ||||
| 
 | ||||
|     const interval = setInterval(() => { | ||||
|       fetchDeployments(); | ||||
|     }, FETCH_DEPLOYMENTS_INTERVAL); | ||||
| 
 | ||||
|     return () => { | ||||
|       clearInterval(interval); | ||||
|     }; | ||||
|   }, [fetchDeployments, fetchProductionBranchDomains]); | ||||
| 
 | ||||
|   const currentDeployment = useMemo(() => { | ||||
|     return deployments.find((deployment) => { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user