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 <neeraj.rtly@gmail.com>
This commit is contained in:
Nabarun Gogoi 2024-02-14 17:35:02 +05:30 committed by GitHub
parent db3b9148b6
commit 9921dc5186
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 103 additions and 90 deletions

View File

@ -236,9 +236,9 @@ export class Database {
return savedEnvironmentVariables;
}
async updateEnvironmentVariable (environmentVariableId: string, update: DeepPartial<EnvironmentVariable>): Promise<boolean> {
async updateEnvironmentVariable (environmentVariableId: string, data: DeepPartial<EnvironmentVariable>): Promise<boolean> {
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<Deployment>): Promise<boolean> {
async updateDeploymentById (deploymentId: string, data: DeepPartial<Deployment>): Promise<boolean> {
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<Project>): Promise<Project> {
async addProject (userId: string, organizationId: string, data: DeepPartial<Project>): Promise<Project> {
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<Project>): Promise<boolean> {
async updateProjectById (projectId: string, data: DeepPartial<Project>): Promise<boolean> {
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<Domain>): Promise<boolean> {
async updateDomainById (domainId: string, data: DeepPartial<Domain>): Promise<boolean> {
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);
}

View File

@ -135,9 +135,9 @@ export const createResolvers = async (service: Service): Promise<any> => {
}
},
updateProject: async (_: any, { projectId, projectDetails }: { projectId: string, projectDetails: DeepPartial<Project> }) => {
updateProject: async (_: any, { projectId, data }: { projectId: string, data: DeepPartial<Project> }) => {
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<any> => {
}
},
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<Domain>}) => {
updateDomain: async (_: any, { domainId, data }: { domainId: string, data: DeepPartial<Domain>}) => {
try {
return await service.updateDomain(domainId, domainDetails);
return await service.updateDomain(domainId, data);
} catch (err) {
log(err);
return false;

View File

@ -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!
}

View File

@ -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<Domain>): Promise<boolean> {
async updateDomain (domainId: string, data: DeepPartial<Domain>): Promise<boolean> {
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
}
});

View File

@ -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';

View File

@ -15,16 +15,20 @@ const UNAUTHORIZED_ERROR_CODE = 401;
interface ContextValue {
octokit: Octokit | null;
isAuth: boolean;
updateAuth: () => void;
}
const OctokitContext = createContext<ContextValue>({
octokit: null,
isAuth: false,
updateAuth: () => {},
});
export const OctokitProvider = ({ children }: { children: ReactNode }) => {
const [authToken, setAuthToken] = useState<string>('');
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 (
<OctokitContext.Provider value={{ octokit, updateAuth }}>
<OctokitContext.Provider value={{ octokit, updateAuth, isAuth }}>
{children}
</OctokitContext.Provider>
);
};
export const useOctokit = () => {
const { octokit, updateAuth } = useContext(OctokitContext);
const { octokit, updateAuth, isAuth } = useContext(OctokitContext);
return { octokit, updateAuth };
return { octokit, updateAuth, isAuth };
};

View File

@ -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 ? (
<>
<h5 className="mt-4 ml-4">Start with template</h5>
<div className="grid grid-cols-3 p-4 gap-4">

View File

@ -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<GitCommitDetails[]>([]);
const [liveDomain, setLiveDomain] = useState<Domain>();
@ -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
</Button>

View File

@ -206,28 +206,28 @@ export class GQLClient {
return result.data;
}
async updateProject (projectId: string, projectDetails: types.UpdateProjectInput): Promise<types.UpdateProjectResponse> {
const { data } = await this.client.mutate({
async updateProject (projectId: string, data: types.UpdateProjectInput): Promise<types.UpdateProjectResponse> {
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<types.UpdateDomainResponse> {
const { data } = await this.client.mutate({
async updateDomain (domainId: string, data: types.UpdateDomainInput): Promise<types.UpdateDomainResponse> {
const result = await this.client.mutate({
mutation: mutations.updateDomainMutation,
variables: {
domainId,
domainDetails
data
}
});
return data;
return result.data;
}
async redeployToProd (deploymentId: string): Promise<types.RedeployToProdResponse> {
@ -275,16 +275,16 @@ export class GQLClient {
return data;
}
async addDomain (projectId: string, domainDetails: types.AddDomainInput): Promise<types.AddDomainResponse> {
const { data } = await this.client.mutate({
async addDomain (projectId: string, data: types.AddDomainInput): Promise<types.AddDomainResponse> {
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<types.GetDomainsResponse> {

View File

@ -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)
}
`;