Implement creating project by importing repository (#49)

* Implement create project with import repository

* Add button for creating project in deploy step
This commit is contained in:
Nabarun Gogoi 2024-02-01 18:10:15 +05:30 committed by GitHub
parent 0aa35d05f4
commit ef89d69577
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 404 additions and 222 deletions

View File

@ -326,6 +326,27 @@ export class Database {
} }
} }
async addProject (userId: string, projectDetails: DeepPartial<Project>): Promise<Project> {
const projectRepository = this.dataSource.getRepository(Project);
// TODO: Check if organization exists
const newProject = projectRepository.create(projectDetails);
// TODO: Set default empty array for webhooks in TypeORM
newProject.webhooks = [];
// TODO: Set icon according to framework
newProject.icon = '';
newProject.owner = Object.assign(new User(), {
id: Number(userId)
});
newProject.organization = Object.assign(new Organization(), {
id: Number(projectDetails.organizationId)
});
return projectRepository.save(newProject);
}
async updateProjectById (projectId: string, updates: DeepPartial<Project>): Promise<boolean> { async updateProjectById (projectId: string, updates: DeepPartial<Project>): Promise<boolean> {
const projectRepository = this.dataSource.getRepository(Project); const projectRepository = this.dataSource.getRepository(Project);
const updateResult = await projectRepository.update({ id: projectId }, updates); const updateResult = await projectRepository.update({ id: projectId }, updates);

View File

@ -28,6 +28,9 @@ export class Project {
@JoinColumn({ name: 'organizationId' }) @JoinColumn({ name: 'organizationId' })
organization!: Organization | null; organization!: Organization | null;
@Column('integer')
organizationId!: number;
@Column('varchar') @Column('varchar')
name!: string; name!: string;
@ -37,14 +40,14 @@ export class Project {
@Column('varchar', { length: 255, default: 'main' }) @Column('varchar', { length: 255, default: 'main' })
prodBranch!: string; prodBranch!: string;
@Column('text') @Column('text', { default: '' })
description!: string; description!: string;
@Column('varchar') @Column('varchar', { nullable: true })
template!: string; template!: string | null;
@Column('varchar') @Column('varchar', { nullable: true })
framework!: string; framework!: string | null;
@Column({ @Column({
type: 'simple-array' type: 'simple-array'

View File

@ -206,6 +206,16 @@ export const createResolvers = async (db: Database, app: OAuthApp): Promise<any>
} }
}, },
addProject: async (_: any, { projectDetails }: { projectDetails: DeepPartial<Project> }, context: any) => {
try {
await db.addProject(context.userId, projectDetails);
return true;
} catch (err) {
log(err);
return false;
}
},
updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: DeepPartial<Project> }) => { updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: DeepPartial<Project> }) => {
try { try {
return await db.updateProjectById(projectId, projectDetails); return await db.updateProjectById(projectId, projectDetails);

View File

@ -64,7 +64,7 @@ type Project {
prodBranch: String! prodBranch: String!
description: String description: String
template: String template: String
framework: String! framework: String
webhooks: [String!] webhooks: [String!]
members: [ProjectMember!] members: [ProjectMember!]
environmentVariables: [EnvironmentVariable!] environmentVariables: [EnvironmentVariable!]
@ -116,48 +116,23 @@ type EnvironmentVariable {
updatedAt: String! updatedAt: String!
} }
type Query {
user: User!
organizations: [Organization!]
projects: [Project!]
projectsInOrganization(organizationId: String!): [Project!]
project(projectId: String!): Project
deployments(projectId: String!): [Deployment!]
environmentVariables(projectId: String!): [EnvironmentVariable!]
projectMembers(projectId: String!): [ProjectMember!]
searchProjects(searchText: String!): [Project!]
domains(projectId: String!): [Domain!]
}
type AuthResult { type AuthResult {
token: String! token: String!
} }
type Mutation {
removeProjectMember(projectMemberId: String!): Boolean!
updateProjectMember(projectMemberId: String!, data: UpdateProjectMemberInput): Boolean!
addProjectMember(projectId: String!, data: AddProjectMemberInput): Boolean!
addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean!
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
updateEnvironmentVariable(environmentVariableId: String!, environmentVariable: UpdateEnvironmentVariableInput!): Boolean!
updateDeploymentToProd(deploymentId: String!): Boolean!
updateProject(projectId: String!, projectDetails: 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!
authenticateGitHub(code: String!): AuthResult!
unauthenticateGitHub: Boolean!
}
input AddEnvironmentVariableInput { input AddEnvironmentVariableInput {
environments: [Environment!]! environments: [Environment!]!
key: String! key: String!
value: String! value: String!
} }
input AddProjectInput {
organizationId: String!
name: String!
repository: String!
prodBranch: String!
}
input UpdateProjectInput { input UpdateProjectInput {
name: String name: String
description: String description: String
@ -188,3 +163,36 @@ input AddProjectMemberInput {
input UpdateProjectMemberInput { input UpdateProjectMemberInput {
permissions: [Permission] permissions: [Permission]
} }
type Query {
user: User!
organizations: [Organization!]
projects: [Project!]
projectsInOrganization(organizationId: String!): [Project!]
project(projectId: String!): Project
deployments(projectId: String!): [Deployment!]
environmentVariables(projectId: String!): [EnvironmentVariable!]
projectMembers(projectId: String!): [ProjectMember!]
searchProjects(searchText: String!): [Project!]
domains(projectId: String!): [Domain!]
}
type Mutation {
addProjectMember(projectId: String!, data: AddProjectMemberInput): Boolean!
updateProjectMember(projectMemberId: String!, data: UpdateProjectMemberInput): Boolean!
removeProjectMember(projectMemberId: String!): Boolean!
addEnvironmentVariables(projectId: String!, environmentVariables: [AddEnvironmentVariableInput!]): Boolean!
updateEnvironmentVariable(environmentVariableId: String!, environmentVariable: UpdateEnvironmentVariableInput!): Boolean!
removeEnvironmentVariable(environmentVariableId: String!): Boolean!
updateDeploymentToProd(deploymentId: String!): Boolean!
addProject(projectDetails: AddProjectInput): Boolean!
updateProject(projectId: String!, projectDetails: 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!
authenticateGitHub(code: String!): AuthResult!
unauthenticateGitHub: Boolean!
}

View File

@ -9,6 +9,7 @@ import {
projectsRoutesWithoutSearch, projectsRoutesWithoutSearch,
} from './pages/projects/routes'; } from './pages/projects/routes';
import ProjectSearchLayout from './layouts/ProjectSearch'; import ProjectSearchLayout from './layouts/ProjectSearch';
import { OctokitProvider } from './context/OctokitContext';
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
@ -40,7 +41,11 @@ const router = createBrowserRouter([
]); ]);
function App() { function App() {
return <RouterProvider router={router} />; return (
<OctokitProvider>
<RouterProvider router={router} />
</OctokitProvider>
);
} }
export default App; export default App;

View File

@ -10,10 +10,10 @@ const GITHUB_OAUTH_URL = `https://github.com/login/oauth/authorize?client_id=${
}&scope=${encodeURIComponent(SCOPES)}`; }&scope=${encodeURIComponent(SCOPES)}`;
interface ConnectAccountInterface { interface ConnectAccountInterface {
onToken: (token: string) => void; onAuth: (token: string) => void;
} }
const ConnectAccount = ({ onToken }: ConnectAccountInterface) => { const ConnectAccount = ({ onAuth: onToken }: ConnectAccountInterface) => {
const client = useGQLClient(); const client = useGQLClient();
const handleCode = async (code: string) => { const handleCode = async (code: string) => {

View File

@ -0,0 +1,76 @@
import React, { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Typography } from '@material-tailwind/react';
import { DeployStep, DeployStatus } from './DeployStep';
import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
import ConfirmDialog from '../../shared/ConfirmDialog';
const Deploy = () => {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(!open);
const navigate = useNavigate();
const handleCancel = useCallback(() => {
navigate('/projects/create');
}, []);
return (
<div>
<div className="flex justify-between mb-6">
<div>
<h4>Deployment started ...</h4>
<div className="flex">
^&nbsp;
<Stopwatch
offsetTimestamp={setStopWatchOffset(Date.now().toString())}
/>
</div>
</div>
<div>
<Button onClick={handleOpen} variant="outlined" size="sm">
^ Cancel
</Button>
</div>
<ConfirmDialog
dialogTitle="Cancel deployment?"
handleOpen={handleOpen}
open={open}
confirmButtonTitle="Yes, Cancel deployment"
handleConfirm={handleCancel}
color="red"
>
<Typography variant="small">
This will halt the deployment and you will have to start the process
from scratch.
</Typography>
</ConfirmDialog>
</div>
<DeployStep
title="Building"
status={DeployStatus.COMPLETE}
step="1"
processTime="72000"
/>
<DeployStep
title="Deployment summary"
status={DeployStatus.PROCESSING}
step="2"
startTime={Date.now().toString()}
/>
<DeployStep
title="Running checks"
status={DeployStatus.NOT_STARTED}
step="3"
/>
<DeployStep
title="Assigning domains"
status={DeployStatus.NOT_STARTED}
step="4"
/>
</div>
);
};
export default Deploy;

View File

@ -3,9 +3,9 @@ import toast from 'react-hot-toast';
import { Collapse, Button, Typography } from '@material-tailwind/react'; import { Collapse, Button, Typography } from '@material-tailwind/react';
import { Stopwatch, setStopWatchOffset } from '../../../../StopWatch'; import { Stopwatch, setStopWatchOffset } from '../../StopWatch';
import FormatMillisecond from '../../../../FormatMilliSecond'; import FormatMillisecond from '../../FormatMilliSecond';
import processLogs from '../../../../../assets/process-logs.json'; import processLogs from '../../../assets/process-logs.json';
enum DeployStatus { enum DeployStatus {
PROCESSING = 'progress', PROCESSING = 'progress',

View File

@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { Link } from 'react-router-dom';
import { Chip, IconButton } from '@material-tailwind/react'; import { Chip, IconButton } from '@material-tailwind/react';
@ -7,39 +8,36 @@ import { GitRepositoryDetails } from '../../../types/project';
interface ProjectRepoCardProps { interface ProjectRepoCardProps {
repository: GitRepositoryDetails; repository: GitRepositoryDetails;
onClick: () => void;
} }
const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ const ProjectRepoCard: React.FC<ProjectRepoCardProps> = ({ repository }) => {
repository,
onClick,
}) => {
return ( return (
<div <Link
className="group flex items-center gap-4 text-gray-500 text-xs hover:bg-gray-100 p-2 cursor-pointer" to={`import?owner=${repository.owner?.login}&repo=${repository.name}`}
onClick={onClick}
> >
<div>^</div> <div className="group flex items-center gap-4 text-gray-500 text-xs hover:bg-gray-100 p-2 cursor-pointer">
<div className="grow"> <div>^</div>
<div> <div className="grow">
<span className="text-black">{repository.full_name}</span> <div>
{repository.visibility === 'private' ? ( <span className="text-black">{repository.full_name}</span>
<Chip {repository.visibility === 'private' ? (
className="normal-case inline ml-6 bg-[#FED7AA] text-[#EA580C] font-normal" <Chip
size="sm" className="normal-case inline ml-6 bg-[#FED7AA] text-[#EA580C] font-normal"
value="Private" size="sm"
icon={'^'} value="Private"
/> icon={'^'}
) : ( />
'' ) : (
)} ''
)}
</div>
<p>{repository.updated_at && relativeTime(repository.updated_at)}</p>
</div>
<div className="hidden group-hover:block">
<IconButton size="sm">{'>'}</IconButton>
</div> </div>
<p>{repository.updated_at && relativeTime(repository.updated_at)}</p>
</div> </div>
<div className="hidden group-hover:block"> </Link>
<IconButton size="sm">{'>'}</IconButton>
</div>
</div>
); );
}; };

View File

@ -13,14 +13,10 @@ const DEFAULT_SEARCHED_REPO = '';
const REPOS_PER_PAGE = 5; const REPOS_PER_PAGE = 5;
interface RepositoryListProps { interface RepositoryListProps {
repoSelectionHandler: (repo: GitRepositoryDetails) => void;
octokit: Octokit; octokit: Octokit;
} }
const RepositoryList = ({ const RepositoryList = ({ octokit }: RepositoryListProps) => {
repoSelectionHandler,
octokit,
}: RepositoryListProps) => {
const [searchedRepo, setSearchedRepo] = useState(DEFAULT_SEARCHED_REPO); const [searchedRepo, setSearchedRepo] = useState(DEFAULT_SEARCHED_REPO);
const [selectedAccount, setSelectedAccount] = useState(''); const [selectedAccount, setSelectedAccount] = useState('');
const [orgs, setOrgs] = useState<GitOrgDetails[]>([]); const [orgs, setOrgs] = useState<GitOrgDetails[]>([]);
@ -135,15 +131,7 @@ const RepositoryList = ({
</div> </div>
{Boolean(repositoryDetails.length) ? ( {Boolean(repositoryDetails.length) ? (
repositoryDetails.map((repo, key) => { repositoryDetails.map((repo, key) => {
return ( return <ProjectRepoCard repository={repo} key={key} />;
<ProjectRepoCard
repository={repo}
key={key}
onClick={() => {
repoSelectionHandler(repo);
}}
/>
);
}) })
) : ( ) : (
<div className="mt-4 p-6 flex items-center justify-center"> <div className="mt-4 p-6 flex items-center justify-center">

View File

@ -0,0 +1,91 @@
import React, {
createContext,
useContext,
ReactNode,
useState,
useMemo,
useCallback,
useEffect,
} from 'react';
import { Octokit, RequestError } from 'octokit';
import { useGQLClient } from './GQLClientContext';
const UNAUTHORIZED_ERROR_CODE = 401;
interface ContextValue {
octokit: Octokit | null;
updateAuth: () => void;
}
const OctokitContext = createContext<ContextValue>({
octokit: null,
updateAuth: () => {},
});
export const OctokitProvider = ({ children }: { children: ReactNode }) => {
const [authToken, setAuthToken] = useState<string>('');
const client = useGQLClient();
const fetchUser = useCallback(async () => {
const { user } = await client.getUser();
if (user.gitHubToken) {
setAuthToken(user.gitHubToken);
}
}, []);
const updateAuth = useCallback(() => {
fetchUser();
}, []);
const octokit = useMemo(() => {
if (!authToken) {
return null;
}
return new Octokit({ auth: authToken });
}, [authToken]);
useEffect(() => {
fetchUser();
}, []);
useEffect(() => {
if (!octokit) {
return;
}
// TODO: Handle React component error
const interceptor = async (error: RequestError | Error) => {
if (
error instanceof RequestError &&
error.status === UNAUTHORIZED_ERROR_CODE
) {
await client.unauthenticateGithub();
await fetchUser();
}
throw error;
};
octokit.hook.error('request', interceptor);
return () => {
// Remove the interceptor when the component unmounts
octokit.hook.remove('request', interceptor);
};
}, [octokit, client]);
return (
<OctokitContext.Provider value={{ octokit, updateAuth }}>
{children}
</OctokitContext.Provider>
);
};
export const useOctokit = () => {
const { octokit, updateAuth } = useContext(OctokitContext);
return { octokit, updateAuth };
};

View File

@ -0,0 +1,70 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { Button } from '@material-tailwind/react';
import { useOctokit } from '../../../context/OctokitContext';
import { GitRepositoryDetails } from '../../../types/project';
import Deploy from '../../../components/projects/create/Deploy';
import { useGQLClient } from '../../../context/GQLClientContext';
const Import = () => {
const [searchParams] = useSearchParams();
const navigate = useNavigate();
const { octokit } = useOctokit();
const client = useGQLClient();
const [gitRepo, setGitRepo] = useState<GitRepositoryDetails>();
useEffect(() => {
const fetchRepo = async () => {
if (!octokit) {
return;
}
const result = await octokit.rest.repos.get({
owner: searchParams.get('owner') ?? '',
repo: searchParams.get('repo') ?? '',
});
setGitRepo(result.data);
};
fetchRepo();
}, [searchParams, octokit]);
const createProjectAndCreate = useCallback(async () => {
if (!gitRepo) {
return;
}
const { addProject } = await client.addProject({
// TODO: Implement form for setting project name
name: gitRepo.name,
// TODO: Get organization id from context or URL
organizationId: String(1),
prodBranch: gitRepo.default_branch ?? 'main',
repository: gitRepo.full_name,
});
if (addProject) {
navigate('/projects/create/success');
}
}, [client, gitRepo]);
return (
<div className="flex flex-col items-center">
<div className="flex w-5/6 my-4 bg-gray-200 rounded-xl p-6">
<div>^</div>
<div className="grow">{gitRepo?.full_name}</div>
</div>
<Deploy />
<Button onClick={createProjectAndCreate}>
CREATE PROJECT (FOR DEMO)
</Button>
</div>
);
};
export default Import;

View File

@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
import { Button } from '@material-tailwind/react'; import { Button } from '@material-tailwind/react';
// TODO: Use dynamic route params for fetching project created details
const Success = () => { const Success = () => {
return ( return (
<div className="flex justify-center"> <div className="flex justify-center">

View File

@ -8,6 +8,7 @@ const STEPPER_VALUES = [
{ step: 2, route: '/projects/create/template/deploy', label: 'Deploy' }, { step: 2, route: '/projects/create/template/deploy', label: 'Deploy' },
]; ];
// TODO: Set dynamic route for template and load details from DB
const CreateWithTemplate = () => { const CreateWithTemplate = () => {
const location = useLocation(); const location = useLocation();
@ -23,6 +24,7 @@ const CreateWithTemplate = () => {
<div className="flex justify-between w-5/6 my-4 bg-gray-200 rounded-xl p-6"> <div className="flex justify-between w-5/6 my-4 bg-gray-200 rounded-xl p-6">
<div>^</div> <div>^</div>
<div className="grow">React native</div> <div className="grow">React native</div>
{/* TODO: Get template Git link from DB */}
<div>^snowball-tools/react-native-starter</div> <div>^snowball-tools/react-native-starter</div>
</div> </div>
<div className="grid grid-cols-3 w-5/6 p-6"> <div className="grid grid-cols-3 w-5/6 p-6">

View File

@ -1,66 +1,13 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React from 'react';
import { User } from 'gql-client';
import { Octokit, RequestError } from 'octokit';
import templateDetails from '../../../assets/templates.json'; import templateDetails from '../../../assets/templates.json';
import TemplateCard from '../../../components/projects/create/TemplateCard'; import TemplateCard from '../../../components/projects/create/TemplateCard';
import RepositoryList from '../../../components/projects/create/RepositoryList'; import RepositoryList from '../../../components/projects/create/RepositoryList';
import ConnectAccount from '../../../components/projects/create/ConnectAccount'; import ConnectAccount from '../../../components/projects/create/ConnectAccount';
import { useGQLClient } from '../../../context/GQLClientContext'; import { useOctokit } from '../../../context/OctokitContext';
const UNAUTHORIZED_ERROR_CODE = 401;
const NewProject = () => { const NewProject = () => {
const client = useGQLClient(); const { octokit, updateAuth } = useOctokit();
const [user, setUser] = useState<User>();
const octokit = useMemo(() => {
if (!user?.gitHubToken) {
return;
}
// TODO: Create github/octokit context
return new Octokit({ auth: user.gitHubToken });
}, [user]);
const fetchUser = useCallback(async () => {
const { user } = await client.getUser();
setUser(user);
}, []);
const handleToken = useCallback(() => {
fetchUser();
}, []);
useEffect(() => {
fetchUser();
}, []);
useEffect(() => {
if (!octokit) {
return;
}
// TODO: Handle React component error
const interceptor = async (error: RequestError | Error) => {
if (
error instanceof RequestError &&
error.status === UNAUTHORIZED_ERROR_CODE
) {
await client.unauthenticateGithub();
await fetchUser();
}
throw error;
};
octokit.hook.error('request', interceptor);
return () => {
// Remove the interceptor when the component unmounts
octokit.hook.remove('request', interceptor);
};
}, [octokit, client]);
return ( return (
<> <>
@ -69,7 +16,7 @@ const NewProject = () => {
{templateDetails.map((framework, key) => { {templateDetails.map((framework, key) => {
return ( return (
<TemplateCard <TemplateCard
isGitAuth={Boolean(user?.gitHubToken)} isGitAuth={Boolean(octokit)}
framework={framework} framework={framework}
key={key} key={key}
/> />
@ -78,9 +25,9 @@ const NewProject = () => {
</div> </div>
<h5 className="mt-4 ml-4">Import a repository</h5> <h5 className="mt-4 ml-4">Import a repository</h5>
{Boolean(octokit) ? ( {Boolean(octokit) ? (
<RepositoryList octokit={octokit!} repoSelectionHandler={() => {}} /> <RepositoryList octokit={octokit!} />
) : ( ) : (
<ConnectAccount onToken={handleToken} /> <ConnectAccount onAuth={updateAuth} />
)} )}
</> </>
); );

View File

@ -4,6 +4,7 @@ import NewProject from './index';
import CreateWithTemplate from './Template'; import CreateWithTemplate from './Template';
import { templateRoutes } from './template/routes'; import { templateRoutes } from './template/routes';
import Success from './Success'; import Success from './Success';
import Import from './Import';
export const createProjectRoutes = [ export const createProjectRoutes = [
{ {
@ -19,4 +20,8 @@ export const createProjectRoutes = [
path: 'success', path: 'success',
element: <Success />, element: <Success />,
}, },
{
path: 'import',
element: <Import />,
},
]; ];

View File

@ -1,82 +1,9 @@
import React, { useCallback } from 'react'; import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Button, Typography } from '@material-tailwind/react'; import DeployComponent from '../../../../components/projects/create/Deploy';
import {
DeployStep,
DeployStatus,
} from '../../../../components/projects/create/template/deploy/DeployStep';
import {
Stopwatch,
setStopWatchOffset,
} from '../../../../components/StopWatch';
import ConfirmDialog from '../../../../components/shared/ConfirmDialog';
const Deploy = () => { const Deploy = () => {
const [open, setOpen] = React.useState(false); return <DeployComponent />;
const handleOpen = () => setOpen(!open);
const navigate = useNavigate();
const handleCancel = useCallback(() => {
navigate('/projects/create/template');
}, []);
return (
<div>
<div className="flex justify-between mb-6">
<div>
<h4>Deployment started ...</h4>
<div className="flex">
^&nbsp;
<Stopwatch
offsetTimestamp={setStopWatchOffset(Date.now().toString())}
/>
</div>
</div>
<div>
<Button onClick={handleOpen} variant="outlined" size="sm">
^ Cancel
</Button>
</div>
<ConfirmDialog
dialogTitle="Cancel deployment?"
handleOpen={handleOpen}
open={open}
confirmButtonTitle="Yes, Cancel deployment"
handleConfirm={handleCancel}
color="red"
>
<Typography variant="small">
This will halt the deployment and you will have to start the process
from scratch.
</Typography>
</ConfirmDialog>
</div>
<DeployStep
title="Building"
status={DeployStatus.COMPLETE}
step="1"
processTime="72000"
/>
<DeployStep
title="Deployment summary"
status={DeployStatus.PROCESSING}
step="2"
startTime={Date.now().toString()}
/>
<DeployStep
title="Running checks"
status={DeployStatus.NOT_STARTED}
step="3"
/>
<DeployStep
title="Assigning domains"
status={DeployStatus.NOT_STARTED}
step="4"
/>
</div>
);
}; };
export default Deploy; export default Deploy;

View File

@ -22,6 +22,8 @@ const CreateRepo = () => {
}, },
}); });
// TODO: Get users and orgs from GitHub
return ( return (
<form onSubmit={handleSubmit(() => {})}> <form onSubmit={handleSubmit(() => {})}>
<div className="mb-2"> <div className="mb-2">

View File

@ -41,6 +41,7 @@ export interface GitRepositoryDetails {
owner: GitOrgDetails | null; owner: GitOrgDetails | null;
visibility?: string; visibility?: string;
updated_at?: string | null; updated_at?: string | null;
default_branch?: string;
} }
export enum GitSelect { export enum GitSelect {

View File

@ -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, AddProjectMemberInput, AddProjectMemberResponse } 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, AddProjectMemberInput, AddProjectMemberResponse, AddProjectInput, AddProjectResponse } from './types';
import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain, addProjectMember } from './mutations'; import { removeProjectMember, addEnvironmentVariables, updateDeploymentToProd, updateProjectMutation, redeployToProd, deleteProject, addDomain, rollbackDeployment, updateDomainMutation, authenticateGitHub, unauthenticateGitHub, updateEnvironmentVariable, removeEnvironmentVariable, updateProjectMember, deleteDomain, addProjectMember, addProject } from './mutations';
export interface GraphQLConfig { export interface GraphQLConfig {
gqlEndpoint: string; gqlEndpoint: string;
@ -194,6 +194,17 @@ export class GQLClient {
return data; return data;
} }
async addProject (projectDetails: AddProjectInput): Promise<AddProjectResponse> {
const { data } = await this.client.mutate({
mutation: addProject,
variables: {
projectDetails
}
});
return data;
}
async updateProject (projectId: string, projectDetails: UpdateProjectInput): Promise<UpdateProjectResponse> { async updateProject (projectId: string, projectDetails: UpdateProjectInput): Promise<UpdateProjectResponse> {
const { data } = await this.client.mutate({ const { data } = await this.client.mutate({
mutation: updateProjectMutation, mutation: updateProjectMutation,

View File

@ -42,6 +42,11 @@ mutation ($deploymentId: String!) {
} }
`; `;
export const addProject = gql`
mutation ($projectDetails: AddProjectInput) {
addProject(projectDetails: $projectDetails)
}`;
export const updateProjectMutation = gql` export const updateProjectMutation = gql`
mutation ($projectId: String!, $projectDetails: UpdateProjectInput) { mutation ($projectId: String!, $projectDetails: UpdateProjectInput) {
updateProject(projectId: $projectId, projectDetails: $projectDetails) updateProject(projectId: $projectId, projectDetails: $projectDetails)

View File

@ -217,6 +217,10 @@ export type UpdateDeploymentToProdResponse = {
updateDeploymentToProd: boolean; updateDeploymentToProd: boolean;
} }
export type AddProjectResponse = {
addProject: boolean
}
export type UpdateProjectResponse = { export type UpdateProjectResponse = {
updateProject: boolean; updateProject: boolean;
} }
@ -233,6 +237,13 @@ export type DeleteDomainResponse = {
deleteDomain: boolean; deleteDomain: boolean;
} }
export type AddProjectInput = {
organizationId: string;
name: string;
repository: string;
prodBranch: string;
}
export type UpdateProjectInput = { export type UpdateProjectInput = {
name?: string name?: string
description?: string description?: string