Skip to content

Commit

Permalink
feat: add request rate limmiter
Browse files Browse the repository at this point in the history
  • Loading branch information
AKharytonchyk committed Nov 2, 2024
1 parent 193cda7 commit 6afa1eb
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 32 deletions.
10 changes: 10 additions & 0 deletions src/components/RepositorySetupGuide.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import React from 'react';
import { Typography, Box } from '@mui/material';
import { Settings } from '@mui/icons-material';

const Outline: React.FC<{text: string}> = ({text}) => {
return (
<div>
<span>{text}</span>
</div>
)
}

const PATSetupGuide = () => {
return (
<>
Expand Down Expand Up @@ -34,6 +42,8 @@ const PATSetupGuide = () => {
<Typography paragraph sx={{marginTop: 2}}>
All the selection would be saved automatically in your browser. As soon as you select the repositories you would be able to see the pull requests if any are available.
</Typography>

<Outline text={"Note: \nABC"}/>
</Box>
</>
);
Expand Down
81 changes: 49 additions & 32 deletions src/service/gitService.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { Octokit } from "@octokit/rest";
import rateLimiter from "../utils/RateLimiterQueue";

export class GitService {
private readonly octokit: Octokit;
constructor(
private readonly baseUrl: string,
private readonly token: string
) {
constructor(baseUrl: string, token: string) {
this.octokit = new Octokit({
baseUrl,
auth: token,
});
}

getPulls(owner: string, repo: string) {
return this.octokit.pulls.list({ owner, repo, state: "open" });
return rateLimiter.enqueue(() => this.octokit.pulls.list({ owner, repo }));
}

async getPullRequests(repo: string) {
Expand All @@ -28,55 +26,74 @@ export class GitService {
}

getOrganizations() {
return this.octokit.orgs.listForAuthenticatedUser();
return rateLimiter.enqueue(() =>
this.octokit.orgs.listForAuthenticatedUser()
);
}

async getRepos(owner: string) {
const repos = await this.octokit.paginate(this.octokit.repos.listForOrg, {
org: owner,
per_page: 100,
timeout: 5000,
});
const repos = await rateLimiter.enqueue(() =>
this.octokit.paginate(this.octokit.repos.listForUser, {
username: owner,
per_page: 100,
timeout: 5000,
})
);

return repos
.filter((repo) => !repo.archived)
.sort((a, b) => a.name.localeCompare(b.name));
}

async getStaredRepos() {
return this.octokit.paginate(
this.octokit.activity.listReposStarredByAuthenticatedUser,
{ per_page: 100, timeout: 5000 }
return rateLimiter.enqueue(() =>
this.octokit.paginate(
this.octokit.activity.listReposStarredByAuthenticatedUser,
{ per_page: 100, timeout: 5000 }
)
);
}

async getUserRepos() {
return this.octokit.paginate(this.octokit.repos.listForAuthenticatedUser, {
per_page: 100,
timeout: 5000,
type: "owner",
});
return rateLimiter.enqueue(() =>
this.octokit.paginate(this.octokit.repos.listForAuthenticatedUser, {
per_page: 100,
timeout: 5000,
type: "owner",
})
);
}

async getPRChecksStatus(owner: string, repo: string, prNumber: number) {
return this.octokit.checks.listForRef({
owner,
repo,
ref: `pull/${prNumber}/head`,
filter: "latest",
});
return rateLimiter.enqueue(() =>
this.octokit.checks.listForRef({
owner,
repo,
ref: `pull/${prNumber}/head`,
filter: "latest",
})
);
}

async hasMergeConflict(owner: string, repo: string, prNumber: number) {
const mergeConflicts = await this.octokit.pulls.get({owner, repo, pull_number:prNumber});
return mergeConflicts.data
const mergeConflicts = await rateLimiter.enqueue(() =>
this.octokit.pulls.get({
owner,
repo,
pull_number: prNumber,
})
);
return mergeConflicts.data;
}

async getPRApprovals(owner: string, repo: string, prNumber: number) {
const reviews = await this.octokit.pulls.listReviews({
owner,
repo,
pull_number: prNumber,
});
const reviews = await rateLimiter.enqueue(() =>
this.octokit.pulls.listReviews({
owner,
repo,
pull_number: prNumber,
})
);

if (
!reviews.data ||
Expand Down
75 changes: 75 additions & 0 deletions src/utils/RateLimiterQueue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
class RateLimiterQueue {
private queue: any[];
private delay: number;
private isProcessing: boolean;
private loggingInterval: NodeJS.Timeout | null;

constructor(maxRequestsPerMinute: number) {
this.queue = [];
this.delay = 60000 / maxRequestsPerMinute;
this.isProcessing = false;

this.loggingInterval = setInterval(() => {
const statusMessage = this.isProcessing
? "Reached rate limit. Waiting to process next request."
: "Currently processing requests.";
console.log(`Queue size: ${this.queue.length}. Rate limit: ${maxRequestsPerMinute}. Estimated time: ${this.queue.length / maxRequestsPerMinute} minutes. ${statusMessage}`);
}, 60000);
}

async enqueue<T>(requestFunction: () => Promise<T>): Promise<T> {
return new Promise((resolve, reject) => {
this.queue.push(async () => {
try {
const result = await requestFunction();
resolve(result);
} catch (error) {
reject(error);
}
});
if (!this.isProcessing) {
this.processQueue();
}
});
}

private async processQueue() {
this.isProcessing = true;
while (this.queue.length > 0) {
console.debug(`Processing request. Queue size before: ${this.queue.length}`);
const requestFunction = this.queue.shift();
if (requestFunction) {
try {
await requestFunction();
console.debug(`Request processed. Queue size after: ${this.queue.length}`);
} catch (error) {
console.error("Error processing request:", error);
}
await this.sleep(this.delay);
}
}
this.isProcessing = false;
}

private sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async processAll<T>(requestFunctions: Array<() => Promise<T>>): Promise<T[]> {
const results = requestFunctions.map((requestFunction) => this.enqueue(requestFunction));
return Promise.all(results);
}

destroy() {
if (this.loggingInterval) {
clearInterval(this.loggingInterval);
this.loggingInterval = null;
}
}
}

const rateLimiter = new RateLimiterQueue(
isNaN(Number(process.env.MAX_REQUESTS_PER_MINUTE)) ? 200 : Number(process.env.MAX_REQUESTS_PER_MINUTE)
);

export default rateLimiter;

0 comments on commit 6afa1eb

Please sign in to comment.