fix: settings page lag (#736)
* feat: made a handler for github stats messages same way as the rest * fix: remove settingsPageLag through incremental rendering and efficient update of local storage * refactor: passed eslint * chore: added GitHubStats types * feat: added contributor card skeletons * refactor: pass eslint * feat: removed trickle rendering and added locking to setCachedData --------- Co-authored-by: Derek <derex1987@gmail.com> Co-authored-by: Diego Perez <52579214+doprz@users.noreply.github.com>
This commit is contained in:
@@ -1,37 +1,13 @@
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { CacheStore } from '@shared/storage/CacheStore';
|
||||
import type { CachedData } from '@shared/types/CachedData';
|
||||
|
||||
// Types
|
||||
type TeamMember = {
|
||||
name: string;
|
||||
role: string[];
|
||||
githubUsername: string;
|
||||
};
|
||||
|
||||
type GitHubStats = {
|
||||
commits: number;
|
||||
linesAdded: number;
|
||||
linesDeleted: number;
|
||||
mergedPRs?: number;
|
||||
};
|
||||
|
||||
type ContributorStats = {
|
||||
total: number;
|
||||
weeks: { w: number; a: number; d: number; c: number }[];
|
||||
author: { login: string };
|
||||
};
|
||||
|
||||
type ContributorUser = {
|
||||
name: string | undefined;
|
||||
};
|
||||
|
||||
type FetchResult<T> = {
|
||||
data: T;
|
||||
dataFetched: Date;
|
||||
lastUpdated: Date;
|
||||
isCached: boolean;
|
||||
};
|
||||
import type {
|
||||
ContributorStats,
|
||||
ContributorUser,
|
||||
FetchResult,
|
||||
GitHubStats,
|
||||
TeamMember,
|
||||
} from '@shared/types/GitHubStats';
|
||||
|
||||
// Constants
|
||||
const CACHE_TTL = 1 * 60 * 60 * 1000; // 1 hour in milliseconds
|
||||
@@ -91,6 +67,8 @@ export class GitHubStatsService {
|
||||
private octokit: Octokit;
|
||||
private cache: Record<string, CachedData<unknown>>;
|
||||
|
||||
private storageLock: Promise<void> = Promise.resolve();
|
||||
|
||||
constructor(githubToken?: string) {
|
||||
this.octokit = githubToken ? new Octokit({ auth: githubToken }) : new Octokit();
|
||||
this.cache = {} as Record<string, CachedData<unknown>>;
|
||||
@@ -114,16 +92,33 @@ export class GitHubStatsService {
|
||||
return null;
|
||||
}
|
||||
|
||||
private async setCachedData<T>(key: string, data: T): Promise<void> {
|
||||
if (Object.keys(this.cache).length === 0) {
|
||||
const githubCache = await CacheStore.get('github');
|
||||
if (githubCache && typeof githubCache === 'object') {
|
||||
this.cache = githubCache as Record<string, CachedData<unknown>>;
|
||||
}
|
||||
}
|
||||
private async setCachedData<T>(key: string, data: T, persist = true): Promise<void> {
|
||||
// get the current lock
|
||||
const existingLock = this.storageLock;
|
||||
|
||||
this.cache[key] = { data, dataFetched: Date.now() };
|
||||
await CacheStore.set('github', this.cache);
|
||||
// update the lock with a new promise
|
||||
this.storageLock = (async () => {
|
||||
// wait for current lock to finish
|
||||
await existingLock;
|
||||
|
||||
// ensure cache is loaded before modifying
|
||||
if (Object.keys(this.cache).length === 0) {
|
||||
const githubCache = await CacheStore.get('github');
|
||||
if (githubCache && typeof githubCache === 'object') {
|
||||
this.cache = githubCache as Record<string, CachedData<unknown>>;
|
||||
}
|
||||
}
|
||||
|
||||
// update local memory
|
||||
this.cache[key] = { data, dataFetched: Date.now() };
|
||||
|
||||
// only write to the physical storage API if persist is true
|
||||
if (persist) {
|
||||
await CacheStore.set('github', this.cache);
|
||||
}
|
||||
})();
|
||||
|
||||
return this.storageLock;
|
||||
}
|
||||
|
||||
private async fetchWithRetry<T>(fetchFn: () => Promise<T>, retries: number = 3, delay: number = 5000): Promise<T> {
|
||||
@@ -182,6 +177,7 @@ export class GitHubStatsService {
|
||||
|
||||
private async fetchContributorNames(contributors: string[]): Promise<Record<string, string>> {
|
||||
const names: Record<string, string> = {};
|
||||
|
||||
await Promise.all(
|
||||
contributors.map(async contributor => {
|
||||
const cacheKey = `contributor_name_${contributor}`;
|
||||
@@ -198,18 +194,17 @@ export class GitHubStatsService {
|
||||
if (data.name) {
|
||||
name = data.name;
|
||||
}
|
||||
await this.setCachedData(cacheKey, name);
|
||||
// Pass 'false' to avoid writing to disk for every single name
|
||||
await this.setCachedData(cacheKey, name, false);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
names[contributor] = name;
|
||||
})
|
||||
);
|
||||
return names;
|
||||
}
|
||||
|
||||
private async fetchMergedPRsCount(username: string): Promise<FetchResult<number>> {
|
||||
const cacheKey = `merged_prs_${username}`;
|
||||
const cachedCount = await this.getCachedData<number>(cacheKey);
|
||||
@@ -233,7 +228,7 @@ export class GitHubStatsService {
|
||||
lastUpdated: new Date(),
|
||||
isCached: false,
|
||||
};
|
||||
await this.setCachedData(cacheKey, fetchResult.data);
|
||||
await this.setCachedData(cacheKey, fetchResult.data, false);
|
||||
return fetchResult;
|
||||
}
|
||||
|
||||
@@ -300,6 +295,8 @@ export class GitHubStatsService {
|
||||
|
||||
const names = await this.fetchContributorNames(contributors);
|
||||
|
||||
await CacheStore.set('github', this.cache);
|
||||
|
||||
return {
|
||||
adminGitHubStats,
|
||||
userGitHubStats,
|
||||
|
||||
Reference in New Issue
Block a user