From 8ca55cd88805f77b5341cac4356e74d25059bd08 Mon Sep 17 00:00:00 2001 From: Nabarun Gogoi Date: Wed, 21 Feb 2024 15:34:33 +0530 Subject: [PATCH] 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 --- packages/backend/src/constants.ts | 2 +- packages/backend/src/database.ts | 32 +++++++++ packages/backend/src/utils.ts | 42 +++++++++++ packages/backend/test/initialize-db.ts | 70 ++++++------------- .../org-slug/projects/id/Deployments.tsx | 19 +++-- 5 files changed, 110 insertions(+), 55 deletions(-) diff --git a/packages/backend/src/constants.ts b/packages/backend/src/constants.ts index ea3b4610..bd3badad 100644 --- a/packages/backend/src/constants.ts +++ b/packages/backend/src/constants.ts @@ -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'; diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index a45c354d..aa0e997f 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -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 { 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): Promise { @@ -60,6 +85,13 @@ export class Database { return updateResult.affected > 0; } + async getOrganizations (options: FindManyOptions): Promise { + const organizationRepository = this.dataSource.getRepository(Organization); + const organizations = await organizationRepository.find(options); + + return organizations; + } + async getOrganization (options: FindOneOptions): Promise { const organizationRepository = this.dataSource.getRepository(Organization); const organization = await organizationRepository.findOne(options); diff --git a/packages/backend/src/utils.ts b/packages/backend/src/utils.ts index f33963f9..f9d21e7a 100644 --- a/packages/backend/src/utils.ts +++ b/packages/backend/src/utils.ts @@ -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 ( return config; }; + +export const checkFileExists = async (filePath: string): Promise => { + try { + await fs.access(filePath, fs.constants.F_OK); + return true; + } catch (err) { + log(err); + return false; + } +}; + +export const getEntities = async (filePath: string): Promise => { + const entitiesData = await fs.readFile(filePath, 'utf-8'); + const entities = JSON.parse(entitiesData); + return entities; +}; + +export const loadAndSaveData = async (entityType: EntityTarget, dataSource: DataSource, entities: any, relations?: any | undefined): Promise => { + const entityRepository = dataSource.getRepository(entityType); + + const savedEntity:Entity[] = []; + + for (const entityData of entities) { + let entity = entityRepository.create(entityData as DeepPartial); + + 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; +}; diff --git a/packages/backend/test/initialize-db.ts b/packages/backend/test/initialize-db.ts index 0aade587..420ff03d 100644 --- a/packages/backend/test/initialize-db.ts +++ b/packages/backend/test/initialize-db.ts @@ -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 (entityType: EntityTarget, 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); - - 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 () => { diff --git a/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx b/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx index 958382ac..7049a270 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/Deployments.tsx @@ -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([]); const [prodBranchDomains, setProdBranchDomains] = useState([]); - 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) => {