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';
|
export const DEFAULT_GQL_PATH = '/graphql';
|
||||||
|
|
||||||
// Note: temporary hardcoded user, later to be derived from auth token
|
// 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';
|
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 { EnvironmentVariable } from './entity/EnvironmentVariable';
|
||||||
import { Domain } from './entity/Domain';
|
import { Domain } from './entity/Domain';
|
||||||
import { PROJECT_DOMAIN } from './constants';
|
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');
|
const log = debug('snowball:database');
|
||||||
|
|
||||||
@ -36,6 +42,25 @@ export class Database {
|
|||||||
async init (): Promise<void> {
|
async init (): Promise<void> {
|
||||||
await this.dataSource.initialize();
|
await this.dataSource.initialize();
|
||||||
log('database initialized');
|
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> {
|
async getUser (options: FindOneOptions<User>): Promise<User | null> {
|
||||||
@ -60,6 +85,13 @@ export class Database {
|
|||||||
return updateResult.affected > 0;
|
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> {
|
async getOrganization (options: FindOneOptions<Organization>): Promise<Organization | null> {
|
||||||
const organizationRepository = this.dataSource.getRepository(Organization);
|
const organizationRepository = this.dataSource.getRepository(Organization);
|
||||||
const organization = await organizationRepository.findOne(options);
|
const organization = await organizationRepository.findOne(options);
|
||||||
|
@ -2,6 +2,7 @@ import fs from 'fs-extra';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import toml from 'toml';
|
import toml from 'toml';
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
|
import { DataSource, DeepPartial, EntityTarget, ObjectLiteral } from 'typeorm';
|
||||||
|
|
||||||
const log = debug('snowball:utils');
|
const log = debug('snowball:utils');
|
||||||
|
|
||||||
@ -19,3 +20,44 @@ export const getConfig = async <ConfigType>(
|
|||||||
|
|
||||||
return config;
|
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 { DataSource } from 'typeorm';
|
||||||
import * as fs from 'fs/promises';
|
|
||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
@ -11,7 +10,7 @@ import { EnvironmentVariable } from '../src/entity/EnvironmentVariable';
|
|||||||
import { Domain } from '../src/entity/Domain';
|
import { Domain } from '../src/entity/Domain';
|
||||||
import { ProjectMember } from '../src/entity/ProjectMember';
|
import { ProjectMember } from '../src/entity/ProjectMember';
|
||||||
import { Deployment } from '../src/entity/Deployment';
|
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 { Config } from '../src/config';
|
||||||
import { DEFAULT_CONFIG_FILE_PATH } from '../src/constants';
|
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 ENVIRONMENT_VARIABLE_DATA_PATH = './fixtures/environment-variables.json';
|
||||||
const REDIRECTED_DOMAIN_DATA_PATH = './fixtures/redirected-domains.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 generateTestData = async (dataSource: DataSource) => {
|
||||||
const savedUsers = await loadAndSaveData(User, dataSource, path.resolve(__dirname, USER_DATA_PATH));
|
const userEntities = await getEntities(path.resolve(__dirname, USER_DATA_PATH));
|
||||||
const savedOrgs = await loadAndSaveData(Organization, dataSource, path.resolve(__dirname, ORGANIZATION_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 = {
|
const projectRelations = {
|
||||||
owner: savedUsers,
|
owner: savedUsers,
|
||||||
organization: savedOrgs
|
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);
|
const domainRepository = dataSource.getRepository(Domain);
|
||||||
|
|
||||||
@ -71,14 +47,16 @@ const generateTestData = async (dataSource: DataSource) => {
|
|||||||
project: savedProjects
|
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 = {
|
const domainRedirectedRelations = {
|
||||||
project: savedProjects,
|
project: savedProjects,
|
||||||
redirectTo: savedPrimaryDomains
|
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();
|
const savedDomains = await domainRepository.find();
|
||||||
|
|
||||||
@ -87,14 +65,16 @@ const generateTestData = async (dataSource: DataSource) => {
|
|||||||
organization: savedOrgs
|
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 = {
|
const projectMemberRelations = {
|
||||||
member: savedUsers,
|
member: savedUsers,
|
||||||
project: savedProjects
|
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 = {
|
const deploymentRelations = {
|
||||||
project: savedProjects,
|
project: savedProjects,
|
||||||
@ -102,23 +82,15 @@ const generateTestData = async (dataSource: DataSource) => {
|
|||||||
createdBy: savedUsers
|
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 = {
|
const environmentVariableRelations = {
|
||||||
project: savedProjects
|
project: savedProjects
|
||||||
};
|
};
|
||||||
|
|
||||||
await loadAndSaveData(EnvironmentVariable, dataSource, path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH), environmentVariableRelations);
|
const environmentVariablesEntities = await getEntities(path.resolve(__dirname, ENVIRONMENT_VARIABLE_DATA_PATH));
|
||||||
};
|
await loadAndSaveData(EnvironmentVariable, dataSource, environmentVariablesEntities, environmentVariableRelations);
|
||||||
|
|
||||||
const checkFileExists = async (filePath: string) => {
|
|
||||||
try {
|
|
||||||
await fs.access(filePath, fs.constants.F_OK);
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
log(err);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
|
@ -16,6 +16,7 @@ const DEFAULT_FILTER_VALUE: FilterValue = {
|
|||||||
searchedBranch: '',
|
searchedBranch: '',
|
||||||
status: StatusOptions.ALL_STATUS,
|
status: StatusOptions.ALL_STATUS,
|
||||||
};
|
};
|
||||||
|
const FETCH_DEPLOYMENTS_INTERVAL = 5000;
|
||||||
|
|
||||||
const DeploymentsTabPanel = () => {
|
const DeploymentsTabPanel = () => {
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
@ -26,22 +27,30 @@ const DeploymentsTabPanel = () => {
|
|||||||
const [deployments, setDeployments] = useState<Deployment[]>([]);
|
const [deployments, setDeployments] = useState<Deployment[]>([]);
|
||||||
const [prodBranchDomains, setProdBranchDomains] = useState<Domain[]>([]);
|
const [prodBranchDomains, setProdBranchDomains] = useState<Domain[]>([]);
|
||||||
|
|
||||||
const fetchDeployments = async () => {
|
const fetchDeployments = useCallback(async () => {
|
||||||
const { deployments } = await client.getDeployments(project.id);
|
const { deployments } = await client.getDeployments(project.id);
|
||||||
setDeployments(deployments);
|
setDeployments(deployments);
|
||||||
};
|
}, [client]);
|
||||||
|
|
||||||
const fetchProductionBranchDomains = useCallback(async () => {
|
const fetchProductionBranchDomains = useCallback(async () => {
|
||||||
const { domains } = await client.getDomains(project.id, {
|
const { domains } = await client.getDomains(project.id, {
|
||||||
branch: project.prodBranch,
|
branch: project.prodBranch,
|
||||||
});
|
});
|
||||||
setProdBranchDomains(domains);
|
setProdBranchDomains(domains);
|
||||||
}, []);
|
}, [client]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDeployments();
|
|
||||||
fetchProductionBranchDomains();
|
fetchProductionBranchDomains();
|
||||||
}, []);
|
fetchDeployments();
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchDeployments();
|
||||||
|
}, FETCH_DEPLOYMENTS_INTERVAL);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
};
|
||||||
|
}, [fetchDeployments, fetchProductionBranchDomains]);
|
||||||
|
|
||||||
const currentDeployment = useMemo(() => {
|
const currentDeployment = useMemo(() => {
|
||||||
return deployments.find((deployment) => {
|
return deployments.find((deployment) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user