Files
oneuptime/Home/Jobs/FetchGitHubStats.ts

171 lines
5.3 KiB
TypeScript

import { EVERY_HOUR, EVERY_FIVE_MINUTE } from "Common/Utils/CronTime";
import { IsDevelopment } from "Common/Server/EnvironmentConfig";
import BasicCron from "Common/Server/Utils/BasicCron";
import logger from "Common/Server/Utils/Logger";
import LocalCache from "Common/Server/Infrastructure/LocalCache";
import URL from "Common/Types/API/URL";
import API from "Common/Utils/API";
import HTTPResponse from "Common/Types/API/HTTPResponse";
import HTTPErrorResponse from "Common/Types/API/HTTPErrorResponse";
import { JSONObject } from "Common/Types/JSON";
const GITHUB_STATS_NAMESPACE: string = "home";
const GITHUB_CONTRIBUTORS_CACHE_KEY: string = "githubContributors";
const GITHUB_COMMITS_CACHE_KEY: string = "githubCommits";
// Default fallback values (used if API fails)
const DEFAULT_CONTRIBUTORS: number = 80;
const DEFAULT_COMMITS: number = 29000;
export const getGitHubContributorsCount: () => number = (): number => {
if (
!LocalCache.hasValue(GITHUB_STATS_NAMESPACE, GITHUB_CONTRIBUTORS_CACHE_KEY)
) {
return DEFAULT_CONTRIBUTORS;
}
return (
LocalCache.getNumber(
GITHUB_STATS_NAMESPACE,
GITHUB_CONTRIBUTORS_CACHE_KEY,
) || DEFAULT_CONTRIBUTORS
);
};
export const getGitHubCommitsCount: () => number = (): number => {
if (!LocalCache.hasValue(GITHUB_STATS_NAMESPACE, GITHUB_COMMITS_CACHE_KEY)) {
return DEFAULT_COMMITS;
}
return (
LocalCache.getNumber(GITHUB_STATS_NAMESPACE, GITHUB_COMMITS_CACHE_KEY) ||
DEFAULT_COMMITS
);
};
export const formatCount: (count: number) => string = (
count: number,
): string => {
if (count >= 1000) {
const thousands: number = Math.ceil(count / 1000);
return `${thousands}k+`;
}
return `${count}+`;
};
const fetchGitHubStats: () => Promise<void> = async (): Promise<void> => {
try {
// Fetch contributors count
let contributorsCount: number = 0;
let hasMoreContributors: boolean = true;
let pageNumber: number = 1;
while (hasMoreContributors) {
const response: HTTPResponse<Array<JSONObject>> | HTTPErrorResponse =
await API.get<Array<JSONObject>>({
url: URL.fromString(
`https://api.github.com/repos/oneuptime/oneuptime/contributors?per_page=100&page=${pageNumber}`,
),
headers: {
Accept: "application/vnd.github.v3+json",
"User-Agent": "OneUptime-Home",
},
});
if (response instanceof HTTPErrorResponse) {
logger.error("FetchGitHubStats: Failed to fetch contributors");
break;
}
const contributors: Array<JSONObject> =
response.data as Array<JSONObject>;
contributorsCount += contributors.length;
if (contributors.length < 100) {
hasMoreContributors = false;
}
pageNumber++;
// Safety limit to prevent infinite loops
if (pageNumber > 50) {
hasMoreContributors = false;
}
}
if (contributorsCount > 0) {
LocalCache.setNumber(
GITHUB_STATS_NAMESPACE,
GITHUB_CONTRIBUTORS_CACHE_KEY,
contributorsCount,
);
logger.debug(
`FetchGitHubStats: Updated contributors count to ${contributorsCount}`,
);
}
// Fetch commits count using pagination header
const commitsResponse: HTTPResponse<Array<JSONObject>> | HTTPErrorResponse =
await API.get<Array<JSONObject>>({
url: URL.fromString(
"https://api.github.com/repos/oneuptime/oneuptime/commits?sha=master&per_page=1&page=1",
),
headers: {
Accept: "application/vnd.github.v3+json",
"User-Agent": "OneUptime-Home",
},
});
if (!(commitsResponse instanceof HTTPErrorResponse)) {
/*
* Parse the Link header to get total commits count
* Format: '<https://api.github.com/repositories/xxx/commits?sha=master&per_page=1&page=2>; rel="next", <https://api.github.com/repositories/xxx/commits?sha=master&per_page=1&page=22486>; rel="last"'
*/
const link: string | undefined = commitsResponse.headers["link"];
if (link) {
const lastLink: string | undefined = link
.split(",")
.find((s: string) => {
return s.includes('rel="last"');
});
if (lastLink) {
const urlMatch: RegExpMatchArray | null = lastLink.match(/<([^>]+)>/);
if (urlMatch && urlMatch[1]) {
const url: URL = URL.fromString(urlMatch[1]);
const commitsCount: number = Number.parseInt(
url.getQueryParam("page") as string,
10,
);
if (commitsCount > 0) {
LocalCache.setNumber(
GITHUB_STATS_NAMESPACE,
GITHUB_COMMITS_CACHE_KEY,
commitsCount,
);
logger.debug(
`FetchGitHubStats: Updated commits count to ${commitsCount}`,
);
}
}
}
}
}
} catch (error) {
logger.error("FetchGitHubStats: Error fetching GitHub stats");
logger.error(error);
}
};
BasicCron({
jobName: "Home:FetchGitHubStats",
options: {
schedule: IsDevelopment ? EVERY_FIVE_MINUTE : EVERY_HOUR,
runOnStartup: true,
},
runFunction: async () => {
logger.debug("FetchGitHubStats: Start");
await fetchGitHubStats();
logger.debug("FetchGitHubStats: End");
},
});