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
}
]
],
"@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 { UserOrganization } from './entity/UserOrganization';
import { Project } from './entity/Project';
import { Deployment } from './entity/Deployment';
import { Deployment, Environment } from './entity/Deployment';
import { ProjectMember } from './entity/ProjectMember';
import { EnvironmentVariable } from './entity/EnvironmentVariable';
@ -243,4 +243,36 @@ export class Database {
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)
@JoinColumn({ name: 'domainId' })
domain!: Domain;
domain!: Domain | null;
@Column('varchar')
branch!: string;

View File

@ -146,6 +146,15 @@ export const createResolvers = async (db: Database): Promise<any> => {
log(err);
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!
updateDeploymentToProd(deploymentId: String!): Boolean!
updateProject(projectId: String!, updateProject: UpdateProjectInput): Boolean!
redeployToProd(deploymentId: String!): Boolean!
}
input AddEnvironmentVariableInput {

View File

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

View File

@ -117,14 +117,12 @@ const main = async () => {
await dataSource.initialize();
await generateTestData(dataSource);
log('Data loaded successfully');
} else {
throw new Error('Database already exists');
log('WARNING: Database already exists');
}
};
main().then(() => {
log('Data loaded successfully');
})
.catch((err) => {
log(err);
});
main().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 (
<div className="grid grid-cols-4 gap-2 border-b border-gray-300 p-3 my-2">
<div className="col-span-2">
@ -63,7 +73,9 @@ const DeploymentDetailsCard = ({
/>
</div>
<Typography color="gray">
{deployment.isProduction ? 'Production (Current)' : 'Preview'}
{deployment.isProduction
? `Production ${deployment.isCurrent ? '(Current)' : ''}`
: 'Preview'}
</Typography>
</div>
<div className="col-span-1">
@ -94,6 +106,7 @@ const DeploymentDetailsCard = ({
<hr className="my-3" />
<MenuItem
onClick={() => setRedeployToProduction(!redeployToProduction)}
disabled={!(deployment.isProduction && deployment.isCurrent)}
>
^ Redeploy to production
</MenuItem>
@ -113,8 +126,8 @@ const DeploymentDetailsCard = ({
open={changeToProduction}
confirmButtonTitle="Change"
color="blue"
handleConfirm={() => {
updateDeployment();
handleConfirm={async () => {
await updateDeployment();
setChangeToProduction((preVal) => !preVal);
}}
>
@ -140,7 +153,10 @@ const DeploymentDetailsCard = ({
open={redeployToProduction}
confirmButtonTitle="Redeploy"
color="blue"
handleConfirm={() => setRedeployToProduction((preVal) => !preVal)}
handleConfirm={async () => {
await redeployToProd();
setRedeployToProduction((preVal) => !preVal);
}}
>
<div className="flex flex-col gap-2">
<Typography variant="small">

View File

@ -1,8 +1,8 @@
import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client';
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 { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation } from './mutations';
import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, RemoveMemberResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse } from './types';
import { removeMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd } from './mutations';
export interface GraphQLConfig {
gqlEndpoint: string;
@ -147,4 +147,15 @@ export class GQLClient {
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`
mutation ($projectId: String!, $updateProject: UpdateProjectInput) {
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
description: string
}
export type RedeployToProdResponse = {
redeployToProd: boolean
}