feat: make redis optional, fall back to less consistent local cache
This commit is contained in:
parent
6027c8271d
commit
75ba80e63c
1
deps.ts
1
deps.ts
|
@ -2,3 +2,4 @@ export * as http from "https://deno.land/std@0.154.0/http/mod.ts";
|
|||
export * as log from "https://deno.land/std@0.154.0/log/mod.ts";
|
||||
export * as hex from "https://deno.land/std@0.154.0/encoding/hex.ts";
|
||||
export * as redis from "https://deno.land/x/redis@v0.27.0/mod.ts";
|
||||
export { default as TTLCache } from "https://deno.land/x/ttl@1.0.1/mod.ts";
|
||||
|
|
|
@ -17,7 +17,7 @@ export default {
|
|||
maxWebhookRetries: parseInt(get("MAX_RETRIES", "3")),
|
||||
maxWebhookRetryMs: parseInt(get("MAX_RETRY_MS", "30000")),
|
||||
mainRedirect: get("MAIN_REDIRECT", null),
|
||||
redisUrl: get("REDIS_URL"),
|
||||
redisUrl: get("REDIS_URL", null),
|
||||
|
||||
// set by deno deploy
|
||||
deployId: get("DENO_DEPLOYMENT_ID", "<unknown>"),
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { reviewCommentManager } from "./manager.ts";
|
||||
import { commentManager } from "./manager.ts";
|
||||
import { UrlConfig } from "./types.d.ts";
|
||||
import { requestLog } from "./util.ts";
|
||||
|
||||
|
@ -36,21 +36,25 @@ export default async function filter(
|
|||
if (event === "pull_request_review") {
|
||||
// ignore edit/dismiss actions
|
||||
if (json.action !== "submitted") return `no-op PR review action '${json.action}'`;
|
||||
|
||||
// if comment (not approval or changes requested), ignore empty review body
|
||||
else if (json.review?.state === "commented" && !json.review?.body) return "empty PR review";
|
||||
if (json.review?.state === "commented" && !json.review?.body) return "empty PR review";
|
||||
}
|
||||
|
||||
// ignore some PR comment events
|
||||
if (event === "pull_request_review_comment") {
|
||||
// ignore edit/delete actions
|
||||
if (json.action !== "created") return `no-op PR comment action '${json.action}'`;
|
||||
|
||||
// check if more than x comments on a PR review in a short timespan
|
||||
const reviewId: number = json.comment?.pull_request_review_id;
|
||||
if (config.commentBurstLimit && reviewId) {
|
||||
const cacheKey = `${reviewId}-${login}`;
|
||||
|
||||
reqLog.debug(`filter: checking cache key ${cacheKey}`);
|
||||
const curr = await reviewCommentManager.getAndIncrement(cacheKey);
|
||||
const curr = await commentManager.getAndIncrement(cacheKey);
|
||||
reqLog.debug(`filter: current value: ${curr}`);
|
||||
|
||||
if (curr && curr >= config.commentBurstLimit) {
|
||||
return `exceeded comment burst limit (${config.commentBurstLimit}) for review ${reviewId}`;
|
||||
}
|
||||
|
|
|
@ -1,23 +1,45 @@
|
|||
import { redis } from "../deps.ts";
|
||||
import { redis, TTLCache } from "../deps.ts";
|
||||
import config from "./config.ts";
|
||||
|
||||
class ReviewCommentManager {
|
||||
private redis: redis.Redis;
|
||||
const KEY_EXPIRY = 3; // seconds
|
||||
|
||||
interface CommentManager {
|
||||
getAndIncrement(key: string): Promise<number>;
|
||||
}
|
||||
|
||||
class LocalCommentManager implements CommentManager {
|
||||
private cache: TTLCache<number>;
|
||||
constructor() {
|
||||
this.cache = new TTLCache(KEY_EXPIRY * 1000);
|
||||
}
|
||||
|
||||
getAndIncrement(key: string): Promise<number> {
|
||||
const value = this.cache.get(key) ?? 0;
|
||||
this.cache.set(key, value + 1);
|
||||
return Promise.resolve(value);
|
||||
}
|
||||
}
|
||||
|
||||
class RedisCommentManager implements CommentManager {
|
||||
private redis: redis.Redis;
|
||||
constructor(redisUrl: string) {
|
||||
this.redis = redis.createLazyClient({
|
||||
...redis.parseURL(config.redisUrl),
|
||||
...redis.parseURL(redisUrl),
|
||||
maxRetryCount: 3,
|
||||
});
|
||||
}
|
||||
|
||||
async getAndIncrement(key: string): Promise<number> {
|
||||
key = `reviewcomment:${key}`;
|
||||
|
||||
// seems like pipelines don't quite work with the lazy client, so force a connection
|
||||
if (!this.redis.isConnected) {
|
||||
// seems like pipelines don't quite work with the lazy client
|
||||
await this.redis.ping();
|
||||
}
|
||||
|
||||
const pl = this.redis.pipeline();
|
||||
pl.incr(`reviewcomment:${key}`);
|
||||
pl.expire(`reviewcomment:${key}`, 3);
|
||||
pl.incr(key);
|
||||
pl.expire(key, KEY_EXPIRY);
|
||||
const results = await pl.flush();
|
||||
|
||||
const newValue = results[0] as number;
|
||||
|
@ -25,4 +47,6 @@ class ReviewCommentManager {
|
|||
}
|
||||
}
|
||||
|
||||
export const reviewCommentManager = new ReviewCommentManager();
|
||||
export const commentManager = config.redisUrl
|
||||
? new RedisCommentManager(config.redisUrl)
|
||||
: new LocalCommentManager();
|
||||
|
|
Loading…
Reference in New Issue