forked from cerc-io/snowballtools-base
		
	Implement functionality to update production branch in project settings (#60)
* Add mutation to update prod branch for project * Implement frontend to update production branch * Handle review changes * Update README for Github creating OAuth app --------- Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
		
							parent
							
								
									1d58beb2ec
								
							
						
					
					
						commit
						aa49bb0de2
					
				| @ -32,7 +32,10 @@ | ||||
| 
 | ||||
| - Set `githubOauth.clientId` and `githubOauth.clientSecret` in backend [config file](packages/backend/environments/local.toml) | ||||
|   - Client id and secret will be available after creating Github OAuth app | ||||
|   - https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app | ||||
|     - https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app | ||||
|     - In "Homepage URL", type `http://localhost:3000` | ||||
|     - In "Authorization callback URL", type `http://localhost:3000/projects/create` | ||||
|     - Generate a new client secret after app is created | ||||
| 
 | ||||
| - Start the server | ||||
| 
 | ||||
| @ -40,7 +43,7 @@ | ||||
|   yarn start | ||||
|   ``` | ||||
| 
 | ||||
| - Copy the graphQL endpoint from terminal and add the endpoint in the `.env` file present in `packages/frontend` | ||||
| - Copy the graphQL endpoint from terminal and add the endpoint in the [.env](packages/frontend/.env) file present in `packages/frontend` | ||||
| 
 | ||||
|   ``` | ||||
|   REACT_APP_GQL_SERVER_URL = 'http://localhost:8000/graphql' | ||||
|  | ||||
| @ -273,6 +273,15 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any> | ||||
|           log(err); | ||||
|           return false; | ||||
|         } | ||||
|       }, | ||||
| 
 | ||||
|       updateProdBranch: async (_: any, { projectId, prodBranch }: {projectId: string, prodBranch: string }) => { | ||||
|         try { | ||||
|           return await db.updateProjectById(projectId, { prodBranch }); | ||||
|         } catch (err) { | ||||
|           log(err); | ||||
|           return (false); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|  | ||||
| @ -139,6 +139,7 @@ type Mutation { | ||||
|   updateEnvironmentVariable(environmentVariableId: String!, environmentVariable: UpdateEnvironmentVariableInput!): Boolean! | ||||
|   updateDeploymentToProd(deploymentId: String!): Boolean! | ||||
|   updateProject(projectId: String!, projectDetails: UpdateProjectInput): Boolean! | ||||
|   updateProdBranch(projectId: String!, prodBranch: String!): Boolean! | ||||
|   redeployToProd(deploymentId: String!): Boolean! | ||||
|   deleteProject(projectId: String!): Boolean! | ||||
|   deleteDomain(domainId: String!): Boolean! | ||||
|  | ||||
| @ -1,32 +1,72 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import React, { useCallback, useEffect } from 'react'; | ||||
| import { useForm } from 'react-hook-form'; | ||||
| import toast from 'react-hot-toast'; | ||||
| import { Project } from 'gql-client'; | ||||
| 
 | ||||
| import { Button, Input, Switch, Typography } from '@material-tailwind/react'; | ||||
| 
 | ||||
| import { GitRepositoryDetails } from '../../../../types/project'; | ||||
| import WebhookCard from './WebhookCard'; | ||||
| import { useGQLClient } from '../../../../context/GQLClientContext'; | ||||
| 
 | ||||
| const GitTabPanel = () => { | ||||
|   // TODO: Get linked repo from project
 | ||||
|   const [linkedRepo] = useState<GitRepositoryDetails>(); | ||||
|   const [webhooksArray, setWebhooksArray] = useState<Array<string>>([]); | ||||
| const GitTabPanel = ({ | ||||
|   project, | ||||
|   onUpdate, | ||||
| }: { | ||||
|   project: Project; | ||||
|   onUpdate: () => Promise<void>; | ||||
| }) => { | ||||
|   const client = useGQLClient(); | ||||
| 
 | ||||
|   const { | ||||
|     register, | ||||
|     handleSubmit, | ||||
|     reset, | ||||
|     formState: { isSubmitSuccessful }, | ||||
|   } = useForm(); | ||||
|   } = useForm({ | ||||
|     defaultValues: { | ||||
|       webhookUrl: project.webhooks, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   const { | ||||
|     register: registerProdBranch, | ||||
|     handleSubmit: handleSubmitProdBranch, | ||||
|     reset: resetProdBranch, | ||||
|   } = useForm({ | ||||
|     defaultValues: { | ||||
|       prodBranch: project.prodBranch, | ||||
|     }, | ||||
|   }); | ||||
| 
 | ||||
|   const updateProdBranchHandler = useCallback( | ||||
|     async (data: any) => { | ||||
|       const { updateProdBranch } = await client.updateProdBranch( | ||||
|         project.id, | ||||
|         data.prodBranch, | ||||
|       ); | ||||
| 
 | ||||
|       if (updateProdBranch) { | ||||
|         await onUpdate(); | ||||
|         toast.success('Production branch upadated successfully'); | ||||
|       } else { | ||||
|         toast.error('Error updating production branch'); | ||||
|       } | ||||
|     }, | ||||
|     [project], | ||||
|   ); | ||||
| 
 | ||||
|   useEffect(() => { | ||||
|     reset(); | ||||
|   }, [isSubmitSuccessful]); | ||||
| 
 | ||||
|   const handleDelete = (index: number) => { | ||||
|     const newArray = webhooksArray.filter((value, idx) => idx != index); | ||||
|     setWebhooksArray(newArray); | ||||
|     toast.success('Webhook deleted successfully'); | ||||
|   useEffect(() => { | ||||
|     resetProdBranch({ | ||||
|       prodBranch: project.prodBranch, | ||||
|     }); | ||||
|   }, [project]); | ||||
| 
 | ||||
|   const handleDelete = () => { | ||||
|     // TODO: Impletement functionality to delete webhooks
 | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
| @ -55,44 +95,30 @@ const GitTabPanel = () => { | ||||
|         </div> | ||||
|       </div> | ||||
| 
 | ||||
|       <div className="mb-2 p-2"> | ||||
|         <Typography variant="h6" className="text-black"> | ||||
|           Production branch | ||||
|         </Typography> | ||||
|         <Typography variant="small"> | ||||
|           By default, each commit pushed to the{' '} | ||||
|           <span className="font-bold">main</span> branch initiates a production | ||||
|           deployment. You can opt for a different branch for deployment in the | ||||
|           settings. | ||||
|         </Typography> | ||||
|         {!linkedRepo && ( | ||||
|           <div className="flex bg-blue-100 gap-4 rounded-lg p-2"> | ||||
|             <div>^</div> | ||||
|             <div> | ||||
|               <Typography variant="small"> | ||||
|                 This project isn't currently linked to a Git repository. To | ||||
|                 establish a production branch, please linked to an existing Git | ||||
|                 repository in the 'Connected Git Repository' section | ||||
|                 above. | ||||
|               </Typography> | ||||
|             </div> | ||||
|           </div> | ||||
|         )} | ||||
|         <Typography variant="small">Branch name</Typography> | ||||
|         <Input | ||||
|           crossOrigin={undefined} | ||||
|           disabled={Boolean(!linkedRepo)} | ||||
|           value="main" | ||||
|         /> | ||||
|         <Button size="sm" disabled className="mt-1"> | ||||
|           Save | ||||
|         </Button> | ||||
|       </div> | ||||
|       <form onSubmit={handleSubmitProdBranch(updateProdBranchHandler)}> | ||||
|         <div className="mb-2 p-2"> | ||||
|           <Typography variant="h6" className="text-black"> | ||||
|             Production branch | ||||
|           </Typography> | ||||
|           <Typography variant="small"> | ||||
|             By default, each commit pushed to the{' '} | ||||
|             <span className="font-bold">{project.prodBranch}</span> branch | ||||
|             initiates a production deployment. You can opt for a different | ||||
|             branch for deployment in the settings. | ||||
|           </Typography> | ||||
|           <Typography variant="small">Branch name</Typography> | ||||
|           <Input | ||||
|             crossOrigin={undefined} | ||||
|             {...registerProdBranch('prodBranch')} | ||||
|           /> | ||||
|           <Button size="sm" className="mt-1" type="submit"> | ||||
|             Save | ||||
|           </Button> | ||||
|         </div> | ||||
|       </form> | ||||
| 
 | ||||
|       <form | ||||
|         onSubmit={handleSubmit((data) => { | ||||
|           setWebhooksArray((prevArray) => [...prevArray, data.webhookUrl]); | ||||
| 
 | ||||
|         onSubmit={handleSubmit(() => { | ||||
|           toast.success('Webhook added successfully.'); | ||||
|         })} | ||||
|       > | ||||
| @ -118,12 +144,12 @@ const GitTabPanel = () => { | ||||
|         </div> | ||||
|       </form> | ||||
|       <div className="mb-2 p-2"> | ||||
|         {webhooksArray?.map((webhookUrl, index) => { | ||||
|         {project.webhooks.map((webhookUrl, index) => { | ||||
|           return ( | ||||
|             <WebhookCard | ||||
|               webhooksArray={webhooksArray} | ||||
|               webhooksArray={project.webhooks} | ||||
|               webhookUrl={webhookUrl} | ||||
|               handleDelete={() => handleDelete(index)} | ||||
|               handleDelete={() => handleDelete()} | ||||
|               key={index} | ||||
|             /> | ||||
|           ); | ||||
|  | ||||
| @ -41,6 +41,7 @@ const NewProject = () => { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // TODO: Handle React component error
 | ||||
|     const interceptor = async (error: RequestError | Error) => { | ||||
|       if ( | ||||
|         error instanceof RequestError && | ||||
|  | ||||
| @ -1,8 +1,8 @@ | ||||
| import { ApolloClient, DefaultOptions, InMemoryCache, NormalizedCacheObject } from '@apollo/client'; | ||||
| 
 | ||||
| import { getUser, getOrganizations, getDeployments, getProjectMembers, searchProjects, getEnvironmentVariables, getProject, getDomains, getProjectsInOrganization } from './queries'; | ||||
| import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse, DeleteProjectResponse, GetProjectsInOrganizationResponse, RollbackDeploymentResponse, AddDomainInput, AddDomainResponse, GetDomainsResponse, UpdateDomainInput, UpdateDomainResponse, AuthenticateGitHubResponse, UnauthenticateGitHubResponse, UpdateEnvironmentVariableResponse, UpdateEnvironmentVariableInput, RemoveEnvironmentVariableResponse, UpdateProjectMemberInput, RemoveProjectMemberResponse, UpdateProjectMemberResponse, DeleteDomainResponse } from './types'; | ||||
| import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain } from './mutations'; | ||||
| import { AddEnvironmentVariableInput, AddEnvironmentVariablesResponse, GetDeploymentsResponse, GetEnvironmentVariablesResponse, GetOrganizationsResponse, GetProjectMembersResponse, SearchProjectsResponse, GetUserResponse, UpdateDeploymentToProdResponse, GetProjectResponse, UpdateProjectResponse, UpdateProjectInput, RedeployToProdResponse, DeleteProjectResponse, GetProjectsInOrganizationResponse, RollbackDeploymentResponse, AddDomainInput, AddDomainResponse, GetDomainsResponse, UpdateDomainInput, UpdateDomainResponse, AuthenticateGitHubResponse, UnauthenticateGitHubResponse, UpdateEnvironmentVariableResponse, UpdateEnvironmentVariableInput, RemoveEnvironmentVariableResponse, UpdateProjectMemberInput, RemoveProjectMemberResponse, UpdateProjectMemberResponse, DeleteDomainResponse, UpdateProdBranchResponse } from './types'; | ||||
| import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain, updateProdBranch } from './mutations'; | ||||
| 
 | ||||
| export interface GraphQLConfig { | ||||
|   gqlEndpoint: string; | ||||
| @ -292,4 +292,16 @@ export class GQLClient { | ||||
| 
 | ||||
|     return data; | ||||
|   } | ||||
| 
 | ||||
|   async updateProdBranch (projectId: string, prodBranch: string): Promise<UpdateProdBranchResponse> { | ||||
|     const { data } = await this.client.mutate({ | ||||
|       mutation: updateProdBranch, | ||||
|       variables: { | ||||
|         projectId, | ||||
|         prodBranch | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     return data; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -86,3 +86,9 @@ export const unauthenticateGitHub = gql` | ||||
| mutation { | ||||
|   unauthenticateGitHub | ||||
| }`;
 | ||||
| 
 | ||||
| export const updateProdBranch = gql` | ||||
| mutation ($projectId: String!, $prodBranch: String!) { | ||||
|   updateProdBranch(projectId: $projectId, prodBranch: $prodBranch) | ||||
| } | ||||
| `;
 | ||||
|  | ||||
| @ -258,3 +258,7 @@ export type AuthenticateGitHubResponse = { | ||||
| export type UnauthenticateGitHubResponse = { | ||||
|   unauthenticateGitHub: boolean | ||||
| } | ||||
| 
 | ||||
| export type UpdateProdBranchResponse = { | ||||
|   updateProdBranch: boolean | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user