@stoked-ui/github 0.0.0-a.0 → 0.1.0-alpha.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/GithubBranch/GithubBranch.d.ts +12 -0
- package/GithubBranch/GithubBranch.js +177 -0
- package/GithubBranch/index.d.ts +2 -0
- package/GithubBranch/index.js +2 -0
- package/GithubBranch/package.json +6 -0
- package/GithubCalendar/GithubCalendar.d.ts +6 -2
- package/GithubCalendar/GithubCalendar.js +70 -28
- package/GithubCommit/GithubCommit.d.ts +11 -0
- package/GithubCommit/GithubCommit.js +170 -0
- package/GithubCommit/index.d.ts +2 -0
- package/GithubCommit/index.js +2 -0
- package/GithubCommit/package.json +6 -0
- package/GithubEvents/EventTypes/PullRequest/PullRequestEvent.js +1 -1
- package/GithubEvents/GithubEvents.d.ts +4 -24
- package/GithubEvents/GithubEvents.js +141 -220
- package/apiHandlers/createGithubBranchHandler.d.ts +12 -0
- package/apiHandlers/createGithubBranchHandler.js +41 -0
- package/apiHandlers/createGithubCommitHandler.d.ts +12 -0
- package/apiHandlers/createGithubCommitHandler.js +39 -0
- package/apiHandlers/createGithubContributionsHandler.d.ts +15 -0
- package/apiHandlers/createGithubContributionsHandler.js +41 -0
- package/apiHandlers/createGithubEventsHandler.d.ts +15 -0
- package/apiHandlers/createGithubEventsHandler.js +50 -0
- package/apiHandlers/getBranchCompareDetails.d.ts +9 -0
- package/apiHandlers/getBranchCompareDetails.js +29 -0
- package/apiHandlers/getCommitDetails.d.ts +8 -0
- package/apiHandlers/getCommitDetails.js +34 -0
- package/apiHandlers/getGithubContributions.d.ts +8 -0
- package/apiHandlers/getGithubContributions.js +126 -0
- package/apiHandlers/getGithubEvents.d.ts +25 -0
- package/apiHandlers/getGithubEvents.js +142 -0
- package/apiHandlers/getPullRequestDetails.js +2 -2
- package/apiHandlers/githubApi.d.ts +17 -0
- package/apiHandlers/githubApi.js +155 -0
- package/apiHandlers/index.d.ts +9 -0
- package/apiHandlers/index.js +9 -1
- package/components/GithubContributorsList.d.ts +8 -0
- package/components/GithubContributorsList.js +72 -0
- package/components/fetchGithubViewData.d.ts +1 -0
- package/components/fetchGithubViewData.js +10 -0
- package/index.d.ts +7 -3
- package/index.js +6 -4
- package/modern/GithubBranch/GithubBranch.js +177 -0
- package/modern/GithubBranch/index.js +2 -0
- package/modern/GithubCalendar/GithubCalendar.js +70 -28
- package/modern/GithubCommit/GithubCommit.js +170 -0
- package/modern/GithubCommit/index.js +2 -0
- package/modern/GithubEvents/EventTypes/PullRequest/PullRequestEvent.js +1 -1
- package/modern/GithubEvents/GithubEvents.js +141 -220
- package/modern/apiHandlers/createGithubBranchHandler.js +41 -0
- package/modern/apiHandlers/createGithubCommitHandler.js +39 -0
- package/modern/apiHandlers/createGithubContributionsHandler.js +41 -0
- package/modern/apiHandlers/createGithubEventsHandler.js +50 -0
- package/modern/apiHandlers/getBranchCompareDetails.js +29 -0
- package/modern/apiHandlers/getCommitDetails.js +34 -0
- package/modern/apiHandlers/getGithubContributions.js +126 -0
- package/modern/apiHandlers/getGithubEvents.js +142 -0
- package/modern/apiHandlers/getPullRequestDetails.js +2 -2
- package/modern/apiHandlers/githubApi.js +155 -0
- package/modern/apiHandlers/index.js +9 -1
- package/modern/components/GithubContributorsList.js +72 -0
- package/modern/components/fetchGithubViewData.js +10 -0
- package/modern/index.js +6 -4
- package/node/GithubBranch/GithubBranch.js +185 -0
- package/node/GithubBranch/index.js +9 -0
- package/node/GithubCalendar/GithubCalendar.js +70 -28
- package/node/GithubCommit/GithubCommit.js +178 -0
- package/node/GithubCommit/index.js +9 -0
- package/node/GithubEvents/EventTypes/PullRequest/PullRequestEvent.js +1 -1
- package/node/GithubEvents/GithubEvents.js +148 -223
- package/node/apiHandlers/createGithubBranchHandler.js +48 -0
- package/node/apiHandlers/createGithubCommitHandler.js +46 -0
- package/node/apiHandlers/createGithubContributionsHandler.js +48 -0
- package/node/apiHandlers/createGithubEventsHandler.js +57 -0
- package/node/apiHandlers/getBranchCompareDetails.js +35 -0
- package/node/apiHandlers/getCommitDetails.js +40 -0
- package/node/apiHandlers/getGithubContributions.js +132 -0
- package/node/apiHandlers/getGithubEvents.js +149 -0
- package/node/apiHandlers/getPullRequestDetails.js +2 -2
- package/node/apiHandlers/githubApi.js +168 -0
- package/node/apiHandlers/index.js +64 -1
- package/node/components/GithubContributorsList.js +80 -0
- package/node/components/fetchGithubViewData.js +16 -0
- package/node/index.js +77 -2
- package/package.json +2 -5
- package/stoked-ui-github-0.1.0-alpha.11.2.tgz +0 -0
- package/types/github.d.ts +75 -11
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { buildGithubContributors, fetchGithubResource, normalizeGithubCommit, normalizeGithubFile, summarizeGithubStats } from './githubApi';
|
|
2
|
+
export default async function getBranchCompareDetails(params) {
|
|
3
|
+
const {
|
|
4
|
+
owner,
|
|
5
|
+
repo,
|
|
6
|
+
base,
|
|
7
|
+
head
|
|
8
|
+
} = params;
|
|
9
|
+
if (!owner || !repo || !base || !head) {
|
|
10
|
+
throw new Error('Missing required parameters: owner, repo, base, head');
|
|
11
|
+
}
|
|
12
|
+
const compare = await fetchGithubResource(`/repos/${owner}/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`);
|
|
13
|
+
const commits = (compare.commits || []).map(commit => normalizeGithubCommit(commit));
|
|
14
|
+
const files = (compare.files || []).map(file => normalizeGithubFile(file));
|
|
15
|
+
return {
|
|
16
|
+
repo: `${owner}/${repo}`,
|
|
17
|
+
baseRef: base,
|
|
18
|
+
headRef: head,
|
|
19
|
+
status: compare.status || 'unknown',
|
|
20
|
+
aheadBy: compare.ahead_by || 0,
|
|
21
|
+
behindBy: compare.behind_by || 0,
|
|
22
|
+
totalCommits: compare.total_commits || commits.length,
|
|
23
|
+
url: compare.html_url || `https://github.com/${owner}/${repo}/compare/${encodeURIComponent(base)}...${encodeURIComponent(head)}`,
|
|
24
|
+
contributors: buildGithubContributors(compare.commits || []),
|
|
25
|
+
commits,
|
|
26
|
+
files,
|
|
27
|
+
stats: summarizeGithubStats(files)
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { buildGithubContributors, fetchGithubResource, getGithubMessageSummary, getGithubShortRef, normalizeGithubCommit, normalizeGithubFile, summarizeGithubStats } from './githubApi';
|
|
2
|
+
export default async function getCommitDetails(params) {
|
|
3
|
+
var _commit$commit, _commit$commit2, _commit$commit3, _commit$commit4;
|
|
4
|
+
const {
|
|
5
|
+
owner,
|
|
6
|
+
repo,
|
|
7
|
+
ref
|
|
8
|
+
} = params;
|
|
9
|
+
if (!owner || !repo || !ref) {
|
|
10
|
+
throw new Error('Missing required parameters: owner, repo, ref');
|
|
11
|
+
}
|
|
12
|
+
const commit = await fetchGithubResource(`/repos/${owner}/${repo}/commits/${encodeURIComponent(ref)}`);
|
|
13
|
+
const files = (commit.files || []).map(file => normalizeGithubFile(file));
|
|
14
|
+
const commits = [normalizeGithubCommit(commit)];
|
|
15
|
+
const contributor = buildGithubContributors([commit])[0] || {
|
|
16
|
+
login: commits[0].author.login,
|
|
17
|
+
name: commits[0].author.name,
|
|
18
|
+
avatarUrl: commits[0].author.avatar,
|
|
19
|
+
contributions: 1
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
repo: `${owner}/${repo}`,
|
|
23
|
+
ref: commit.sha || ref,
|
|
24
|
+
shortRef: getGithubShortRef(commit.sha || ref),
|
|
25
|
+
url: commit.html_url || `https://github.com/${owner}/${repo}/commit/${encodeURIComponent(commit.sha || ref)}`,
|
|
26
|
+
summary: getGithubMessageSummary(((_commit$commit = commit.commit) == null ? void 0 : _commit$commit.message) || ''),
|
|
27
|
+
message: ((_commit$commit2 = commit.commit) == null ? void 0 : _commit$commit2.message) || '',
|
|
28
|
+
committedAt: ((_commit$commit3 = commit.commit) == null || (_commit$commit3 = _commit$commit3.author) == null ? void 0 : _commit$commit3.date) || ((_commit$commit4 = commit.commit) == null || (_commit$commit4 = _commit$commit4.committer) == null ? void 0 : _commit$commit4.date) || '',
|
|
29
|
+
contributor,
|
|
30
|
+
commits,
|
|
31
|
+
files,
|
|
32
|
+
stats: summarizeGithubStats(files)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
const CONTRIBUTION_LEVEL_MAP = {
|
|
2
|
+
NONE: 0,
|
|
3
|
+
FIRST_QUARTILE: 1,
|
|
4
|
+
SECOND_QUARTILE: 2,
|
|
5
|
+
THIRD_QUARTILE: 3,
|
|
6
|
+
FOURTH_QUARTILE: 4
|
|
7
|
+
};
|
|
8
|
+
const GITHUB_CONTRIBUTIONS_QUERY = `
|
|
9
|
+
query GithubContributions($login: String!, $from: DateTime!, $to: DateTime!) {
|
|
10
|
+
user(login: $login) {
|
|
11
|
+
contributionsCollection(from: $from, to: $to) {
|
|
12
|
+
contributionCalendar {
|
|
13
|
+
totalContributions
|
|
14
|
+
weeks {
|
|
15
|
+
contributionDays {
|
|
16
|
+
date
|
|
17
|
+
contributionCount
|
|
18
|
+
contributionLevel
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
function isValidDateInput(value) {
|
|
27
|
+
if (!value) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return !Number.isNaN(new Date(value).getTime());
|
|
31
|
+
}
|
|
32
|
+
function toDateRange(from, to) {
|
|
33
|
+
const rangeEnd = isValidDateInput(to) ? new Date(to) : new Date();
|
|
34
|
+
const rangeStart = isValidDateInput(from) ? new Date(from) : new Date(rangeEnd);
|
|
35
|
+
if (!isValidDateInput(from)) {
|
|
36
|
+
rangeStart.setFullYear(rangeEnd.getFullYear() - 1);
|
|
37
|
+
}
|
|
38
|
+
if (rangeStart > rangeEnd) {
|
|
39
|
+
throw new Error('Invalid contribution range: "from" must be before "to".');
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
from: rangeStart,
|
|
43
|
+
to: rangeEnd
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function buildContributionTotals(contributions) {
|
|
47
|
+
return contributions.reduce((acc, contribution) => {
|
|
48
|
+
const year = contribution.date.slice(0, 4);
|
|
49
|
+
acc[year] = (acc[year] || 0) + contribution.count;
|
|
50
|
+
return acc;
|
|
51
|
+
}, {});
|
|
52
|
+
}
|
|
53
|
+
function buildCountLabel(total, from, to) {
|
|
54
|
+
const fromYear = from.getFullYear();
|
|
55
|
+
const toYear = to.getFullYear();
|
|
56
|
+
if (fromYear === toYear) {
|
|
57
|
+
return `${total} contributions in ${fromYear}`;
|
|
58
|
+
}
|
|
59
|
+
return `${total} contributions from ${fromYear} to ${toYear}`;
|
|
60
|
+
}
|
|
61
|
+
export default async function getGithubContributions({
|
|
62
|
+
githubUser,
|
|
63
|
+
githubToken = process.env.GITHUB_TOKEN,
|
|
64
|
+
from,
|
|
65
|
+
to
|
|
66
|
+
}) {
|
|
67
|
+
var _payload$errors2, _payload$data;
|
|
68
|
+
if (!githubUser) {
|
|
69
|
+
throw new Error('Missing required parameter: githubUser');
|
|
70
|
+
}
|
|
71
|
+
if (!githubToken) {
|
|
72
|
+
throw new Error('GitHub token not configured');
|
|
73
|
+
}
|
|
74
|
+
const range = toDateRange(from, to);
|
|
75
|
+
const response = await fetch('https://api.github.com/graphql', {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
'User-Agent': 'stoked-ui-github-calendar',
|
|
80
|
+
Authorization: `Bearer ${githubToken}`
|
|
81
|
+
},
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
query: GITHUB_CONTRIBUTIONS_QUERY,
|
|
84
|
+
variables: {
|
|
85
|
+
login: githubUser,
|
|
86
|
+
from: range.from.toISOString(),
|
|
87
|
+
to: range.to.toISOString()
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
});
|
|
91
|
+
const payload = await response.json();
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
var _payload$errors;
|
|
94
|
+
const message = (payload == null ? void 0 : payload.message) || (payload == null || (_payload$errors = payload.errors) == null || (_payload$errors = _payload$errors[0]) == null ? void 0 : _payload$errors.message) || `GitHub GraphQL error: ${response.status}`;
|
|
95
|
+
throw new Error(message);
|
|
96
|
+
}
|
|
97
|
+
if (payload != null && (_payload$errors2 = payload.errors) != null && _payload$errors2.length) {
|
|
98
|
+
throw new Error(payload.errors.map(error => error.message || 'Unknown GitHub GraphQL error').join('; '));
|
|
99
|
+
}
|
|
100
|
+
const calendar = payload == null || (_payload$data = payload.data) == null || (_payload$data = _payload$data.user) == null || (_payload$data = _payload$data.contributionsCollection) == null ? void 0 : _payload$data.contributionCalendar;
|
|
101
|
+
if (!calendar) {
|
|
102
|
+
throw new Error(`No contribution calendar returned for "${githubUser}".`);
|
|
103
|
+
}
|
|
104
|
+
const contributions = (calendar.weeks || []).flatMap(week => week.contributionDays || []).map(day => {
|
|
105
|
+
var _CONTRIBUTION_LEVEL_M;
|
|
106
|
+
return {
|
|
107
|
+
date: day.date,
|
|
108
|
+
count: day.contributionCount,
|
|
109
|
+
level: (_CONTRIBUTION_LEVEL_M = CONTRIBUTION_LEVEL_MAP[day.contributionLevel]) != null ? _CONTRIBUTION_LEVEL_M : 0
|
|
110
|
+
};
|
|
111
|
+
}).sort((a, b) => {
|
|
112
|
+
if (a.date < b.date) {
|
|
113
|
+
return -1;
|
|
114
|
+
}
|
|
115
|
+
if (a.date > b.date) {
|
|
116
|
+
return 1;
|
|
117
|
+
}
|
|
118
|
+
return 0;
|
|
119
|
+
});
|
|
120
|
+
const total = buildContributionTotals(contributions);
|
|
121
|
+
return {
|
|
122
|
+
total,
|
|
123
|
+
contributions,
|
|
124
|
+
countLabel: buildCountLabel(calendar.totalContributions || 0, range.from, range.to)
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
function parseLinkHeader(header) {
|
|
2
|
+
if (!header) return {};
|
|
3
|
+
return header.split(',').reduce((links, part) => {
|
|
4
|
+
const match = part.match(/<(.+)>;\s*rel="([\w]+)"/);
|
|
5
|
+
if (match) {
|
|
6
|
+
const [, url, rel] = match;
|
|
7
|
+
if (rel === 'next' || rel === 'last') {
|
|
8
|
+
links[rel] = url;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
return links;
|
|
12
|
+
}, {});
|
|
13
|
+
}
|
|
14
|
+
export async function githubEventsQuery({
|
|
15
|
+
query,
|
|
16
|
+
githubUser,
|
|
17
|
+
githubToken
|
|
18
|
+
}) {
|
|
19
|
+
try {
|
|
20
|
+
const {
|
|
21
|
+
page = query.page || 1,
|
|
22
|
+
per_page = query.per_page || 100,
|
|
23
|
+
repo,
|
|
24
|
+
action,
|
|
25
|
+
date,
|
|
26
|
+
description
|
|
27
|
+
} = query;
|
|
28
|
+
const pageNum = Number(page);
|
|
29
|
+
const perPage = Number(per_page);
|
|
30
|
+
console.log(`Fetching events for user: ${githubUser}, page: ${pageNum}, per_page: ${perPage}`);
|
|
31
|
+
let allEvents = [];
|
|
32
|
+
let hasMore = true;
|
|
33
|
+
let githubPage = 1;
|
|
34
|
+
const maxPages = 30;
|
|
35
|
+
const fetchPerPage = 100;
|
|
36
|
+
while (hasMore && githubPage <= maxPages) {
|
|
37
|
+
console.log(`Fetching page ${githubPage}...`);
|
|
38
|
+
const fetchOptions = {
|
|
39
|
+
headers: {
|
|
40
|
+
'User-Agent': 'brianstoker.com-website'
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
if (githubToken) {
|
|
44
|
+
fetchOptions.headers.Authorization = `token ${githubToken}`;
|
|
45
|
+
}
|
|
46
|
+
const queryParams = new URLSearchParams({
|
|
47
|
+
page: String(githubPage),
|
|
48
|
+
per_page: String(fetchPerPage)
|
|
49
|
+
});
|
|
50
|
+
const response = await fetch(`https://api.github.com/users/${githubUser}/events?${queryParams}`, fetchOptions);
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const errorText = await response.text();
|
|
53
|
+
console.error(`GitHub API error: ${response.status}`, errorText);
|
|
54
|
+
throw new Error(`GitHub API error: ${response.status} - ${errorText}`);
|
|
55
|
+
}
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
console.log(`Page ${githubPage}: Received ${data.length} events`);
|
|
58
|
+
if (data.length === 0) {
|
|
59
|
+
hasMore = false;
|
|
60
|
+
} else {
|
|
61
|
+
allEvents = [...allEvents, ...data];
|
|
62
|
+
console.log(`Total events so far: ${allEvents.length}`);
|
|
63
|
+
const linkHeader = response.headers.get('Link');
|
|
64
|
+
const links = parseLinkHeader(linkHeader);
|
|
65
|
+
hasMore = !!links.next;
|
|
66
|
+
githubPage++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
allEvents = Array.from(new Map(allEvents.map(event => [event.id, event])).values());
|
|
70
|
+
console.log(`Final total events: ${allEvents.length}`);
|
|
71
|
+
let filteredEvents = allEvents;
|
|
72
|
+
if (repo) {
|
|
73
|
+
filteredEvents = filteredEvents.filter(event => event.repo.name === repo);
|
|
74
|
+
}
|
|
75
|
+
if (action) {
|
|
76
|
+
filteredEvents = filteredEvents.filter(event => event.type.replace('Event', '') === action);
|
|
77
|
+
}
|
|
78
|
+
if (date) {
|
|
79
|
+
const now = new Date();
|
|
80
|
+
let cutoffDate;
|
|
81
|
+
switch (date) {
|
|
82
|
+
case 'today':
|
|
83
|
+
cutoffDate = new Date(now.setHours(0, 0, 0, 0));
|
|
84
|
+
break;
|
|
85
|
+
case 'yesterday':
|
|
86
|
+
cutoffDate = new Date(now);
|
|
87
|
+
cutoffDate.setDate(cutoffDate.getDate() - 1);
|
|
88
|
+
cutoffDate.setHours(0, 0, 0, 0);
|
|
89
|
+
break;
|
|
90
|
+
case 'week':
|
|
91
|
+
cutoffDate = new Date(now);
|
|
92
|
+
cutoffDate.setDate(cutoffDate.getDate() - 7);
|
|
93
|
+
cutoffDate.setHours(0, 0, 0, 0);
|
|
94
|
+
break;
|
|
95
|
+
case 'month':
|
|
96
|
+
cutoffDate = new Date(now);
|
|
97
|
+
cutoffDate.setMonth(cutoffDate.getMonth() - 1);
|
|
98
|
+
cutoffDate.setHours(0, 0, 0, 0);
|
|
99
|
+
break;
|
|
100
|
+
default:
|
|
101
|
+
cutoffDate = new Date(0);
|
|
102
|
+
}
|
|
103
|
+
filteredEvents = filteredEvents.filter(event => new Date(event.created_at) >= cutoffDate);
|
|
104
|
+
}
|
|
105
|
+
if (description) {
|
|
106
|
+
filteredEvents = filteredEvents.filter(event => {
|
|
107
|
+
var _event$payload$commit, _event$payload$pull_r, _event$payload$issue, _event$payload$issue2;
|
|
108
|
+
let eventDescription = '';
|
|
109
|
+
if (event.type === 'PushEvent' && (_event$payload$commit = event.payload.commits) != null && _event$payload$commit.length) {
|
|
110
|
+
eventDescription = `Pushed ${event.payload.commits.length} commits`;
|
|
111
|
+
} else if (event.type === 'PullRequestEvent' && (_event$payload$pull_r = event.payload.pull_request) != null && _event$payload$pull_r.title) {
|
|
112
|
+
eventDescription = event.payload.pull_request.title;
|
|
113
|
+
} else if (event.type === 'IssuesEvent' && (_event$payload$issue = event.payload.issue) != null && _event$payload$issue.title) {
|
|
114
|
+
eventDescription = event.payload.issue.title;
|
|
115
|
+
} else if (event.type === 'IssueCommentEvent' && (_event$payload$issue2 = event.payload.issue) != null && _event$payload$issue2.title) {
|
|
116
|
+
eventDescription = `Commented on issue: ${event.payload.issue.title}`;
|
|
117
|
+
}
|
|
118
|
+
return eventDescription.toLowerCase().includes(description.toLowerCase());
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const startIndex = (pageNum - 1) * perPage;
|
|
122
|
+
const endIndex = startIndex + perPage;
|
|
123
|
+
const paginatedEvents = filteredEvents.slice(startIndex, endIndex);
|
|
124
|
+
const repositories = Array.from(new Set(allEvents.map(event => event.repo.name))).sort();
|
|
125
|
+
const actionTypes = Array.from(new Set(allEvents.map(event => event.type.replace('Event', '')))).sort();
|
|
126
|
+
return {
|
|
127
|
+
events: paginatedEvents,
|
|
128
|
+
total: filteredEvents.length,
|
|
129
|
+
repositories,
|
|
130
|
+
actionTypes,
|
|
131
|
+
page: pageNum,
|
|
132
|
+
per_page: perPage,
|
|
133
|
+
total_pages: Math.ceil(filteredEvents.length / perPage),
|
|
134
|
+
total_fetched_events: allEvents.length,
|
|
135
|
+
max_pages_fetched: githubPage - 1
|
|
136
|
+
};
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error('Error fetching GitHub events:', error);
|
|
139
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
export default githubEventsQuery;
|
|
@@ -37,7 +37,7 @@ export default async function getPullRequestDetails(params) {
|
|
|
37
37
|
if (githubToken) {
|
|
38
38
|
headers.Authorization = `token ${githubToken}`;
|
|
39
39
|
}
|
|
40
|
-
async
|
|
40
|
+
const fetchWithRateLimit = async url => {
|
|
41
41
|
const response = await fetch(url, {
|
|
42
42
|
headers
|
|
43
43
|
});
|
|
@@ -59,7 +59,7 @@ export default async function getPullRequestDetails(params) {
|
|
|
59
59
|
data: await response.json(),
|
|
60
60
|
rateLimit
|
|
61
61
|
};
|
|
62
|
-
}
|
|
62
|
+
};
|
|
63
63
|
|
|
64
64
|
// Fetch basic PR information
|
|
65
65
|
const {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const GITHUB_API_BASE_URL = 'https://api.github.com';
|
|
2
|
+
const DEFAULT_DIFF_LINE_LIMIT = 24;
|
|
3
|
+
function getGithubHeaders() {
|
|
4
|
+
const githubToken = process.env.GITHUB_TOKEN;
|
|
5
|
+
const headers = {
|
|
6
|
+
'User-Agent': 'stoked-ui-github-components',
|
|
7
|
+
Accept: 'application/vnd.github+json'
|
|
8
|
+
};
|
|
9
|
+
if (githubToken) {
|
|
10
|
+
headers.Authorization = `token ${githubToken}`;
|
|
11
|
+
}
|
|
12
|
+
return headers;
|
|
13
|
+
}
|
|
14
|
+
export async function fetchGithubResource(path) {
|
|
15
|
+
const response = await fetch(`${GITHUB_API_BASE_URL}${path}`, {
|
|
16
|
+
headers: getGithubHeaders()
|
|
17
|
+
});
|
|
18
|
+
const rateLimit = {
|
|
19
|
+
remaining: response.headers.get('x-ratelimit-remaining'),
|
|
20
|
+
reset: response.headers.get('x-ratelimit-reset')
|
|
21
|
+
};
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
const body = await response.text();
|
|
24
|
+
if (response.status === 403 && rateLimit.remaining === '0') {
|
|
25
|
+
const resetDate = rateLimit.reset ? new Date(Number(rateLimit.reset) * 1000).toLocaleString() : 'later';
|
|
26
|
+
throw new Error(`GitHub rate limit exceeded. Resets at ${resetDate}.`);
|
|
27
|
+
}
|
|
28
|
+
throw new Error(body || `GitHub API error: ${response.status}`);
|
|
29
|
+
}
|
|
30
|
+
return response.json();
|
|
31
|
+
}
|
|
32
|
+
export function parseGithubDiff(patch, maxLines = DEFAULT_DIFF_LINE_LIMIT) {
|
|
33
|
+
if (!patch) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
const patchLines = patch.split('\n');
|
|
37
|
+
const visibleLines = patchLines.slice(0, maxLines);
|
|
38
|
+
const diffLines = visibleLines.map((line, index) => {
|
|
39
|
+
let type = 'context';
|
|
40
|
+
if (line.startsWith('+')) {
|
|
41
|
+
type = 'addition';
|
|
42
|
+
} else if (line.startsWith('-')) {
|
|
43
|
+
type = 'deletion';
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
type,
|
|
47
|
+
content: line,
|
|
48
|
+
lineNumber: index + 1
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
if (patchLines.length > maxLines) {
|
|
52
|
+
diffLines.push({
|
|
53
|
+
type: 'context',
|
|
54
|
+
content: `... ${patchLines.length - maxLines} more diff lines`,
|
|
55
|
+
lineNumber: diffLines.length + 1
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return diffLines;
|
|
59
|
+
}
|
|
60
|
+
function toFileChangeType(status) {
|
|
61
|
+
switch (status) {
|
|
62
|
+
case 'added':
|
|
63
|
+
return 'added';
|
|
64
|
+
case 'removed':
|
|
65
|
+
return 'deleted';
|
|
66
|
+
default:
|
|
67
|
+
return 'modified';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function normalizeGithubFile(file, maxDiffLines = DEFAULT_DIFF_LINE_LIMIT) {
|
|
71
|
+
return {
|
|
72
|
+
path: file.filename || file.path || 'unknown',
|
|
73
|
+
type: toFileChangeType(file.status),
|
|
74
|
+
additions: file.additions || 0,
|
|
75
|
+
deletions: file.deletions || 0,
|
|
76
|
+
diff: file.diff || parseGithubDiff(file.patch, maxDiffLines)
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function getGithubIdentity(commit) {
|
|
80
|
+
var _commit$commit, _commit$commit2;
|
|
81
|
+
const apiAuthor = commit.author || commit.committer || {};
|
|
82
|
+
const commitAuthor = ((_commit$commit = commit.commit) == null ? void 0 : _commit$commit.author) || ((_commit$commit2 = commit.commit) == null ? void 0 : _commit$commit2.committer) || {};
|
|
83
|
+
return {
|
|
84
|
+
login: apiAuthor.login || commitAuthor.name || commitAuthor.email || commit.sha || 'unknown',
|
|
85
|
+
name: commitAuthor.name || apiAuthor.login || commitAuthor.email || 'Unknown author',
|
|
86
|
+
avatarUrl: apiAuthor.avatar_url || ''
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function normalizeGithubCommit(commit) {
|
|
90
|
+
var _commit$commit3, _commit$commit4, _commit$commit5;
|
|
91
|
+
const identity = getGithubIdentity(commit);
|
|
92
|
+
return {
|
|
93
|
+
id: commit.sha || commit.node_id || identity.login,
|
|
94
|
+
message: ((_commit$commit3 = commit.commit) == null ? void 0 : _commit$commit3.message) || '',
|
|
95
|
+
author: {
|
|
96
|
+
name: identity.name,
|
|
97
|
+
login: identity.login,
|
|
98
|
+
avatar: identity.avatarUrl
|
|
99
|
+
},
|
|
100
|
+
date: ((_commit$commit4 = commit.commit) == null || (_commit$commit4 = _commit$commit4.author) == null ? void 0 : _commit$commit4.date) || ((_commit$commit5 = commit.commit) == null || (_commit$commit5 = _commit$commit5.committer) == null ? void 0 : _commit$commit5.date) || '',
|
|
101
|
+
hash: commit.sha || '',
|
|
102
|
+
url: commit.html_url || ''
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
export function buildGithubContributors(commits) {
|
|
106
|
+
const contributors = new Map();
|
|
107
|
+
commits.forEach(commit => {
|
|
108
|
+
const identity = getGithubIdentity(commit);
|
|
109
|
+
const key = identity.login || identity.name;
|
|
110
|
+
const current = contributors.get(key);
|
|
111
|
+
if (current) {
|
|
112
|
+
current.contributions += 1;
|
|
113
|
+
if (!current.avatarUrl && identity.avatarUrl) {
|
|
114
|
+
current.avatarUrl = identity.avatarUrl;
|
|
115
|
+
}
|
|
116
|
+
if (current.name === 'Unknown author' && identity.name) {
|
|
117
|
+
current.name = identity.name;
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
contributors.set(key, {
|
|
122
|
+
login: identity.login,
|
|
123
|
+
name: identity.name,
|
|
124
|
+
avatarUrl: identity.avatarUrl,
|
|
125
|
+
contributions: 1
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
return Array.from(contributors.values()).sort((a, b) => {
|
|
129
|
+
if (b.contributions !== a.contributions) {
|
|
130
|
+
return b.contributions - a.contributions;
|
|
131
|
+
}
|
|
132
|
+
return a.login.localeCompare(b.login);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
export function summarizeGithubStats(files) {
|
|
136
|
+
return files.reduce((stats, file) => ({
|
|
137
|
+
additions: stats.additions + file.additions,
|
|
138
|
+
deletions: stats.deletions + file.deletions,
|
|
139
|
+
changedFiles: stats.changedFiles + 1
|
|
140
|
+
}), {
|
|
141
|
+
additions: 0,
|
|
142
|
+
deletions: 0,
|
|
143
|
+
changedFiles: 0
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
export function getGithubMessageSummary(message) {
|
|
147
|
+
var _message$split$;
|
|
148
|
+
return ((_message$split$ = message.split('\n')[0]) == null ? void 0 : _message$split$.trim()) || 'Untitled commit';
|
|
149
|
+
}
|
|
150
|
+
export function getGithubShortRef(ref, length = 7) {
|
|
151
|
+
if (!ref) {
|
|
152
|
+
return '';
|
|
153
|
+
}
|
|
154
|
+
return ref.length > length ? ref.slice(0, length) : ref;
|
|
155
|
+
}
|
|
@@ -1 +1,9 @@
|
|
|
1
|
-
export { default as
|
|
1
|
+
export { default as createGithubBranchHandler } from './createGithubBranchHandler';
|
|
2
|
+
export { default as createGithubCommitHandler } from './createGithubCommitHandler';
|
|
3
|
+
export { default as getBranchCompareDetails } from './getBranchCompareDetails';
|
|
4
|
+
export { default as getCommitDetails } from './getCommitDetails';
|
|
5
|
+
export { default as getPullRequestDetails } from './getPullRequestDetails';
|
|
6
|
+
export { default as getGithubContributions } from './getGithubContributions';
|
|
7
|
+
export { default as createGithubContributionsHandler } from './createGithubContributionsHandler';
|
|
8
|
+
export { default as getGithubEvents, githubEventsQuery } from './getGithubEvents';
|
|
9
|
+
export { default as createGithubEventsHandler } from './createGithubEventsHandler';
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import Avatar from '@mui/material/Avatar';
|
|
3
|
+
import Box from '@mui/material/Box';
|
|
4
|
+
import Chip from '@mui/material/Chip';
|
|
5
|
+
import Typography from '@mui/material/Typography';
|
|
6
|
+
import { styled } from '@mui/material/styles';
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
const ContributorCard = styled(Box)(({
|
|
9
|
+
theme
|
|
10
|
+
}) => ({
|
|
11
|
+
display: 'flex',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
gap: theme.spacing(1.5),
|
|
14
|
+
padding: theme.spacing(1.5),
|
|
15
|
+
border: `1px solid ${theme.palette.divider}`,
|
|
16
|
+
borderRadius: theme.shape.borderRadius,
|
|
17
|
+
backgroundColor: theme.palette.background.paper
|
|
18
|
+
}));
|
|
19
|
+
export default function GithubContributorsList({
|
|
20
|
+
contributors,
|
|
21
|
+
title
|
|
22
|
+
}) {
|
|
23
|
+
if (!contributors.length) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return /*#__PURE__*/_jsxs(Box, {
|
|
27
|
+
sx: {
|
|
28
|
+
mb: 3
|
|
29
|
+
},
|
|
30
|
+
children: [/*#__PURE__*/_jsx(Typography, {
|
|
31
|
+
variant: "subtitle2",
|
|
32
|
+
color: "text.secondary",
|
|
33
|
+
sx: {
|
|
34
|
+
mb: 1.5
|
|
35
|
+
},
|
|
36
|
+
children: title
|
|
37
|
+
}), /*#__PURE__*/_jsx(Box, {
|
|
38
|
+
sx: {
|
|
39
|
+
display: 'flex',
|
|
40
|
+
flexWrap: 'wrap',
|
|
41
|
+
gap: 1.5
|
|
42
|
+
},
|
|
43
|
+
children: contributors.map(contributor => /*#__PURE__*/_jsxs(ContributorCard, {
|
|
44
|
+
children: [/*#__PURE__*/_jsx(Avatar, {
|
|
45
|
+
src: contributor.avatarUrl,
|
|
46
|
+
alt: contributor.login,
|
|
47
|
+
sx: {
|
|
48
|
+
width: 40,
|
|
49
|
+
height: 40
|
|
50
|
+
}
|
|
51
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
|
52
|
+
sx: {
|
|
53
|
+
minWidth: 0
|
|
54
|
+
},
|
|
55
|
+
children: [/*#__PURE__*/_jsx(Typography, {
|
|
56
|
+
variant: "body2",
|
|
57
|
+
fontWeight: 600,
|
|
58
|
+
children: contributor.login
|
|
59
|
+
}), contributor.name && contributor.name !== contributor.login ? /*#__PURE__*/_jsx(Typography, {
|
|
60
|
+
variant: "caption",
|
|
61
|
+
color: "text.secondary",
|
|
62
|
+
children: contributor.name
|
|
63
|
+
}) : null]
|
|
64
|
+
}), /*#__PURE__*/_jsx(Chip, {
|
|
65
|
+
label: `${contributor.contributions} commit${contributor.contributions === 1 ? '' : 's'}`,
|
|
66
|
+
size: "small",
|
|
67
|
+
variant: "outlined"
|
|
68
|
+
})]
|
|
69
|
+
}, `${contributor.login}-${contributor.name}`))
|
|
70
|
+
})]
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default async function fetchGithubViewData(apiUrl, params) {
|
|
2
|
+
const query = new URLSearchParams(params);
|
|
3
|
+
const separator = apiUrl.includes('?') ? '&' : '?';
|
|
4
|
+
const response = await fetch(`${apiUrl}${separator}${query.toString()}`);
|
|
5
|
+
if (!response.ok) {
|
|
6
|
+
const errorBody = await response.json().catch(() => null);
|
|
7
|
+
throw new Error((errorBody == null ? void 0 : errorBody.message) || 'Failed to fetch GitHub data');
|
|
8
|
+
}
|
|
9
|
+
return response.json();
|
|
10
|
+
}
|
package/modern/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @stoked-ui/github v0.
|
|
2
|
+
* @stoked-ui/github v0.1.0-alpha.11.2
|
|
3
3
|
*
|
|
4
4
|
* @license MIT
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
export {
|
|
8
|
+
export { default as GithubBranch } from './GithubBranch';
|
|
9
|
+
export { default as GithubCalendar } from './GithubCalendar';
|
|
10
|
+
export { default as GithubCommit } from './GithubCommit';
|
|
11
|
+
export { default as GithubEvents } from './GithubEvents';
|
|
12
|
+
export { createGithubBranchHandler, createGithubCommitHandler, createGithubContributionsHandler, createGithubEventsHandler, getBranchCompareDetails, getCommitDetails, getGithubContributions, getGithubEvents, getPullRequestDetails, githubEventsQuery } from './apiHandlers';
|