From 9921dc5186937baa6a2434dab9a06c6397c1181a Mon Sep 17 00:00:00 2001 From: Nabarun Gogoi Date: Wed, 14 Feb 2024 17:35:02 +0530 Subject: [PATCH] Show public repo GitHub activity without authentication (#68) * Handle request error while fetching Git activity * Use default Octokit instance if auth token is not available * Rename input arguments of mutation methods to data --------- Co-authored-by: neeraj --- packages/backend/src/database.ts | 20 ++--- packages/backend/src/resolvers.ts | 12 +-- packages/backend/src/schema.gql | 6 +- packages/backend/src/service.ts | 16 ++-- packages/frontend/src/constants.ts | 2 - .../frontend/src/context/OctokitContext.tsx | 18 +++-- .../pages/org-slug/projects/create/index.tsx | 4 +- .../pages/org-slug/projects/id/Overview.tsx | 79 +++++++++++-------- packages/gql-client/src/client.ts | 24 +++--- packages/gql-client/src/mutations.ts | 12 +-- 10 files changed, 103 insertions(+), 90 deletions(-) diff --git a/packages/backend/src/database.ts b/packages/backend/src/database.ts index bcd2a00..6ff53dd 100644 --- a/packages/backend/src/database.ts +++ b/packages/backend/src/database.ts @@ -236,9 +236,9 @@ export class Database { return savedEnvironmentVariables; } - async updateEnvironmentVariable (environmentVariableId: string, update: DeepPartial): Promise { + async updateEnvironmentVariable (environmentVariableId: string, data: DeepPartial): Promise { const environmentVariableRepository = this.dataSource.getRepository(EnvironmentVariable); - const updateResult = await environmentVariableRepository.update({ id: environmentVariableId }, update); + const updateResult = await environmentVariableRepository.update({ id: environmentVariableId }, data); return Boolean(updateResult.affected); } @@ -293,18 +293,18 @@ export class Database { return projects; } - async updateDeploymentById (deploymentId: string, updates: DeepPartial): Promise { + async updateDeploymentById (deploymentId: string, data: DeepPartial): Promise { const deploymentRepository = this.dataSource.getRepository(Deployment); - const updateResult = await deploymentRepository.update({ id: deploymentId }, updates); + const updateResult = await deploymentRepository.update({ id: deploymentId }, data); return Boolean(updateResult.affected); } - async addProject (userId: string, organizationId: string, projectDetails: DeepPartial): Promise { + async addProject (userId: string, organizationId: string, data: DeepPartial): Promise { const projectRepository = this.dataSource.getRepository(Project); // TODO: Check if organization exists - const newProject = projectRepository.create(projectDetails); + const newProject = projectRepository.create(data); // TODO: Set default empty array for webhooks in TypeORM newProject.webhooks = []; // TODO: Set icon according to framework @@ -323,9 +323,9 @@ export class Database { return projectRepository.save(newProject); } - async updateProjectById (projectId: string, updates: DeepPartial): Promise { + async updateProjectById (projectId: string, data: DeepPartial): Promise { const projectRepository = this.dataSource.getRepository(Project); - const updateResult = await projectRepository.update({ id: projectId }, updates); + const updateResult = await projectRepository.update({ id: projectId }, data); return Boolean(updateResult.affected); } @@ -372,9 +372,9 @@ export class Database { return domain; } - async updateDomainById (domainId: string, updates: DeepPartial): Promise { + async updateDomainById (domainId: string, data: DeepPartial): Promise { const domainRepository = this.dataSource.getRepository(Domain); - const updateResult = await domainRepository.update({ id: domainId }, updates); + const updateResult = await domainRepository.update({ id: domainId }, data); return Boolean(updateResult.affected); } diff --git a/packages/backend/src/resolvers.ts b/packages/backend/src/resolvers.ts index e11720a..02602aa 100644 --- a/packages/backend/src/resolvers.ts +++ b/packages/backend/src/resolvers.ts @@ -135,9 +135,9 @@ export const createResolvers = async (service: Service): Promise => { } }, - updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: DeepPartial }) => { + updateProject: async (_: any, { projectId, data }: { projectId: string, data: DeepPartial }) => { try { - return await service.updateProject(projectId, projectDetails); + return await service.updateProject(projectId, data); } catch (err) { log(err); return false; @@ -179,18 +179,18 @@ export const createResolvers = async (service: Service): Promise => { } }, - addDomain: async (_: any, { projectId, domainDetails }: { projectId: string, domainDetails: { name: string } }) => { + addDomain: async (_: any, { projectId, data }: { projectId: string, data: { name: string } }) => { try { - return Boolean(await service.addDomain(projectId, domainDetails)); + return Boolean(await service.addDomain(projectId, data)); } catch (err) { log(err); return false; } }, - updateDomain: async (_: any, { domainId, domainDetails }: { domainId: string, domainDetails: DeepPartial}) => { + updateDomain: async (_: any, { domainId, data }: { domainId: string, data: DeepPartial}) => { try { - return await service.updateDomain(domainId, domainDetails); + return await service.updateDomain(domainId, data); } catch (err) { log(err); return false; diff --git a/packages/backend/src/schema.gql b/packages/backend/src/schema.gql index c6b1764..8d04807 100644 --- a/packages/backend/src/schema.gql +++ b/packages/backend/src/schema.gql @@ -195,13 +195,13 @@ type Mutation { removeEnvironmentVariable(environmentVariableId: String!): Boolean! updateDeploymentToProd(deploymentId: String!): Boolean! addProject(organizationSlug: String!, data: AddProjectInput): Project! - updateProject(projectId: String!, projectDetails: UpdateProjectInput): Boolean! + updateProject(projectId: String!, data: UpdateProjectInput): Boolean! redeployToProd(deploymentId: String!): Boolean! deleteProject(projectId: String!): Boolean! deleteDomain(domainId: String!): Boolean! rollbackDeployment(projectId: String!, deploymentId: String!): Boolean! - addDomain(projectId: String!, domainDetails: AddDomainInput!): Boolean! - updateDomain(domainId: String!, domainDetails: UpdateDomainInput!): Boolean! + addDomain(projectId: String!, data: AddDomainInput!): Boolean! + updateDomain(domainId: String!, data: UpdateDomainInput!): Boolean! authenticateGitHub(code: String!): AuthResult! unauthenticateGitHub: Boolean! } diff --git a/packages/backend/src/service.ts b/packages/backend/src/service.ts index d127491..9301d2c 100644 --- a/packages/backend/src/service.ts +++ b/packages/backend/src/service.ts @@ -405,7 +405,7 @@ export class Service { return newCurrentDeploymentUpdate && oldCurrentDeploymentUpdate; } - async addDomain (projectId: string, domainDetails: { name: string }): Promise<{ + async addDomain (projectId: string, data: { name: string }): Promise<{ primaryDomain: Domain, redirectedDomain: Domain }> { @@ -416,14 +416,14 @@ export class Service { } const primaryDomainDetails = { - ...domainDetails, + ...data, branch: currentProject.prodBranch, project: currentProject }; const savedPrimaryDomain = await this.db.addDomain(primaryDomainDetails); - const domainArr = domainDetails.name.split('www.'); + const domainArr = data.name.split('www.'); const redirectedDomainDetails = { name: domainArr.length > 1 ? domainArr[1] : `www.${domainArr[0]}`, @@ -437,7 +437,7 @@ export class Service { return { primaryDomain: savedPrimaryDomain, redirectedDomain: savedRedirectedDomain }; } - async updateDomain (domainId: string, domainDetails: DeepPartial): Promise { + async updateDomain (domainId: string, data: DeepPartial): Promise { const domain = await this.db.getDomain({ where: { id: domainId @@ -449,7 +449,7 @@ export class Service { } const newDomain = { - ...domainDetails + ...data }; const domainsRedirectedFrom = await this.db.getDomains({ @@ -462,14 +462,14 @@ export class Service { }); // If there are domains redirecting to current domain, only branch of current domain can be updated - if (domainsRedirectedFrom.length > 0 && domainDetails.branch === domain.branch) { + if (domainsRedirectedFrom.length > 0 && data.branch === domain.branch) { throw new Error('Remove all redirects to this domain before updating'); } - if (domainDetails.redirectToId) { + if (data.redirectToId) { const redirectedDomain = await this.db.getDomain({ where: { - id: domainDetails.redirectToId + id: data.redirectToId } }); diff --git a/packages/frontend/src/constants.ts b/packages/frontend/src/constants.ts index c665657..4eeb139 100644 --- a/packages/frontend/src/constants.ts +++ b/packages/frontend/src/constants.ts @@ -1,5 +1,3 @@ -export const ORGANIZATION_ID = '2379cf1f-a232-4ad2-ae14-4d881131cc26'; - export const GIT_TEMPLATE_LINK = 'https://git.vdb.to/cerc-io/test-progressive-web-app'; diff --git a/packages/frontend/src/context/OctokitContext.tsx b/packages/frontend/src/context/OctokitContext.tsx index 21c88b2..0eed37c 100644 --- a/packages/frontend/src/context/OctokitContext.tsx +++ b/packages/frontend/src/context/OctokitContext.tsx @@ -15,16 +15,20 @@ const UNAUTHORIZED_ERROR_CODE = 401; interface ContextValue { octokit: Octokit | null; + isAuth: boolean; updateAuth: () => void; } const OctokitContext = createContext({ octokit: null, + isAuth: false, updateAuth: () => {}, }); export const OctokitProvider = ({ children }: { children: ReactNode }) => { const [authToken, setAuthToken] = useState(''); + const [isAuth, setIsAuth] = useState(false); + const client = useGQLClient(); const fetchUser = useCallback(async () => { @@ -41,9 +45,11 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => { const octokit = useMemo(() => { if (!authToken) { - return null; + setIsAuth(false); + return new Octokit(); } + setIsAuth(true); return new Octokit({ auth: authToken }); }, [authToken]); @@ -52,10 +58,6 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => { }, []); useEffect(() => { - if (!octokit) { - return; - } - // TODO: Handle React component error const interceptor = async (error: RequestError | Error) => { if ( @@ -78,14 +80,14 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => { }, [octokit, client]); return ( - + {children} ); }; export const useOctokit = () => { - const { octokit, updateAuth } = useContext(OctokitContext); + const { octokit, updateAuth, isAuth } = useContext(OctokitContext); - return { octokit, updateAuth }; + return { octokit, updateAuth, isAuth }; }; diff --git a/packages/frontend/src/pages/org-slug/projects/create/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/index.tsx index b80731d..8658d18 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/index.tsx @@ -7,9 +7,9 @@ import ConnectAccount from '../../../../components/projects/create/ConnectAccoun import { useOctokit } from '../../../../context/OctokitContext'; const NewProject = () => { - const { octokit, updateAuth } = useOctokit(); + const { octokit, updateAuth, isAuth } = useOctokit(); - return Boolean(octokit) ? ( + return isAuth ? ( <>
Start with template
diff --git a/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx b/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx index 59c5204..28e6f18 100644 --- a/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx +++ b/packages/frontend/src/pages/org-slug/projects/id/Overview.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Domain, DomainStatus } from 'gql-client'; -import { useOutletContext } from 'react-router-dom'; +import { useNavigate, useOutletContext } from 'react-router-dom'; +import { RequestError } from 'octokit'; import { Typography, Button, Chip, Avatar } from '@material-tailwind/react'; @@ -14,6 +15,7 @@ const COMMITS_PER_PAGE = 4; const OverviewTabPanel = () => { const { octokit } = useOctokit(); + const navigate = useNavigate(); const [activities, setActivities] = useState([]); const [liveDomain, setLiveDomain] = useState(); @@ -29,47 +31,55 @@ const OverviewTabPanel = () => { // TODO: Save repo commits in DB and avoid using GitHub API in frontend // TODO: Figure out fetching latest commits for all branches const fetchRepoActivity = async () => { - const [owner, repo] = project.repository.split('/'); + try { + const [owner, repo] = project.repository.split('/'); - if (!repo) { - // Do not fetch branches if repo not available - return; - } + if (!repo) { + // Do not fetch branches if repo not available + return; + } - // Get all branches in project repo - const result = await octokit.rest.repos.listBranches({ - owner, - repo, - }); - - // Get first 4 commits from repo branches - const commitsByBranchPromises = result.data.map(async (branch) => { - const result = await octokit.rest.repos.listCommits({ + // Get all branches in project repo + const result = await octokit.rest.repos.listBranches({ owner, repo, - sha: branch.commit.sha, - per_page: COMMITS_PER_PAGE, }); - return result.data.map((data) => ({ - ...data, - branch, - })); - }); + // Get first 4 commits from repo branches + const commitsByBranchPromises = result.data.map(async (branch) => { + const result = await octokit.rest.repos.listCommits({ + owner, + repo, + sha: branch.commit.sha, + per_page: COMMITS_PER_PAGE, + }); - const commitsByBranch = await Promise.all(commitsByBranchPromises); - const commitsWithBranch = commitsByBranch.flat(); + return result.data.map((data) => ({ + ...data, + branch, + })); + }); - // Order commits by date and set latest 4 commits in activity section - const orderedCommits = commitsWithBranch - .sort( - (a, b) => - new Date(b.commit.author!.date!).getTime() - - new Date(a.commit.author!.date!).getTime(), - ) - .slice(0, COMMITS_PER_PAGE); + const commitsByBranch = await Promise.all(commitsByBranchPromises); + const commitsWithBranch = commitsByBranch.flat(); - setActivities(orderedCommits); + // Order commits by date and set latest 4 commits in activity section + const orderedCommits = commitsWithBranch + .sort( + (a, b) => + new Date(b.commit.author!.date!).getTime() - + new Date(a.commit.author!.date!).getTime(), + ) + .slice(0, COMMITS_PER_PAGE); + + setActivities(orderedCommits); + } catch (err) { + if (!(err instanceof RequestError)) { + throw err; + } + + console.log(err.message); + } }; fetchRepoActivity(); @@ -127,6 +137,9 @@ const OverviewTabPanel = () => { className="normal-case rounded-full" color="blue" size="sm" + onClick={() => { + navigate('settings/domains'); + }} > Setup diff --git a/packages/gql-client/src/client.ts b/packages/gql-client/src/client.ts index 77c70eb..ef20528 100644 --- a/packages/gql-client/src/client.ts +++ b/packages/gql-client/src/client.ts @@ -206,28 +206,28 @@ export class GQLClient { return result.data; } - async updateProject (projectId: string, projectDetails: types.UpdateProjectInput): Promise { - const { data } = await this.client.mutate({ + async updateProject (projectId: string, data: types.UpdateProjectInput): Promise { + const result = await this.client.mutate({ mutation: mutations.updateProjectMutation, variables: { projectId, - projectDetails + data } }); - return data; + return result.data; } - async updateDomain (domainId: string, domainDetails: types.UpdateDomainInput): Promise { - const { data } = await this.client.mutate({ + async updateDomain (domainId: string, data: types.UpdateDomainInput): Promise { + const result = await this.client.mutate({ mutation: mutations.updateDomainMutation, variables: { domainId, - domainDetails + data } }); - return data; + return result.data; } async redeployToProd (deploymentId: string): Promise { @@ -275,16 +275,16 @@ export class GQLClient { return data; } - async addDomain (projectId: string, domainDetails: types.AddDomainInput): Promise { - const { data } = await this.client.mutate({ + async addDomain (projectId: string, data: types.AddDomainInput): Promise { + const result = await this.client.mutate({ mutation: mutations.addDomain, variables: { projectId, - domainDetails + data } }); - return data; + return result.data; } async getDomains (projectId: string, filter?: types.FilterDomainInput): Promise { diff --git a/packages/gql-client/src/mutations.ts b/packages/gql-client/src/mutations.ts index 14a6bb1..fd7a9c2 100644 --- a/packages/gql-client/src/mutations.ts +++ b/packages/gql-client/src/mutations.ts @@ -50,13 +50,13 @@ mutation ($organizationSlug: String!, $data: AddProjectInput) { }`; export const updateProjectMutation = gql` -mutation ($projectId: String!, $projectDetails: UpdateProjectInput) { - updateProject(projectId: $projectId, projectDetails: $projectDetails) +mutation ($projectId: String!, $data: UpdateProjectInput) { + updateProject(projectId: $projectId, data: $data) }`; export const updateDomainMutation = gql` -mutation ($domainId: String!, $domainDetails: UpdateDomainInput!) { - updateDomain(domainId: $domainId, domainDetails: $domainDetails) +mutation ($domainId: String!, $data: UpdateDomainInput!) { + updateDomain(domainId: $domainId, data: $data) }`; export const redeployToProd = gql` @@ -83,8 +83,8 @@ mutation ($projectId: String! ,$deploymentId: String!) { `; export const addDomain = gql` -mutation ($projectId: String!, $domainDetails: AddDomainInput!) { - addDomain(projectId: $projectId, domainDetails: $domainDetails) +mutation ($projectId: String!, $data: AddDomainInput!) { + addDomain(projectId: $projectId, data: $data) } `;