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)
|
- 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
|
- 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
|
- Start the server
|
||||||
|
|
||||||
@ -40,7 +43,7 @@
|
|||||||
yarn start
|
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'
|
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);
|
log(err);
|
||||||
return false;
|
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!
|
updateEnvironmentVariable(environmentVariableId: String!, environmentVariable: UpdateEnvironmentVariableInput!): Boolean!
|
||||||
updateDeploymentToProd(deploymentId: String!): Boolean!
|
updateDeploymentToProd(deploymentId: String!): Boolean!
|
||||||
updateProject(projectId: String!, projectDetails: UpdateProjectInput): Boolean!
|
updateProject(projectId: String!, projectDetails: UpdateProjectInput): Boolean!
|
||||||
|
updateProdBranch(projectId: String!, prodBranch: String!): Boolean!
|
||||||
redeployToProd(deploymentId: String!): Boolean!
|
redeployToProd(deploymentId: String!): Boolean!
|
||||||
deleteProject(projectId: String!): Boolean!
|
deleteProject(projectId: String!): Boolean!
|
||||||
deleteDomain(domainId: 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 { useForm } from 'react-hook-form';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
|
import { Project } from 'gql-client';
|
||||||
|
|
||||||
import { Button, Input, Switch, Typography } from '@material-tailwind/react';
|
import { Button, Input, Switch, Typography } from '@material-tailwind/react';
|
||||||
|
|
||||||
import { GitRepositoryDetails } from '../../../../types/project';
|
|
||||||
import WebhookCard from './WebhookCard';
|
import WebhookCard from './WebhookCard';
|
||||||
|
import { useGQLClient } from '../../../../context/GQLClientContext';
|
||||||
|
|
||||||
const GitTabPanel = () => {
|
const GitTabPanel = ({
|
||||||
// TODO: Get linked repo from project
|
project,
|
||||||
const [linkedRepo] = useState<GitRepositoryDetails>();
|
onUpdate,
|
||||||
const [webhooksArray, setWebhooksArray] = useState<Array<string>>([]);
|
}: {
|
||||||
|
project: Project;
|
||||||
|
onUpdate: () => Promise<void>;
|
||||||
|
}) => {
|
||||||
|
const client = useGQLClient();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
formState: { isSubmitSuccessful },
|
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(() => {
|
useEffect(() => {
|
||||||
reset();
|
reset();
|
||||||
}, [isSubmitSuccessful]);
|
}, [isSubmitSuccessful]);
|
||||||
|
|
||||||
const handleDelete = (index: number) => {
|
useEffect(() => {
|
||||||
const newArray = webhooksArray.filter((value, idx) => idx != index);
|
resetProdBranch({
|
||||||
setWebhooksArray(newArray);
|
prodBranch: project.prodBranch,
|
||||||
toast.success('Webhook deleted successfully');
|
});
|
||||||
|
}, [project]);
|
||||||
|
|
||||||
|
const handleDelete = () => {
|
||||||
|
// TODO: Impletement functionality to delete webhooks
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -55,44 +95,30 @@ const GitTabPanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmitProdBranch(updateProdBranchHandler)}>
|
||||||
<div className="mb-2 p-2">
|
<div className="mb-2 p-2">
|
||||||
<Typography variant="h6" className="text-black">
|
<Typography variant="h6" className="text-black">
|
||||||
Production branch
|
Production branch
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="small">
|
<Typography variant="small">
|
||||||
By default, each commit pushed to the{' '}
|
By default, each commit pushed to the{' '}
|
||||||
<span className="font-bold">main</span> branch initiates a production
|
<span className="font-bold">{project.prodBranch}</span> branch
|
||||||
deployment. You can opt for a different branch for deployment in the
|
initiates a production deployment. You can opt for a different
|
||||||
settings.
|
branch for deployment in the settings.
|
||||||
</Typography>
|
</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>
|
<Typography variant="small">Branch name</Typography>
|
||||||
<Input
|
<Input
|
||||||
crossOrigin={undefined}
|
crossOrigin={undefined}
|
||||||
disabled={Boolean(!linkedRepo)}
|
{...registerProdBranch('prodBranch')}
|
||||||
value="main"
|
|
||||||
/>
|
/>
|
||||||
<Button size="sm" disabled className="mt-1">
|
<Button size="sm" className="mt-1" type="submit">
|
||||||
Save
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit((data) => {
|
onSubmit={handleSubmit(() => {
|
||||||
setWebhooksArray((prevArray) => [...prevArray, data.webhookUrl]);
|
|
||||||
|
|
||||||
toast.success('Webhook added successfully.');
|
toast.success('Webhook added successfully.');
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@ -118,12 +144,12 @@ const GitTabPanel = () => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<div className="mb-2 p-2">
|
<div className="mb-2 p-2">
|
||||||
{webhooksArray?.map((webhookUrl, index) => {
|
{project.webhooks.map((webhookUrl, index) => {
|
||||||
return (
|
return (
|
||||||
<WebhookCard
|
<WebhookCard
|
||||||
webhooksArray={webhooksArray}
|
webhooksArray={project.webhooks}
|
||||||
webhookUrl={webhookUrl}
|
webhookUrl={webhookUrl}
|
||||||
handleDelete={() => handleDelete(index)}
|
handleDelete={() => handleDelete()}
|
||||||
key={index}
|
key={index}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -41,6 +41,7 @@ const NewProject = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Handle React component error
|
||||||
const interceptor = async (error: RequestError | Error) => {
|
const interceptor = async (error: RequestError | Error) => {
|
||||||
if (
|
if (
|
||||||
error instanceof RequestError &&
|
error instanceof RequestError &&
|
||||||
|
@ -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, getDomains, getProjectsInOrganization } from './queries';
|
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 { 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 } from './mutations';
|
import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain, updateProdBranch } from './mutations';
|
||||||
|
|
||||||
export interface GraphQLConfig {
|
export interface GraphQLConfig {
|
||||||
gqlEndpoint: string;
|
gqlEndpoint: string;
|
||||||
@ -292,4 +292,16 @@ export class GQLClient {
|
|||||||
|
|
||||||
return data;
|
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 {
|
mutation {
|
||||||
unauthenticateGitHub
|
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 = {
|
export type UnauthenticateGitHubResponse = {
|
||||||
unauthenticateGitHub: boolean
|
unauthenticateGitHub: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UpdateProdBranchResponse = {
|
||||||
|
updateProdBranch: boolean
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user