Skip to content

Commit e08c812

Browse files
authored
Merge pull request #644 from jeff-phillips-18/wizard-ux-updates
Update knowledge and skill wizard info and doc upload pages
2 parents ff68087 + 24feea6 commit e08c812

File tree

54 files changed

+2039
-1601
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2039
-1601
lines changed
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// src/app/api/github/knowledge-files/route.ts
2+
import { NextRequest, NextResponse } from 'next/server';
3+
import { getToken } from 'next-auth/jwt';
4+
import {
5+
BASE_BRANCH,
6+
checkIfRepoExists,
7+
fetchCommitInfo,
8+
fetchMarkdownFiles,
9+
forkRepo,
10+
getBranchSha,
11+
GITHUB_API_URL,
12+
TAXONOMY_DOCUMENTS_REPO
13+
} from '@/app/api/github/utils';
14+
15+
export async function GET(req: NextRequest) {
16+
const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET! });
17+
18+
if (!token || !token.accessToken) {
19+
console.error('Unauthorized: Missing or invalid access token');
20+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
21+
}
22+
23+
const githubToken = token.accessToken as string;
24+
const headers = {
25+
'Content-Type': 'application/json',
26+
Authorization: `Bearer ${githubToken}`,
27+
Accept: 'application/vnd.github+json',
28+
'X-GitHub-Api-Version': '2022-11-28'
29+
};
30+
31+
try {
32+
// Fetch GitHub username
33+
const githubUsername = await getGitHubUsername(headers);
34+
35+
// Split the TAXONOMY_DOCUMENTS_REPO into owner and repo name
36+
const repoPath = TAXONOMY_DOCUMENTS_REPO.replace('github.com/', '');
37+
const [repoOwner, repoName] = repoPath.split('/');
38+
39+
// Check if the repository is already forked
40+
const repoForked = await checkIfRepoExists(headers, githubUsername, repoName);
41+
console.log(`Repository forked: ${repoForked}`);
42+
if (!repoForked) {
43+
// Fork the repository if it is not already forked
44+
await forkRepo(headers, repoOwner, repoName, githubUsername);
45+
// Add a delay to ensure the fork operation completes to avoid a race condition when retrieving the bas SHA
46+
// This only occurs if this is the first time submitting and the fork isn't present.
47+
// TODO change to a retry
48+
console.log('Pause 5s for the forking operation to complete');
49+
await new Promise((resolve) => setTimeout(resolve, 5000));
50+
console.log('Repository forked');
51+
}
52+
53+
// Fetch the latest commit SHA of the base branch
54+
const baseBranchSha = await getBranchSha(headers, githubUsername, repoName, BASE_BRANCH);
55+
console.log(`Base branch SHA: ${baseBranchSha}`);
56+
57+
const files = await fetchMarkdownFiles(headers, githubUsername, repoName, BASE_BRANCH);
58+
59+
let mostRecentSha = '';
60+
let mostRecentDate = 0;
61+
let mostRecentFiles: string[] = [];
62+
63+
for (const file of files) {
64+
const commitInfo = await fetchCommitInfo(headers, githubUsername, repoName, file.path);
65+
if (commitInfo) {
66+
const { sha, date } = commitInfo;
67+
const commitDate = new Date(date).getTime();
68+
if (commitDate > mostRecentDate) {
69+
mostRecentDate = commitDate;
70+
mostRecentSha = sha;
71+
mostRecentFiles = [];
72+
}
73+
if (sha === mostRecentSha) {
74+
mostRecentFiles.push(file.path);
75+
}
76+
}
77+
}
78+
const fileNames = mostRecentFiles.join(',');
79+
80+
return NextResponse.json(
81+
{
82+
repoUrl: `https://github.com/${githubUsername}/${repoName}`,
83+
commitSha: baseBranchSha,
84+
fileNames
85+
},
86+
{ status: 201 }
87+
);
88+
} catch (error) {
89+
console.error('Failed to retrieve document info:', error);
90+
return NextResponse.json({ error: 'Failed to retrieve document info' }, { status: 500 });
91+
}
92+
}
93+
94+
async function getGitHubUsername(headers: HeadersInit): Promise<string> {
95+
const response = await fetch(`${GITHUB_API_URL}/user`, { headers });
96+
97+
if (!response.ok) {
98+
const errorText = await response.text();
99+
console.error('Failed to fetch GitHub username:', response.status, errorText);
100+
throw new Error('Failed to fetch GitHub username');
101+
}
102+
103+
const data = await response.json();
104+
return data.login;
105+
}

src/app/api/github/knowledge-files/route.ts

Lines changed: 10 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
// src/app/api/github/knowledge-files/route.ts
22
import { NextRequest, NextResponse } from 'next/server';
33
import { getToken } from 'next-auth/jwt';
4-
5-
const GITHUB_API_URL = 'https://api.github.com';
6-
const TAXONOMY_DOCUMENTS_REPO = process.env.NEXT_PUBLIC_TAXONOMY_DOCUMENTS_REPO || 'https://github.com/instructlab-public/taxonomy-knowledge-docs';
7-
const BASE_BRANCH = 'main';
4+
import {
5+
BASE_BRANCH,
6+
checkIfRepoExists,
7+
fetchCommitInfo,
8+
fetchMarkdownFiles,
9+
forkRepo,
10+
getBranchSha,
11+
GITHUB_API_URL,
12+
TAXONOMY_DOCUMENTS_REPO
13+
} from '@/app/api/github/utils';
814

915
// Interface for the response
1016
interface KnowledgeFile {
@@ -100,62 +106,6 @@ async function getGitHubUsernameAndEmail(headers: HeadersInit): Promise<{ github
100106
return { githubUsername: data.login, userEmail: data.email };
101107
}
102108

103-
async function checkIfRepoExists(headers: HeadersInit, owner: string, repo: string): Promise<boolean> {
104-
const response = await fetch(`${GITHUB_API_URL}/repos/${owner}/${repo}`, { headers });
105-
const exists = response.ok;
106-
if (!exists) {
107-
const errorText = await response.text();
108-
console.error('Repository does not exist:', response.status, errorText);
109-
}
110-
return exists;
111-
}
112-
113-
async function forkRepo(headers: HeadersInit, owner: string, repo: string, forkOwner: string) {
114-
const response = await fetch(`${GITHUB_API_URL}/repos/${owner}/${repo}/forks`, {
115-
method: 'POST',
116-
headers
117-
});
118-
119-
if (!response.ok) {
120-
const errorText = await response.text();
121-
console.error('Failed to fork repository:', response.status, errorText);
122-
throw new Error('Failed to fork repository');
123-
}
124-
125-
// Wait for the fork to be created
126-
let forkCreated = false;
127-
for (let i = 0; i < 10; i++) {
128-
const forkExists = await checkIfRepoExists(headers, forkOwner, repo);
129-
if (forkExists) {
130-
forkCreated = true;
131-
break;
132-
}
133-
await new Promise((resolve) => setTimeout(resolve, 3000));
134-
}
135-
136-
if (!forkCreated) {
137-
throw new Error('Failed to confirm fork creation');
138-
}
139-
}
140-
141-
async function getBranchSha(headers: HeadersInit, owner: string, repo: string, branch: string): Promise<string> {
142-
console.log(`Fetching branch SHA for ${branch}...`);
143-
const response = await fetch(`${GITHUB_API_URL}/repos/${owner}/${repo}/git/ref/heads/${branch}`, { headers });
144-
145-
if (!response.ok) {
146-
const errorText = await response.text();
147-
console.error('Failed to get branch SHA:', response.status, errorText);
148-
if (response.status === 409 && errorText.includes('Git Repository is empty')) {
149-
throw new Error('Git Repository is empty.');
150-
}
151-
throw new Error('Failed to get branch SHA');
152-
}
153-
154-
const data = await response.json();
155-
console.log('Branch SHA:', data.object.sha);
156-
return data.object.sha;
157-
}
158-
159109
async function createFilesCommit(
160110
headers: HeadersInit,
161111
owner: string,
@@ -292,55 +242,6 @@ export async function GET(req: NextRequest) {
292242
}
293243
}
294244

295-
// Fetch all markdown files from the main branch
296-
async function fetchMarkdownFiles(
297-
headers: HeadersInit,
298-
owner: string,
299-
repo: string,
300-
branchName: string
301-
): Promise<{ path: string; content: string }[]> {
302-
try {
303-
const response = await fetch(`${GITHUB_API_URL}/repos/${owner}/${repo}/git/trees/${branchName}?recursive=1`, { headers });
304-
if (!response.ok) {
305-
const errorText = await response.text();
306-
console.error('Failed to fetch files from knowledge document repository:', response.status, errorText);
307-
throw new Error('Failed to fetch file from knowledge document repository:');
308-
}
309-
310-
const data = await response.json();
311-
const files = data.tree.filter(
312-
(item: { type: string; path: string }) => item.type === 'blob' && item.path.endsWith('.md') && item.path !== 'README.md'
313-
);
314-
return files.map((file: { path: string; content: string }) => ({ path: file.path, content: file.content }));
315-
} catch (error) {
316-
console.error('Error fetching files from knowledge document repository:', error);
317-
return [];
318-
}
319-
}
320-
321-
// Fetch the latest commit info for a file
322-
async function fetchCommitInfo(headers: HeadersInit, owner: string, repo: string, filePath: string): Promise<{ sha: string; date: string } | null> {
323-
try {
324-
const response = await fetch(`${GITHUB_API_URL}/repos/${owner}/${repo}/commits?path=${filePath}`, { headers });
325-
if (!response.ok) {
326-
const errorText = await response.text();
327-
console.error('Failed to fetch commit information for file:', response.status, errorText);
328-
throw new Error('Failed to fetch commit information for file.');
329-
}
330-
331-
const data = await response.json();
332-
if (data.length === 0) return null;
333-
334-
return {
335-
sha: data[0].sha,
336-
date: data[0].commit.committer.date
337-
};
338-
} catch (error) {
339-
console.error(`Error fetching commit info for ${filePath}:`, error);
340-
return null;
341-
}
342-
}
343-
344245
// Fetch the content of a file from the repository
345246
async function fetchFileContent(headers: HeadersInit, owner: string, repo: string, filePath: string): Promise<string> {
346247
try {

src/app/api/github/pr/knowledge/route.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { KnowledgeYamlData, AttributionData } from '@/types';
66
import { GITHUB_API_URL, BASE_BRANCH } from '@/types/const';
77
import { dumpYaml } from '@/utils/yamlConfig';
88
import { checkUserForkExists, createBranch, createFilesInSingleCommit, createFork, getBaseBranchSha, getGitHubUsername } from '@/utils/github';
9+
import { prInfoFromSummary } from '@/app/api/github/utils';
910

1011
const KNOWLEDGE_DIR = 'knowledge';
1112
const UPSTREAM_REPO_OWNER = process.env.NEXT_PUBLIC_TAXONOMY_REPO_OWNER!;
@@ -29,7 +30,7 @@ export async function POST(req: NextRequest) {
2930

3031
try {
3132
const body = await req.json();
32-
const { content, attribution, name, email, submissionSummary, documentOutline, filePath } = body;
33+
const { content, attribution, name, email, submissionSummary, filePath } = body;
3334

3435
const knowledgeData: KnowledgeYamlData = yaml.load(content) as KnowledgeYamlData;
3536
const attributionData: AttributionData = attribution;
@@ -64,6 +65,8 @@ Creator names: ${attributionData.creator_names}
6465
// Create a new branch in the user's fork
6566
await createBranch(headers, githubUsername, UPSTREAM_REPO_NAME, branchName, baseBranchSha);
6667

68+
const { prTitle, prBody, commitMessage } = prInfoFromSummary(submissionSummary);
69+
6770
// Create both files in a single commit with DCO sign-off
6871
await createFilesInSingleCommit(
6972
headers,
@@ -74,11 +77,11 @@ Creator names: ${attributionData.creator_names}
7477
{ path: newAttributionFilePath, content: attributionContent }
7578
],
7679
branchName,
77-
`${submissionSummary}\n\nSigned-off-by: ${name} <${email}>`
80+
`${commitMessage}\n\nSigned-off-by: ${name} <${email}>`
7881
);
7982

8083
// Create a pull request from the user's fork to the upstream repository
81-
const pr = await createPullRequest(headers, githubUsername, branchName, submissionSummary, documentOutline);
84+
const pr = await createPullRequest(headers, githubUsername, branchName, prTitle, prBody);
8285

8386
return NextResponse.json(pr, { status: 201 });
8487
} catch (error) {
@@ -87,14 +90,14 @@ Creator names: ${attributionData.creator_names}
8790
}
8891
}
8992

90-
async function createPullRequest(headers: HeadersInit, username: string, branchName: string, knowledgeSummary: string, documentOutline: string) {
93+
async function createPullRequest(headers: HeadersInit, username: string, branchName: string, prTitle: string, prBody?: string) {
9194
const response = await fetch(`${GITHUB_API_URL}/repos/${UPSTREAM_REPO_OWNER}/${UPSTREAM_REPO_NAME}/pulls`, {
9295
method: 'POST',
9396
headers,
9497
body: JSON.stringify({
95-
title: `Knowledge: ${knowledgeSummary}`,
98+
title: `Knowledge: ${prTitle}`,
9699
head: `${username}:${branchName}`,
97-
body: documentOutline,
100+
body: prBody,
98101
base: BASE_BRANCH
99102
})
100103
});

src/app/api/github/pr/skill/route.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SkillYamlData, AttributionData } from '@/types';
66
import { GITHUB_API_URL, BASE_BRANCH } from '@/types/const';
77
import { dumpYaml } from '@/utils/yamlConfig';
88
import { checkUserForkExists, createBranch, createFilesInSingleCommit, createFork, getBaseBranchSha, getGitHubUsername } from '@/utils/github';
9+
import { prInfoFromSummary } from '@/app/api/github/utils';
910

1011
const SKILLS_DIR = 'compositional_skills';
1112
const UPSTREAM_REPO_OWNER = process.env.NEXT_PUBLIC_TAXONOMY_REPO_OWNER!;
@@ -29,7 +30,7 @@ export async function POST(req: NextRequest) {
2930

3031
try {
3132
const body = await req.json();
32-
const { content, attribution, name, email, submissionSummary, documentOutline, filePath } = body;
33+
const { content, attribution, name, email, submissionSummary, filePath } = body;
3334

3435
const githubUsername = await getGitHubUsername(headers);
3536
console.log('Skill contribution from gitHub Username:', githubUsername);
@@ -62,6 +63,8 @@ Creator names: ${attributionData.creator_names}
6263
// Create a new branch in the user's fork
6364
await createBranch(headers, githubUsername, UPSTREAM_REPO_NAME, branchName, baseBranchSha);
6465

66+
const { prTitle, prBody, commitMessage } = prInfoFromSummary(submissionSummary);
67+
6568
// Create both files in a single commit
6669
await createFilesInSingleCommit(
6770
headers,
@@ -72,11 +75,11 @@ Creator names: ${attributionData.creator_names}
7275
{ path: newAttributionFilePath, content: attributionString }
7376
],
7477
branchName,
75-
`${submissionSummary}\n\nSigned-off-by: ${name} <${email}>`
78+
`${commitMessage}\n\nSigned-off-by: ${name} <${email}>`
7679
);
7780

7881
// Create a pull request from the user's fork to the upstream repository
79-
const pr = await createPullRequest(headers, githubUsername, branchName, submissionSummary, documentOutline);
82+
const pr = await createPullRequest(headers, githubUsername, branchName, prTitle, prBody);
8083

8184
return NextResponse.json(pr, { status: 201 });
8285
} catch (error) {
@@ -85,14 +88,14 @@ Creator names: ${attributionData.creator_names}
8588
}
8689
}
8790

88-
async function createPullRequest(headers: HeadersInit, username: string, branchName: string, skillSummary: string, skillDescription: string) {
91+
async function createPullRequest(headers: HeadersInit, username: string, branchName: string, prTitle: string, prBody?: string) {
8992
const response = await fetch(`${GITHUB_API_URL}/repos/${UPSTREAM_REPO_OWNER}/${UPSTREAM_REPO_NAME}/pulls`, {
9093
method: 'POST',
9194
headers,
9295
body: JSON.stringify({
93-
title: `Skill: ${skillSummary}`,
96+
title: `Skill: ${prTitle}`,
97+
body: prBody,
9498
head: `${username}:${branchName}`,
95-
body: skillDescription,
9699
base: BASE_BRANCH
97100
})
98101
});

0 commit comments

Comments
 (0)