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
					
				@ -33,6 +33,9 @@
 | 
			
		||||
- 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
 | 
			
		||||
    - 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>
 | 
			
		||||
 | 
			
		||||
      <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">main</span> branch initiates a production
 | 
			
		||||
          deployment. You can opt for a different branch for deployment in the
 | 
			
		||||
          settings.
 | 
			
		||||
            <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>
 | 
			
		||||
        {!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"
 | 
			
		||||
            {...registerProdBranch('prodBranch')}
 | 
			
		||||
          />
 | 
			
		||||
        <Button size="sm" disabled className="mt-1">
 | 
			
		||||
          <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