From cc8f9527da6c0737a3734fd247d51698ea54d6fd Mon Sep 17 00:00:00 2001 From: Nabarun Gogoi Date: Thu, 11 Apr 2024 17:19:15 +0530 Subject: [PATCH] Check for GitHub authentication in template create page and handle GitHub unauthorized errors (#173) * Fix GitHub auth check on reloading template create page * Fix error handling for unauthenticated GitHub token --- .../projects/create/MockConnectGitCard.tsx | 6 ++-- .../create/RepositoryList/RepositoryList.tsx | 31 ++++++++++------- .../frontend/src/context/OctokitContext.tsx | 34 +++++++++++++++---- .../pages/org-slug/projects/create/index.tsx | 2 +- .../projects/create/template/index.tsx | 24 ++++++++----- 5 files changed, 66 insertions(+), 31 deletions(-) diff --git a/packages/frontend/src/components/projects/create/MockConnectGitCard.tsx b/packages/frontend/src/components/projects/create/MockConnectGitCard.tsx index 5d214c94..abc49ab6 100644 --- a/packages/frontend/src/components/projects/create/MockConnectGitCard.tsx +++ b/packages/frontend/src/components/projects/create/MockConnectGitCard.tsx @@ -74,12 +74,12 @@ export const MockConnectGitCard = () => { return (
{IMPORT_CONTENT.map((repo, index) => ( - <> - + + {index !== IMPORT_CONTENT.length - 1 && (
)} - + ))}
); diff --git a/packages/frontend/src/components/projects/create/RepositoryList/RepositoryList.tsx b/packages/frontend/src/components/projects/create/RepositoryList/RepositoryList.tsx index 5b96ce37..dad6a9f7 100644 --- a/packages/frontend/src/components/projects/create/RepositoryList/RepositoryList.tsx +++ b/packages/frontend/src/components/projects/create/RepositoryList/RepositoryList.tsx @@ -1,5 +1,4 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Octokit } from 'octokit'; import assert from 'assert'; import { useDebounce } from 'usehooks-ts'; @@ -14,20 +13,18 @@ import { import { Select, SelectOption } from 'components/shared/Select'; import { Input } from 'components/shared/Input'; import { Button } from 'components/shared/Button'; +import { useOctokit } from 'context/OctokitContext'; const DEFAULT_SEARCHED_REPO = ''; const REPOS_PER_PAGE = 5; -interface RepositoryListProps { - octokit: Octokit; -} - -export const RepositoryList = ({ octokit }: RepositoryListProps) => { +export const RepositoryList = () => { const [searchedRepo, setSearchedRepo] = useState(DEFAULT_SEARCHED_REPO); const [selectedAccount, setSelectedAccount] = useState(); const [orgs, setOrgs] = useState([]); // TODO: Add new type for Git user when required const [gitUser, setGitUser] = useState(); + const { octokit, isAuth } = useOctokit(); const [repositoryDetails, setRepositoryDetails] = useState< GitRepositoryDetails[] @@ -35,15 +32,23 @@ export const RepositoryList = ({ octokit }: RepositoryListProps) => { useEffect(() => { const fetchUserAndOrgs = async () => { - const user = await octokit.rest.users.getAuthenticated(); - const orgs = await octokit.rest.orgs.listForAuthenticatedUser(); - setOrgs(orgs.data); - setGitUser(user.data); - setSelectedAccount({ label: user.data.login, value: user.data.login }); + try { + const user = await octokit.rest.users.getAuthenticated(); + const orgs = await octokit.rest.orgs.listForAuthenticatedUser(); + setOrgs(orgs.data); + setGitUser(user.data); + setSelectedAccount({ label: user.data.login, value: user.data.login }); + } catch (error) { + // Error handled by octokit error hook interceptor in Octokit context + console.error(error); + return; + } }; - fetchUserAndOrgs(); - }, [octokit]); + if (isAuth) { + fetchUserAndOrgs(); + } + }, [octokit, isAuth]); const debouncedSearchedRepo = useDebounce(searchedRepo, 500); diff --git a/packages/frontend/src/context/OctokitContext.tsx b/packages/frontend/src/context/OctokitContext.tsx index 21ffe246..4fd8f6c4 100644 --- a/packages/frontend/src/context/OctokitContext.tsx +++ b/packages/frontend/src/context/OctokitContext.tsx @@ -8,8 +8,11 @@ import React, { useEffect, } from 'react'; import { Octokit, RequestError } from 'octokit'; +import { useNavigate, useParams } from 'react-router-dom'; +import { useDebounceCallback } from 'usehooks-ts'; import { useGQLClient } from './GQLClientContext'; +import { useToast } from 'components/shared/Toast'; const UNAUTHORIZED_ERROR_CODE = 401; @@ -26,17 +29,17 @@ const OctokitContext = createContext({ }); export const OctokitProvider = ({ children }: { children: ReactNode }) => { - const [authToken, setAuthToken] = useState(''); + const [authToken, setAuthToken] = useState(null); const [isAuth, setIsAuth] = useState(false); - + const navigate = useNavigate(); + const { orgSlug } = useParams(); + const { toast, dismiss } = useToast(); const client = useGQLClient(); const fetchUser = useCallback(async () => { const { user } = await client.getUser(); - if (user.gitHubToken) { - setAuthToken(user.gitHubToken); - } + setAuthToken(user.gitHubToken); }, []); const updateAuth = useCallback(() => { @@ -57,6 +60,23 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => { fetchUser(); }, []); + const debouncedUnauthorizedGithubHandler = useDebounceCallback( + useCallback( + (error: RequestError) => { + toast({ + id: 'unauthorized-github-token', + title: `GitHub authentication error: ${error.message}`, + variant: 'error', + onDismiss: dismiss, + }); + + navigate(`/${orgSlug}/projects/create`); + }, + [toast, navigate, orgSlug], + ), + 500, + ); + useEffect(() => { // TODO: Handle React component error const interceptor = async (error: RequestError | Error) => { @@ -66,6 +86,8 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => { ) { await client.unauthenticateGithub(); await fetchUser(); + + debouncedUnauthorizedGithubHandler(error); } throw error; @@ -77,7 +99,7 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => { // Remove the interceptor when the component unmounts octokit.hook.remove('request', interceptor); }; - }, [octokit, client]); + }, [octokit, client, debouncedUnauthorizedGithubHandler]); return ( 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 49eeed96..a01de914 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/index.tsx @@ -31,7 +31,7 @@ const NewProject = () => { Import a repository - + ) : ( diff --git a/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx index 305f2fa1..47bd0eb9 100644 --- a/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx +++ b/packages/frontend/src/pages/org-slug/projects/create/template/index.tsx @@ -26,7 +26,7 @@ type SubmitRepoValues = { }; const CreateRepo = () => { - const { octokit } = useOctokit(); + const { octokit, isAuth } = useOctokit(); const { template } = useOutletContext<{ template: Template }>(); const client = useGQLClient(); @@ -104,18 +104,26 @@ const CreateRepo = () => { useEffect(() => { const fetchUserAndOrgs = async () => { - const user = await octokit?.rest.users.getAuthenticated(); - const orgs = await octokit?.rest.orgs.listForAuthenticatedUser(); + try { + const user = await octokit?.rest.users.getAuthenticated(); + const orgs = await octokit?.rest.orgs.listForAuthenticatedUser(); - if (user && orgs) { - const orgsLoginArr = orgs.data.map((org) => org.login); + if (user && orgs) { + const orgsLoginArr = orgs.data.map((org) => org.login); - setGitAccounts([user.data.login, ...orgsLoginArr]); + setGitAccounts([user.data.login, ...orgsLoginArr]); + } + } catch (error) { + // Error handled by octokit error hook interceptor in Octokit context + console.error(error); + return; } }; - fetchUserAndOrgs(); - }, [octokit]); + if (isAuth) { + fetchUserAndOrgs(); + } + }, [octokit, isAuth]); const { handleSubmit, control, reset } = useForm({ defaultValues: {