Skip to content

Commit

Permalink
Knowledge and Skill contribution fails if the initial
Browse files Browse the repository at this point in the history
taxonomy clone is not present in user github account.

UI attempts to create the clone, but the clone can
take some time to finish. When UI attempts to get the
base sha of the repo's main branch it fails because
the repository is empty.

Add retry logic to check the bash sha every 5 seconds
and make 10 attempts. If clone takes more than 50 seconds
it fails.

In this specific scenario, when user creates it's first
contribution, it can take a bit longer because of the
repo cloning. Without any notification, this wait time
might look like a no-op to the user. To address this
added a spinner with an alter message that notifies
user that the submission is in progress.

Patch also includes refactoring of duplicate code present
across api/skill and api/knowledge.

Signed-off-by: Anil Vishnoi <[email protected]>
  • Loading branch information
vishnoianil committed Sep 13, 2024
1 parent 7f40a3b commit f270c14
Show file tree
Hide file tree
Showing 20 changed files with 324 additions and 412 deletions.
182 changes: 7 additions & 175 deletions src/app/api/pr/knowledge/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { getToken } from 'next-auth/jwt';
import { NextRequest } from 'next/server';
import yaml from 'js-yaml';
import { KnowledgeYamlData, AttributionData } from '@/types';
import { GITHUB_API_URL, BASE_BRANCH } from '@/types/const';
import { dumpYaml } from '@/utils/yamlConfig';
import { checkUserForkExists, createBranch, createFilesInSingleCommit, createFork, getBaseBranchSha, getGitHubUsername } from '@/utils/github';

const GITHUB_API_URL = 'https://api.github.com';
const KNOWLEDGE_DIR = 'knowledge';
const UPSTREAM_REPO_OWNER = process.env.NEXT_PUBLIC_TAXONOMY_REPO_OWNER!;
const UPSTREAM_REPO_NAME = process.env.NEXT_PUBLIC_TAXONOMY_REPO!;
const BASE_BRANCH = 'main';

export async function POST(req: NextRequest) {
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET! });
Expand Down Expand Up @@ -40,12 +40,9 @@ export async function POST(req: NextRequest) {
console.log('GitHub Username:', githubUsername);

// Check if user's fork exists, if not, create it
const forkExists = await checkUserForkExists(headers, githubUsername);
const forkExists = await checkUserForkExists(headers, githubUsername, UPSTREAM_REPO_NAME);
if (!forkExists) {
await createFork(headers);
// Add a delay to ensure the fork operation completes to avoid a race condition when retrieving the base SHA
console.log('Pause 5s for the forking operation to complete');
await new Promise((resolve) => setTimeout(resolve, 5000));
await createFork(headers, UPSTREAM_REPO_OWNER, UPSTREAM_REPO_NAME, githubUsername);
}

const branchName = `knowledge-contribution-${Date.now()}`;
Expand All @@ -62,16 +59,17 @@ Creator names: ${attributionData.creator_names}
`;

// Get the base branch SHA
const baseBranchSha = await getBaseBranchSha(headers, githubUsername);
const baseBranchSha = await getBaseBranchSha(headers, githubUsername, UPSTREAM_REPO_NAME);
console.log(`Base branch SHA: ${baseBranchSha}`);

// Create a new branch in the user's fork
await createBranch(headers, githubUsername, branchName, baseBranchSha);
await createBranch(headers, githubUsername, UPSTREAM_REPO_NAME, branchName, baseBranchSha);

// Create both files in a single commit with DCO sign-off
await createFilesInSingleCommit(
headers,
githubUsername,
UPSTREAM_REPO_NAME,
[
{ path: newYamlFilePath, content: yamlString },
{ path: newAttributionFilePath, content: attributionContent }
Expand All @@ -90,172 +88,6 @@ Creator names: ${attributionData.creator_names}
}
}

async function getGitHubUsername(headers: HeadersInit): Promise<string> {
const response = await fetch(`${GITHUB_API_URL}/user`, {
headers
});

if (!response.ok) {
const errorText = await response.text();
console.error('Failed to fetch GitHub username:', response.status, errorText);
throw new Error('Failed to fetch GitHub username');
}

const data = await response.json();
return data.login;
}

async function checkUserForkExists(headers: HeadersInit, username: string) {
const response = await fetch(`${GITHUB_API_URL}/repos/${username}/${UPSTREAM_REPO_NAME}`, {
headers
});

return response.ok;
}

async function createFork(headers: HeadersInit) {
const response = await fetch(`${GITHUB_API_URL}/repos/${UPSTREAM_REPO_OWNER}/${UPSTREAM_REPO_NAME}/forks`, {
method: 'POST',
headers
});

if (!response.ok) {
const errorText = await response.text();
console.error('Failed to create fork:', response.status, errorText);
throw new Error('Failed to create fork');
}

const responseData = await response.json();
console.log('Fork created successfully:', responseData);
}

async function getBaseBranchSha(headers: HeadersInit, username: string) {
const response = await fetch(`${GITHUB_API_URL}/repos/${username}/${UPSTREAM_REPO_NAME}/git/refs/heads/${BASE_BRANCH}`, {
headers
});

if (!response.ok) {
const errorText = await response.text();
console.error('Failed to get base branch SHA:', response.status, errorText);
throw new Error('Failed to get base branch SHA');
}

const data = await response.json();
return data.object.sha;
}

async function createBranch(headers: HeadersInit, username: string, branchName: string, baseSha: string) {
const body = JSON.stringify({
ref: `refs/heads/${branchName}`,
sha: baseSha
});

console.log(`Creating branch with body: ${body}`);

const response = await fetch(`${GITHUB_API_URL}/repos/${username}/${UPSTREAM_REPO_NAME}/git/refs`, {
method: 'POST',
headers,
body
});

if (!response.ok) {
const errorText = await response.text();
console.error('Failed to create branch:', response.status, errorText);
throw new Error('Failed to create branch');
}

const responseData = await response.json();
console.log('Branch created successfully:', responseData);
}

async function createFilesInSingleCommit(
headers: HeadersInit,
username: string,
files: { path: string; content: string }[],
branchName: string,
commitMessage: string
) {
const fileData = files.map((file) => ({
path: file.path,
mode: '100644',
type: 'blob',
content: file.content
}));

const response = await fetch(`${GITHUB_API_URL}/repos/${username}/${UPSTREAM_REPO_NAME}/git/trees`, {
method: 'POST',
headers,
body: JSON.stringify({
base_tree: await getBaseTreeSha(headers, username, branchName),
tree: fileData
})
});

if (!response.ok) {
const errorText = await response.text();
console.error('Failed to create files:', response.status, errorText);
throw new Error('Failed to create files');
}

const treeData = await response.json();

const commitResponse = await fetch(`${GITHUB_API_URL}/repos/${username}/${UPSTREAM_REPO_NAME}/git/commits`, {
method: 'POST',
headers,
body: JSON.stringify({
message: commitMessage,
tree: treeData.sha,
parents: [await getCommitSha(headers, username, branchName)]
})
});

if (!commitResponse.ok) {
const errorText = await commitResponse.text();
console.error('Failed to create commit:', commitResponse.status, errorText);
throw new Error('Failed to create commit');
}

const commitData = await commitResponse.json();

await fetch(`${GITHUB_API_URL}/repos/${username}/${UPSTREAM_REPO_NAME}/git/refs/heads/${branchName}`, {
method: 'PATCH',
headers,
body: JSON.stringify({
sha: commitData.sha
})
});
}

async function getBaseTreeSha(headers: HeadersInit, username: string, branchName: string): Promise<string> {
const response = await fetch(`${GITHUB_API_URL}/repos/${username}/${UPSTREAM_REPO_NAME}/git/trees/${branchName}`, {
headers
});

if (!response.ok) {
const errorText = await response.text();
console.error('Failed to get base tree SHA:', response.status, errorText);
throw new Error('Failed to get base tree SHA');
}

const data = await response.json();
return data.sha;
}

async function getCommitSha(headers: HeadersInit, username: string, branchName: string): Promise<string> {
const response = await fetch(`${GITHUB_API_URL}/repos/${username}/${UPSTREAM_REPO_NAME}/git/refs/heads/${branchName}`, {
headers
});

if (!response.ok) {
const errorText = await response.text();
console.error('Failed to get commit SHA:', response.status, errorText);
throw new Error('Failed to get commit SHA');
}

const data = await response.json();
return data.object.sha;
}

async function createPullRequest(headers: HeadersInit, username: string, branchName: string, knowledgeSummary: string, documentOutline: string) {
const response = await fetch(`${GITHUB_API_URL}/repos/${UPSTREAM_REPO_OWNER}/${UPSTREAM_REPO_NAME}/pulls`, {
method: 'POST',
Expand Down
Loading

0 comments on commit f270c14

Please sign in to comment.