Add GQL mutation for redeploying deployment to production (#42)

* Add graphql mutation to redeploy deployment to production

* Implement frontend to redeploy deployment to production

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
Nabarun Gogoi 2024-01-25 17:34:13 +05:30 committed by Ashwin Phatak
parent 5310d7c7d0
commit 0feeb9408d
11 changed files with 99 additions and 22 deletions

View File

@ -24,6 +24,7 @@
{ {
"allowArgumentsExplicitlyTypedAsAny": true "allowArgumentsExplicitlyTypedAsAny": true
} }
] ],
"@typescript-eslint/no-unused-vars": ["error", { "ignoreRestSiblings": true }]
} }
} }

View File

@ -8,7 +8,7 @@ import { User } from './entity/User';
import { Organization } from './entity/Organization'; import { Organization } from './entity/Organization';
import { UserOrganization } from './entity/UserOrganization'; import { UserOrganization } from './entity/UserOrganization';
import { Project } from './entity/Project'; import { Project } from './entity/Project';
import { Deployment } from './entity/Deployment'; import { Deployment, Environment } from './entity/Deployment';
import { ProjectMember } from './entity/ProjectMember'; import { ProjectMember } from './entity/ProjectMember';
import { EnvironmentVariable } from './entity/EnvironmentVariable'; import { EnvironmentVariable } from './entity/EnvironmentVariable';
@ -243,4 +243,36 @@ export class Database {
return false; return false;
} }
} }
async redeployToProdById (deploymentId: string): Promise<boolean> {
const deploymentRepository = this.dataSource.getRepository(Deployment);
const deployment = await deploymentRepository.findOne({
relations: {
project: true,
domain: true
},
where: {
id: Number(deploymentId)
}
});
if (deployment === null) {
throw new Error('Deployment not found');
}
const { id, createdAt, updatedAt, ...updatedDeployment } = deployment;
if (updatedDeployment.environment === Environment.Production) {
// TODO: Put isCurrent field in project
updatedDeployment.isCurrent = true;
}
await deploymentRepository.update({ id: Number(deploymentId) }, { domain: null, isCurrent: false });
const savedUpdatedDeployment = await deploymentRepository.save(updatedDeployment);
if (savedUpdatedDeployment) {
return true;
} else {
return false;
}
}
} }

View File

@ -35,7 +35,7 @@ export class Deployment {
@OneToOne(() => Domain) @OneToOne(() => Domain)
@JoinColumn({ name: 'domainId' }) @JoinColumn({ name: 'domainId' })
domain!: Domain; domain!: Domain | null;
@Column('varchar') @Column('varchar')
branch!: string; branch!: string;

View File

@ -146,6 +146,15 @@ export const createResolvers = async (db: Database): Promise<any> => {
log(err); log(err);
return false; return false;
} }
},
redeployToProd: async (_: any, { deploymentId }: {deploymentId: string }) => {
try {
return db.redeployToProdById(deploymentId);
} catch (err) {
log(err);
return false;
}
} }
} }
}; };

View File

@ -127,6 +127,7 @@ type Mutation {
addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean! addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean!
updateDeploymentToProd(deploymentId: String!): Boolean! updateDeploymentToProd(deploymentId: String!): Boolean!
updateProject(projectId: String!, updateProject: UpdateProjectInput): Boolean! updateProject(projectId: String!, updateProject: UpdateProjectInput): Boolean!
redeployToProd(deploymentId: String!): Boolean!
} }
input AddEnvironmentVariableInput { input AddEnvironmentVariableInput {

View File

@ -5,7 +5,7 @@
"title": "nextjs-boilerplate-1", "title": "nextjs-boilerplate-1",
"status": "Building", "status": "Building",
"environment": "Production", "environment": "Production",
"isCurrent": false, "isCurrent": true,
"branch": "prod", "branch": "prod",
"commitHash": "testXyz" "commitHash": "testXyz"
}, },
@ -15,7 +15,7 @@
"title": "nextjs-boilerplate-2", "title": "nextjs-boilerplate-2",
"status": "Ready", "status": "Ready",
"environment": "Preview", "environment": "Preview",
"isCurrent": true, "isCurrent": false,
"branch": "prod", "branch": "prod",
"commitHash": "testXyz" "commitHash": "testXyz"
}, },
@ -35,7 +35,7 @@
"title": "nextjs-boilerplate-1", "title": "nextjs-boilerplate-1",
"status": "Building", "status": "Building",
"environment": "Production", "environment": "Production",
"isCurrent": false, "isCurrent": true,
"branch": "prod", "branch": "prod",
"commitHash": "testXyz" "commitHash": "testXyz"
}, },
@ -45,7 +45,7 @@
"title": "nextjs-boilerplate-2", "title": "nextjs-boilerplate-2",
"status": "Ready", "status": "Ready",
"environment": "Preview", "environment": "Preview",
"isCurrent": true, "isCurrent": false,
"branch": "prod", "branch": "prod",
"commitHash": "testXyz" "commitHash": "testXyz"
}, },
@ -65,7 +65,7 @@
"title": "nextjs-boilerplate-1", "title": "nextjs-boilerplate-1",
"status": "Building", "status": "Building",
"environment": "Production", "environment": "Production",
"isCurrent": false, "isCurrent": true,
"branch": "prod", "branch": "prod",
"commitHash": "testXyz" "commitHash": "testXyz"
}, },
@ -75,7 +75,7 @@
"title": "nextjs-boilerplate-2", "title": "nextjs-boilerplate-2",
"status": "Ready", "status": "Ready",
"environment": "Preview", "environment": "Preview",
"isCurrent": true, "isCurrent": false,
"branch": "prod", "branch": "prod",
"commitHash": "testXyz" "commitHash": "testXyz"
}, },

View File

@ -117,14 +117,12 @@ const main = async () => {
await dataSource.initialize(); await dataSource.initialize();
await generateTestData(dataSource); await generateTestData(dataSource);
log('Data loaded successfully');
} else { } else {
throw new Error('Database already exists'); log('WARNING: Database already exists');
} }
}; };
main().then(() => { main().catch((err) => {
log('Data loaded successfully'); log(err);
}) });
.catch((err) => {
log(err);
});

View File

@ -50,6 +50,16 @@ const DeploymentDetailsCard = ({
} }
}; };
const redeployToProd = async () => {
const isRedeployed = await client.redeployToProd(deployment.id);
if (isRedeployed) {
await onUpdate();
toast.success('Redeployed to production');
} else {
toast.error('Unable to redeploy to production');
}
};
return ( return (
<div className="grid grid-cols-4 gap-2 border-b border-gray-300 p-3 my-2"> <div className="grid grid-cols-4 gap-2 border-b border-gray-300 p-3 my-2">
<div className="col-span-2"> <div className="col-span-2">
@ -63,7 +73,9 @@ const DeploymentDetailsCard = ({
/> />
</div> </div>
<Typography color="gray"> <Typography color="gray">
{deployment.isProduction ? 'Production (Current)' : 'Preview'} {deployment.isProduction
? `Production ${deployment.isCurrent ? '(Current)' : ''}`
: 'Preview'}
</Typography> </Typography>
</div> </div>
<div className="col-span-1"> <div className="col-span-1">
@ -94,6 +106,7 @@ const DeploymentDetailsCard = ({
<hr className="my-3" /> <hr className="my-3" />
<MenuItem <MenuItem
onClick={() => setRedeployToProduction(!redeployToProduction)} onClick={() => setRedeployToProduction(!redeployToProduction)}
disabled={!(deployment.isProduction && deployment.isCurrent)}
> >
^ Redeploy to production ^ Redeploy to production
</MenuItem> </MenuItem>
@ -113,8 +126,8 @@ const DeploymentDetailsCard = ({
open={changeToProduction} open={changeToProduction}
confirmButtonTitle="Change" confirmButtonTitle="Change"
color="blue" color="blue"
handleConfirm={() => { handleConfirm={async () => {
updateDeployment(); await updateDeployment();
setChangeToProduction((preVal) => !preVal); setChangeToProduction((preVal) => !preVal);
}} }}
> >
@ -140,7 +153,10 @@ const DeploymentDetailsCard = ({
open={redeployToProduction} open={redeployToProduction}
confirmButtonTitle="Redeploy" confirmButtonTitle="Redeploy"
color="blue" color="blue"
handleConfirm={() => setRedeployToProduction((preVal) => !preVal)} handleConfirm={async () => {
await redeployToProd();
setRedeployToProduction((preVal) => !preVal);
}}
> >
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Typography variant="small"> <Typography variant="small">

View File

@ -1,8 +1,8 @@
import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject } from './queries'; import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject } from './queries';
import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput } from './types'; import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse } from './types';
import { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation } from './mutations'; import { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd } from './mutations';
export interface GraphQLConfig { export interface GraphQLConfig {
gqlEndpoint: string; gqlEndpoint: string;
@ -147,4 +147,15 @@ export class GQLClient {
return data; return data;
} }
async redeployToProd (deploymentId: string): Promise<RedeployToProdResponse> {
const { data } = await this.client.mutate({
mutation: redeployToProd,
variables: {
deploymentId
}
});
return data;
}
} }

View File

@ -21,5 +21,10 @@ mutation ($deploymentId: String!) {
export const updateProjectMutation = gql` export const updateProjectMutation = gql`
mutation ($projectId: String!, $updateProject: UpdateProjectInput) { mutation ($projectId: String!, $updateProject: UpdateProjectInput) {
updateProject(projectId: $projectId, updateProject: $updateProject) updateProject(projectId: $projectId, updateProject: $updateProject)
}`;
export const redeployToProd = gql`
mutation ($deploymentId: String!) {
redeployToProd(deploymentId: $deploymentId)
} }
`; `;

View File

@ -182,3 +182,7 @@ export type UpdateProjectInput = {
name: string name: string
description: string description: string
} }
export type RedeployToProdResponse = {
redeployToProd: boolean
}