Use branches from GitHub API in edit domain dialog (#74)

* Use branches from GitHub API

* Disable git branch input if repo not found

* Disable git branch if branches is empty

* Use async select for accounts dropdown

* Log actual HTTP error in console

---------

Co-authored-by: neeraj <neeraj.rtly@gmail.com>
This commit is contained in:
Nabarun Gogoi 2024-02-19 16:55:07 +05:30 committed by GitHub
parent ce55fe62d8
commit c3d1b4f3eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 50 additions and 33 deletions

View File

@ -3,11 +3,12 @@ import { Octokit } from 'octokit';
import assert from 'assert'; import assert from 'assert';
import { useDebounce } from 'usehooks-ts'; import { useDebounce } from 'usehooks-ts';
import { Button, Typography, Option, Select } from '@material-tailwind/react'; import { Button, Typography, Option } from '@material-tailwind/react';
import SearchBar from '../../SearchBar'; import SearchBar from '../../SearchBar';
import ProjectRepoCard from './ProjectRepoCard'; import ProjectRepoCard from './ProjectRepoCard';
import { GitOrgDetails, GitRepositoryDetails } from '../../../types'; import { GitOrgDetails, GitRepositoryDetails } from '../../../types';
import AsyncSelect from '../../shared/AsyncSelect';
const DEFAULT_SEARCHED_REPO = ''; const DEFAULT_SEARCHED_REPO = '';
const REPOS_PER_PAGE = 5; const REPOS_PER_PAGE = 5;
@ -109,8 +110,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
<div className="p-4"> <div className="p-4">
<div className="flex gap-2 mb-2"> <div className="flex gap-2 mb-2">
<div className="basis-1/3"> <div className="basis-1/3">
{/* TODO: Fix selection of Git user at start */} <AsyncSelect
<Select
value={selectedAccount} value={selectedAccount}
onChange={(value) => setSelectedAccount(value!)} onChange={(value) => setSelectedAccount(value!)}
> >
@ -119,7 +119,7 @@ const RepositoryList = ({ octokit }: RepositoryListProps) => {
^ {account.login} ^ {account.login}
</Option> </Option>
))} ))}
</Select> </AsyncSelect>
</div> </div>
<div className="basis-2/3"> <div className="basis-2/3">
<SearchBar <SearchBar

View File

@ -12,7 +12,6 @@ import {
Card, Card,
} from '@material-tailwind/react'; } from '@material-tailwind/react';
import { RepositoryDetails } from '../../../../types';
import ConfirmDialog from '../../../shared/ConfirmDialog'; import ConfirmDialog from '../../../shared/ConfirmDialog';
import EditDomainDialog from './EditDomainDialog'; import EditDomainDialog from './EditDomainDialog';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../context/GQLClientContext';
@ -27,7 +26,7 @@ enum RefreshStatus {
interface DomainCardProps { interface DomainCardProps {
domains: Domain[]; domains: Domain[];
domain: Domain; domain: Domain;
repo: RepositoryDetails; branches: string[];
project: Project; project: Project;
onUpdate: () => Promise<void>; onUpdate: () => Promise<void>;
} }
@ -44,7 +43,7 @@ const DOMAIN_RECORD = {
const DomainCard = ({ const DomainCard = ({
domains, domains,
domain, domain,
repo, branches,
project, project,
onUpdate, onUpdate,
}: DomainCardProps) => { }: DomainCardProps) => {
@ -188,7 +187,7 @@ const DomainCard = ({
domains={domains} domains={domains}
open={editDialogOpen} open={editDialogOpen}
domain={domain} domain={domain}
repo={repo} branches={branches}
onUpdate={onUpdate} onUpdate={onUpdate}
/> />
</> </>

View File

@ -15,7 +15,6 @@ import {
Option, Option,
} from '@material-tailwind/react'; } from '@material-tailwind/react';
import { RepositoryDetails } from '../../../../types';
import { useGQLClient } from '../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../context/GQLClientContext';
const DEFAULT_REDIRECT_OPTIONS = ['none']; const DEFAULT_REDIRECT_OPTIONS = ['none'];
@ -25,7 +24,7 @@ interface EditDomainDialogProp {
open: boolean; open: boolean;
handleOpen: () => void; handleOpen: () => void;
domain: Domain; domain: Domain;
repo: RepositoryDetails; branches: string[];
onUpdate: () => Promise<void>; onUpdate: () => Promise<void>;
} }
@ -40,7 +39,7 @@ const EditDomainDialog = ({
open, open,
handleOpen, handleOpen,
domain, domain,
repo, branches,
onUpdate, onUpdate,
}: EditDomainDialogProp) => { }: EditDomainDialogProp) => {
const client = useGQLClient(); const client = useGQLClient();
@ -120,7 +119,7 @@ const EditDomainDialog = ({
branch: domain.branch, branch: domain.branch,
redirectedTo: getRedirectUrl(domain), redirectedTo: getRedirectUrl(domain),
}); });
}, [domain, repo]); }, [domain]);
return ( return (
<Dialog open={open} handler={handleOpen}> <Dialog open={open} handler={handleOpen}>
@ -166,9 +165,13 @@ const EditDomainDialog = ({
<Input <Input
crossOrigin={undefined} crossOrigin={undefined}
{...register('branch', { {...register('branch', {
validate: (value) => repo.branch.includes(value), validate: (value) =>
Boolean(branches.length) ? branches.includes(value) : true,
})} })}
disabled={watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]} disabled={
!Boolean(branches.length) ||
watch('redirectedTo') !== DEFAULT_REDIRECT_OPTIONS[0]
}
/> />
{!isValid && ( {!isValid && (
<Typography variant="small" className="text-red-500"> <Typography variant="small" className="text-red-500">

View File

@ -14,13 +14,13 @@ import { useGQLClient } from './GQLClientContext';
const UNAUTHORIZED_ERROR_CODE = 401; const UNAUTHORIZED_ERROR_CODE = 401;
interface ContextValue { interface ContextValue {
octokit: Octokit | null; octokit: Octokit;
isAuth: boolean; isAuth: boolean;
updateAuth: () => void; updateAuth: () => void;
} }
const OctokitContext = createContext<ContextValue>({ const OctokitContext = createContext<ContextValue>({
octokit: null, octokit: new Octokit(),
isAuth: false, isAuth: false,
updateAuth: () => {}, updateAuth: () => {},
}); });

View File

@ -24,7 +24,7 @@ const NewProject = () => {
})} })}
</div> </div>
<h5 className="mt-4 ml-4">Import a repository</h5> <h5 className="mt-4 ml-4">Import a repository</h5>
<RepositoryList octokit={octokit!} /> <RepositoryList octokit={octokit} />
</> </>
) : ( ) : (
<ConnectAccount onAuth={updateAuth} /> <ConnectAccount onAuth={updateAuth} />

View File

@ -24,10 +24,6 @@ const OverviewTabPanel = () => {
const { project } = useOutletContext<OutletContextType>(); const { project } = useOutletContext<OutletContextType>();
useEffect(() => { useEffect(() => {
if (!octokit) {
return;
}
// TODO: Save repo commits in DB and avoid using GitHub API in frontend // TODO: Save repo commits in DB and avoid using GitHub API in frontend
// TODO: Figure out fetching latest commits for all branches // TODO: Figure out fetching latest commits for all branches
const fetchRepoActivity = async () => { const fetchRepoActivity = async () => {

View File

@ -1,4 +1,5 @@
import React, { useEffect, useState } from 'react'; import { RequestError } from 'octokit';
import React, { useCallback, useEffect, useState } from 'react';
import { Link, useOutletContext } from 'react-router-dom'; import { Link, useOutletContext } from 'react-router-dom';
import { Domain } from 'gql-client'; import { Domain } from 'gql-client';
@ -6,14 +7,41 @@ import { Button, Typography } from '@material-tailwind/react';
import DomainCard from '../../../../../components/projects/project/settings/DomainCard'; import DomainCard from '../../../../../components/projects/project/settings/DomainCard';
import { useGQLClient } from '../../../../../context/GQLClientContext'; import { useGQLClient } from '../../../../../context/GQLClientContext';
import repositories from '../../../../../assets/repositories.json';
import { OutletContextType } from '../../../../../types'; import { OutletContextType } from '../../../../../types';
import { useOctokit } from '../../../../../context/OctokitContext';
const Domains = () => { const Domains = () => {
const client = useGQLClient(); const client = useGQLClient();
const { octokit } = useOctokit();
const { project } = useOutletContext<OutletContextType>(); const { project } = useOutletContext<OutletContextType>();
const [domains, setDomains] = useState<Domain[]>([]); const [domains, setDomains] = useState<Domain[]>([]);
const [branches, setBranches] = useState<string[]>([]);
const fetchBranches = useCallback(async () => {
const [owner, repo] = project.repository.split('/');
try {
const result = await octokit.rest.repos.listBranches({
owner,
repo,
});
const branches = result.data.map((repo) => repo.name);
setBranches(branches);
} catch (err) {
if (!(err instanceof RequestError && err.status === 404)) {
throw err;
}
console.error(err);
}
}, []);
useEffect(() => {
fetchBranches();
}, []);
const fetchDomains = async () => { const fetchDomains = async () => {
if (project === undefined) { if (project === undefined) {
@ -46,7 +74,7 @@ const Domains = () => {
domain={domain} domain={domain}
key={domain.id} key={domain.id}
// TODO: Use github API for getting linked repository // TODO: Use github API for getting linked repository
repo={repositories[0]!} branches={branches}
project={project} project={project}
onUpdate={fetchDomains} onUpdate={fetchDomains}
/> />

View File

@ -6,15 +6,6 @@ export interface GitOrgDetails {
avatar_url: string; avatar_url: string;
} }
// TODO: Use GitRepositoryDetails
export interface RepositoryDetails {
title: string;
updatedAt: string;
user: string;
private: boolean;
branch: string[];
}
export interface GitRepositoryDetails { export interface GitRepositoryDetails {
id: number; id: number;
name: string; name: string;