From 7e2a3130121002042ac3c01f8e8c9cd5da8afdde Mon Sep 17 00:00:00 2001 From: Nabarun Gogoi Date: Thu, 1 Feb 2024 14:04:07 +0530 Subject: [PATCH] Implement functionality to add and delete Deploy webhooks (#47) * Add mutation to add webhooks * Implement frontend to add webhooks and refactor code to use updateProject resolver * Implement functionality to delete webhooks * Refactor webhook card component * Update readme for frontend env GitHub OAuth client ID --------- Co-authored-by: neeraj --- README.md | 14 +++- packages/backend/src/resolvers.ts | 14 +--- packages/backend/src/schema.gql | 3 +- packages/frontend/.env | 2 +- .../projects/project/settings/GitTabPanel.tsx | 78 ++++++++++++------- .../projects/project/settings/WebhookCard.tsx | 20 +++-- packages/gql-client/src/client.ts | 16 +--- packages/gql-client/src/mutations.ts | 6 -- packages/gql-client/src/types.ts | 10 +-- 9 files changed, 80 insertions(+), 83 deletions(-) diff --git a/README.md b/README.md index 541fe042..354bb05f 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ ``` - 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 + - 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 - In "Homepage URL", type `http://localhost:3000` - In "Authorization callback URL", type `http://localhost:3000/projects/create` @@ -43,16 +43,22 @@ yarn start ``` +- Change directory to `packages/frontend` in a new terminal + + ```bash + cd 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' ``` -- Change directory to `packages/frontend` +- Copy the GitHub OAuth app client ID from previous steps and set it in frontend [.env](packages/frontend/.env) file - ```bash - cd packages/frontend + ``` + REACT_APP_GITHUB_CLIENT_ID = ``` - Start the React application diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index 2dfef012..f4437361 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -9,6 +9,7 @@ import { deploymentToGqlType, projectMemberToGqlType, projectToGqlType, environm import { Environment } from './entity/Deployment'; import { Permission } from './entity/ProjectMember'; import { Domain } from './entity/Domain'; +import { Project } from './entity/Project'; const log = debug('snowball:database'); @@ -188,9 +189,9 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise } }, - updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: { name: string, description: string } }) => { + updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: DeepPartial }) => { try { - return db.updateProjectById(projectId, projectDetails); + return await db.updateProjectById(projectId, projectDetails); } catch (err) { log(err); return false; @@ -273,15 +274,6 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise 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); - } } } }; diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index 09c1df39..4458f80a 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -139,7 +139,6 @@ 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! @@ -159,6 +158,8 @@ input AddEnvironmentVariableInput { input UpdateProjectInput { name: String description: String + prodBranch: String + webhooks: [String!] } input AddDomainInput { diff --git a/packages/frontend/.env b/packages/frontend/.env index 1c7a146a..04eea5f8 100644 --- a/packages/frontend/.env +++ b/packages/frontend/.env @@ -1,3 +1,3 @@ REACT_APP_GQL_SERVER_URL = 'http://localhost:8000/graphql' -REACT_APP_GITHUB_CLIENT_ID = 4720362b6740b00652b6 +REACT_APP_GITHUB_CLIENT_ID = diff --git a/packages/frontend/src/components/projects/project/settings/GitTabPanel.tsx b/packages/frontend/src/components/projects/project/settings/GitTabPanel.tsx index d6c51bf5..d7310c8e 100644 --- a/packages/frontend/src/components/projects/project/settings/GitTabPanel.tsx +++ b/packages/frontend/src/components/projects/project/settings/GitTabPanel.tsx @@ -17,17 +17,6 @@ const GitTabPanel = ({ }) => { const client = useGQLClient(); - const { - register, - handleSubmit, - reset, - formState: { isSubmitSuccessful }, - } = useForm({ - defaultValues: { - webhookUrl: project.webhooks, - }, - }); - const { register: registerProdBranch, handleSubmit: handleSubmitProdBranch, @@ -40,12 +29,11 @@ const GitTabPanel = ({ const updateProdBranchHandler = useCallback( async (data: any) => { - const { updateProdBranch } = await client.updateProdBranch( - project.id, - data.prodBranch, - ); + const { updateProject } = await client.updateProject(project.id, { + prodBranch: data.prodBranch, + }); - if (updateProdBranch) { + if (updateProject) { await onUpdate(); toast.success('Production branch upadated successfully'); } else { @@ -55,9 +43,33 @@ const GitTabPanel = ({ [project], ); - useEffect(() => { - reset(); - }, [isSubmitSuccessful]); + const { + register: registerWebhooks, + handleSubmit: handleSubmitWebhooks, + reset: resetWebhooks, + } = useForm({ + defaultValues: { + webhookUrl: '', + }, + }); + + const updateWebhooksHandler = useCallback( + async (data: any) => { + const { updateProject } = await client.updateProject(project.id, { + webhooks: [...project.webhooks, data.webhookUrl], + }); + + if (updateProject) { + await onUpdate(); + toast.success('Webhook added successfully'); + } else { + toast.error('Error adding webhook'); + } + + resetWebhooks(); + }, + [project], + ); useEffect(() => { resetProdBranch({ @@ -65,8 +77,18 @@ const GitTabPanel = ({ }); }, [project]); - const handleDelete = () => { - // TODO: Impletement functionality to delete webhooks + const handleDeleteWebhook = async (index: number) => { + project.webhooks.splice(index, 1); + const { updateProject } = await client.updateProject(project.id, { + webhooks: project.webhooks, + }); + + if (updateProject) { + await onUpdate(); + toast.success('Webhook deleted successfully'); + } else { + toast.error('Error deleting webhook'); + } }; return ( @@ -117,11 +139,7 @@ const GitTabPanel = ({ -
{ - toast.success('Webhook added successfully.'); - })} - > +
Deploy webhooks @@ -133,7 +151,10 @@ const GitTabPanel = ({
Webhook URL - +
diff --git a/packages/gql-client/src/client.ts b/packages/gql-client/src/client.ts index 6e4bbe6b..55e4a6f2 100644 --- a/packages/gql-client/src/client.ts +++ b/packages/gql-client/src/client.ts @@ -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, UpdateProdBranchResponse } from './types'; -import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain, updateProdBranch } 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 } from './types'; +import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain } from './mutations'; export interface GraphQLConfig { gqlEndpoint: string; @@ -292,16 +292,4 @@ export class GQLClient { return data; } - - async updateProdBranch (projectId: string, prodBranch: string): Promise { - const { data } = await this.client.mutate({ - mutation: updateProdBranch, - variables: { - projectId, - prodBranch - } - }); - - return data; - } } diff --git a/packages/gql-client/src/mutations.ts b/packages/gql-client/src/mutations.ts index 80342024..17949959 100644 --- a/packages/gql-client/src/mutations.ts +++ b/packages/gql-client/src/mutations.ts @@ -86,9 +86,3 @@ export const unauthenticateGitHub = gql` mutation { unauthenticateGitHub }`; - -export const updateProdBranch = gql` -mutation ($projectId: String!, $prodBranch: String!) { - updateProdBranch(projectId: $projectId, prodBranch: $prodBranch) -} -`; diff --git a/packages/gql-client/src/types.ts b/packages/gql-client/src/types.ts index debb1bc1..22ebf441 100644 --- a/packages/gql-client/src/types.ts +++ b/packages/gql-client/src/types.ts @@ -223,8 +223,10 @@ export type DeleteDomainResponse = { } export type UpdateProjectInput = { - name: string - description: string + name?: string + description?: string + prodBranch?: string + webhooks?: string[] } export type UpdateDomainInput = { @@ -258,7 +260,3 @@ export type AuthenticateGitHubResponse = { export type UnauthenticateGitHubResponse = { unauthenticateGitHub: boolean } - -export type UpdateProdBranchResponse = { - updateProdBranch: boolean -}