mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-28 02:42:25 +08:00
feat: add actor-based comment filtering to GitHub data fetching (#812)
- Introduced `include_comments_by_actor` and `exclude_comments_by_actor` inputs in action.yml to allow filtering of comments based on actor usernames. - Updated context parsing to handle new input fields. - Implemented `filterCommentsByActor` function to filter comments according to specified inclusion and exclusion patterns. - Modified `fetchGitHubData` to apply actor filters when retrieving comments from pull requests and issues. - Added comprehensive tests for the new filtering functionality. This enhancement provides more control over which comments are processed based on the actor, improving the flexibility of the workflow.
This commit is contained in:
10
action.yml
10
action.yml
@@ -35,6 +35,14 @@ inputs:
|
|||||||
description: "Comma-separated list of usernames to allow without write permissions, or '*' to allow all users. Only works when github_token input is provided. WARNING: Use with extreme caution - this bypasses security checks and should only be used for workflows with very limited permissions (e.g., issue labeling)."
|
description: "Comma-separated list of usernames to allow without write permissions, or '*' to allow all users. Only works when github_token input is provided. WARNING: Use with extreme caution - this bypasses security checks and should only be used for workflows with very limited permissions (e.g., issue labeling)."
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
|
include_comments_by_actor:
|
||||||
|
description: "Comma-separated list of actor usernames to INCLUDE in comments. Supports wildcards: '*[bot]' matches all bots, 'dependabot[bot]' matches specific bot. Empty (default) includes all actors."
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
exclude_comments_by_actor:
|
||||||
|
description: "Comma-separated list of actor usernames to EXCLUDE from comments. Supports wildcards: '*[bot]' matches all bots, 'renovate[bot]' matches specific bot. Empty (default) excludes none. If actor is in both lists, exclusion takes priority."
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
|
||||||
# Claude Code configuration
|
# Claude Code configuration
|
||||||
prompt:
|
prompt:
|
||||||
@@ -186,6 +194,8 @@ runs:
|
|||||||
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
OVERRIDE_GITHUB_TOKEN: ${{ inputs.github_token }}
|
||||||
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
|
ALLOWED_BOTS: ${{ inputs.allowed_bots }}
|
||||||
ALLOWED_NON_WRITE_USERS: ${{ inputs.allowed_non_write_users }}
|
ALLOWED_NON_WRITE_USERS: ${{ inputs.allowed_non_write_users }}
|
||||||
|
INCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.include_comments_by_actor }}
|
||||||
|
EXCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.exclude_comments_by_actor }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||||
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
|
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
|
||||||
|
|||||||
@@ -98,6 +98,8 @@ type BaseContext = {
|
|||||||
allowedNonWriteUsers: string;
|
allowedNonWriteUsers: string;
|
||||||
trackProgress: boolean;
|
trackProgress: boolean;
|
||||||
includeFixLinks: boolean;
|
includeFixLinks: boolean;
|
||||||
|
includeCommentsByActor: string;
|
||||||
|
excludeCommentsByActor: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,6 +158,8 @@ export function parseGitHubContext(): GitHubContext {
|
|||||||
allowedNonWriteUsers: process.env.ALLOWED_NON_WRITE_USERS ?? "",
|
allowedNonWriteUsers: process.env.ALLOWED_NON_WRITE_USERS ?? "",
|
||||||
trackProgress: process.env.TRACK_PROGRESS === "true",
|
trackProgress: process.env.TRACK_PROGRESS === "true",
|
||||||
includeFixLinks: process.env.INCLUDE_FIX_LINKS === "true",
|
includeFixLinks: process.env.INCLUDE_FIX_LINKS === "true",
|
||||||
|
includeCommentsByActor: process.env.INCLUDE_COMMENTS_BY_ACTOR ?? "",
|
||||||
|
excludeCommentsByActor: process.env.EXCLUDE_COMMENTS_BY_ACTOR ?? "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ import type {
|
|||||||
} from "../types";
|
} from "../types";
|
||||||
import type { CommentWithImages } from "../utils/image-downloader";
|
import type { CommentWithImages } from "../utils/image-downloader";
|
||||||
import { downloadCommentImages } from "../utils/image-downloader";
|
import { downloadCommentImages } from "../utils/image-downloader";
|
||||||
|
import {
|
||||||
|
parseActorFilter,
|
||||||
|
shouldIncludeCommentByActor,
|
||||||
|
} from "../utils/actor-filter";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts the trigger timestamp from the GitHub webhook payload.
|
* Extracts the trigger timestamp from the GitHub webhook payload.
|
||||||
@@ -166,6 +170,35 @@ export function isBodySafeToUse(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters comments by actor username based on include/exclude patterns
|
||||||
|
* @param comments - Array of comments to filter
|
||||||
|
* @param includeActors - Comma-separated actors to include
|
||||||
|
* @param excludeActors - Comma-separated actors to exclude
|
||||||
|
* @returns Filtered array of comments
|
||||||
|
*/
|
||||||
|
export function filterCommentsByActor<T extends { author: { login: string } }>(
|
||||||
|
comments: T[],
|
||||||
|
includeActors: string = "",
|
||||||
|
excludeActors: string = "",
|
||||||
|
): T[] {
|
||||||
|
const includeParsed = parseActorFilter(includeActors);
|
||||||
|
const excludeParsed = parseActorFilter(excludeActors);
|
||||||
|
|
||||||
|
// No filters = return all
|
||||||
|
if (includeParsed.length === 0 && excludeParsed.length === 0) {
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return comments.filter((comment) =>
|
||||||
|
shouldIncludeCommentByActor(
|
||||||
|
comment.author.login,
|
||||||
|
includeParsed,
|
||||||
|
excludeParsed,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
type FetchDataParams = {
|
type FetchDataParams = {
|
||||||
octokits: Octokits;
|
octokits: Octokits;
|
||||||
repository: string;
|
repository: string;
|
||||||
@@ -174,6 +207,8 @@ type FetchDataParams = {
|
|||||||
triggerUsername?: string;
|
triggerUsername?: string;
|
||||||
triggerTime?: string;
|
triggerTime?: string;
|
||||||
originalTitle?: string;
|
originalTitle?: string;
|
||||||
|
includeCommentsByActor?: string;
|
||||||
|
excludeCommentsByActor?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GitHubFileWithSHA = GitHubFile & {
|
export type GitHubFileWithSHA = GitHubFile & {
|
||||||
@@ -198,6 +233,8 @@ export async function fetchGitHubData({
|
|||||||
triggerUsername,
|
triggerUsername,
|
||||||
triggerTime,
|
triggerTime,
|
||||||
originalTitle,
|
originalTitle,
|
||||||
|
includeCommentsByActor,
|
||||||
|
excludeCommentsByActor,
|
||||||
}: FetchDataParams): Promise<FetchDataResult> {
|
}: FetchDataParams): Promise<FetchDataResult> {
|
||||||
const [owner, repo] = repository.split("/");
|
const [owner, repo] = repository.split("/");
|
||||||
if (!owner || !repo) {
|
if (!owner || !repo) {
|
||||||
@@ -225,9 +262,13 @@ export async function fetchGitHubData({
|
|||||||
const pullRequest = prResult.repository.pullRequest;
|
const pullRequest = prResult.repository.pullRequest;
|
||||||
contextData = pullRequest;
|
contextData = pullRequest;
|
||||||
changedFiles = pullRequest.files.nodes || [];
|
changedFiles = pullRequest.files.nodes || [];
|
||||||
comments = filterCommentsToTriggerTime(
|
comments = filterCommentsByActor(
|
||||||
pullRequest.comments?.nodes || [],
|
filterCommentsToTriggerTime(
|
||||||
triggerTime,
|
pullRequest.comments?.nodes || [],
|
||||||
|
triggerTime,
|
||||||
|
),
|
||||||
|
includeCommentsByActor,
|
||||||
|
excludeCommentsByActor,
|
||||||
);
|
);
|
||||||
reviewData = pullRequest.reviews || [];
|
reviewData = pullRequest.reviews || [];
|
||||||
|
|
||||||
@@ -248,9 +289,13 @@ export async function fetchGitHubData({
|
|||||||
|
|
||||||
if (issueResult.repository.issue) {
|
if (issueResult.repository.issue) {
|
||||||
contextData = issueResult.repository.issue;
|
contextData = issueResult.repository.issue;
|
||||||
comments = filterCommentsToTriggerTime(
|
comments = filterCommentsByActor(
|
||||||
contextData?.comments?.nodes || [],
|
filterCommentsToTriggerTime(
|
||||||
triggerTime,
|
contextData?.comments?.nodes || [],
|
||||||
|
triggerTime,
|
||||||
|
),
|
||||||
|
includeCommentsByActor,
|
||||||
|
excludeCommentsByActor,
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(`Successfully fetched issue #${prNumber} data`);
|
console.log(`Successfully fetched issue #${prNumber} data`);
|
||||||
@@ -318,7 +363,27 @@ export async function fetchGitHubData({
|
|||||||
body: r.body,
|
body: r.body,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Filter review comments to trigger time
|
// Filter review comments to trigger time and by actor
|
||||||
|
if (reviewData && reviewData.nodes) {
|
||||||
|
// Filter reviews by actor
|
||||||
|
reviewData.nodes = filterCommentsByActor(
|
||||||
|
reviewData.nodes,
|
||||||
|
includeCommentsByActor,
|
||||||
|
excludeCommentsByActor,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also filter inline review comments within each review
|
||||||
|
reviewData.nodes.forEach((review) => {
|
||||||
|
if (review.comments?.nodes) {
|
||||||
|
review.comments.nodes = filterCommentsByActor(
|
||||||
|
review.comments.nodes,
|
||||||
|
includeCommentsByActor,
|
||||||
|
excludeCommentsByActor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const allReviewComments =
|
const allReviewComments =
|
||||||
reviewData?.nodes?.flatMap((r) => r.comments?.nodes ?? []) ?? [];
|
reviewData?.nodes?.flatMap((r) => r.comments?.nodes ?? []) ?? [];
|
||||||
const filteredReviewComments = filterCommentsToTriggerTime(
|
const filteredReviewComments = filterCommentsToTriggerTime(
|
||||||
|
|||||||
65
src/github/utils/actor-filter.ts
Normal file
65
src/github/utils/actor-filter.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* Parses actor filter string into array of patterns
|
||||||
|
* @param filterString - Comma-separated actor names (e.g., "user1,user2,*[bot]")
|
||||||
|
* @returns Array of actor patterns
|
||||||
|
*/
|
||||||
|
export function parseActorFilter(filterString: string): string[] {
|
||||||
|
if (!filterString.trim()) return [];
|
||||||
|
return filterString
|
||||||
|
.split(",")
|
||||||
|
.map((actor) => actor.trim())
|
||||||
|
.filter((actor) => actor.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if an actor matches a pattern
|
||||||
|
* Supports wildcards: "*[bot]" matches all bots, "dependabot[bot]" matches specific
|
||||||
|
* @param actor - Actor username to check
|
||||||
|
* @param pattern - Pattern to match against
|
||||||
|
* @returns true if actor matches pattern
|
||||||
|
*/
|
||||||
|
export function actorMatchesPattern(actor: string, pattern: string): boolean {
|
||||||
|
// Exact match
|
||||||
|
if (actor === pattern) return true;
|
||||||
|
|
||||||
|
// Wildcard bot pattern: "*[bot]" matches any username ending with [bot]
|
||||||
|
if (pattern === "*[bot]" && actor.endsWith("[bot]")) return true;
|
||||||
|
|
||||||
|
// No match
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a comment should be included based on actor filters
|
||||||
|
* @param actor - Comment author username
|
||||||
|
* @param includeActors - Array of actors to include (empty = include all)
|
||||||
|
* @param excludeActors - Array of actors to exclude (empty = exclude none)
|
||||||
|
* @returns true if comment should be included
|
||||||
|
*/
|
||||||
|
export function shouldIncludeCommentByActor(
|
||||||
|
actor: string,
|
||||||
|
includeActors: string[],
|
||||||
|
excludeActors: string[],
|
||||||
|
): boolean {
|
||||||
|
// Check exclusion first (exclusion takes priority)
|
||||||
|
if (excludeActors.length > 0) {
|
||||||
|
for (const pattern of excludeActors) {
|
||||||
|
if (actorMatchesPattern(actor, pattern)) {
|
||||||
|
return false; // Excluded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check inclusion
|
||||||
|
if (includeActors.length > 0) {
|
||||||
|
for (const pattern of includeActors) {
|
||||||
|
if (actorMatchesPattern(actor, pattern)) {
|
||||||
|
return true; // Explicitly included
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false; // Not in include list
|
||||||
|
}
|
||||||
|
|
||||||
|
// No filters or passed all checks
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -89,6 +89,8 @@ export const tagMode: Mode = {
|
|||||||
triggerUsername: context.actor,
|
triggerUsername: context.actor,
|
||||||
triggerTime,
|
triggerTime,
|
||||||
originalTitle,
|
originalTitle,
|
||||||
|
includeCommentsByActor: context.inputs.includeCommentsByActor,
|
||||||
|
excludeCommentsByActor: context.inputs.excludeCommentsByActor,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Setup branch
|
// Setup branch
|
||||||
|
|||||||
172
test/actor-filter.test.ts
Normal file
172
test/actor-filter.test.ts
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import {
|
||||||
|
parseActorFilter,
|
||||||
|
actorMatchesPattern,
|
||||||
|
shouldIncludeCommentByActor,
|
||||||
|
} from "../src/github/utils/actor-filter";
|
||||||
|
|
||||||
|
describe("parseActorFilter", () => {
|
||||||
|
test("parses comma-separated actors", () => {
|
||||||
|
expect(parseActorFilter("user1,user2,bot[bot]")).toEqual([
|
||||||
|
"user1",
|
||||||
|
"user2",
|
||||||
|
"bot[bot]",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles empty string", () => {
|
||||||
|
expect(parseActorFilter("")).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles whitespace-only string", () => {
|
||||||
|
expect(parseActorFilter(" ")).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("trims whitespace", () => {
|
||||||
|
expect(parseActorFilter(" user1 , user2 ")).toEqual(["user1", "user2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("filters out empty entries", () => {
|
||||||
|
expect(parseActorFilter("user1,,user2")).toEqual(["user1", "user2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles single actor", () => {
|
||||||
|
expect(parseActorFilter("user1")).toEqual(["user1"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles wildcard bot pattern", () => {
|
||||||
|
expect(parseActorFilter("*[bot]")).toEqual(["*[bot]"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("actorMatchesPattern", () => {
|
||||||
|
test("matches exact username", () => {
|
||||||
|
expect(actorMatchesPattern("john-doe", "john-doe")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not match different username", () => {
|
||||||
|
expect(actorMatchesPattern("john-doe", "jane-doe")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("matches wildcard bot pattern", () => {
|
||||||
|
expect(actorMatchesPattern("dependabot[bot]", "*[bot]")).toBe(true);
|
||||||
|
expect(actorMatchesPattern("renovate[bot]", "*[bot]")).toBe(true);
|
||||||
|
expect(actorMatchesPattern("github-actions[bot]", "*[bot]")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not match non-bot with wildcard", () => {
|
||||||
|
expect(actorMatchesPattern("john-doe", "*[bot]")).toBe(false);
|
||||||
|
expect(actorMatchesPattern("user-bot", "*[bot]")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("matches specific bot", () => {
|
||||||
|
expect(actorMatchesPattern("dependabot[bot]", "dependabot[bot]")).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(actorMatchesPattern("renovate[bot]", "renovate[bot]")).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("does not match different specific bot", () => {
|
||||||
|
expect(actorMatchesPattern("dependabot[bot]", "renovate[bot]")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("is case sensitive", () => {
|
||||||
|
expect(actorMatchesPattern("User1", "user1")).toBe(false);
|
||||||
|
expect(actorMatchesPattern("user1", "User1")).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("shouldIncludeCommentByActor", () => {
|
||||||
|
test("includes all when no filters", () => {
|
||||||
|
expect(shouldIncludeCommentByActor("user1", [], [])).toBe(true);
|
||||||
|
expect(shouldIncludeCommentByActor("bot[bot]", [], [])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("excludes when in exclude list", () => {
|
||||||
|
expect(shouldIncludeCommentByActor("bot[bot]", [], ["*[bot]"])).toBe(false);
|
||||||
|
expect(shouldIncludeCommentByActor("user1", [], ["user1"])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("includes when not in exclude list", () => {
|
||||||
|
expect(shouldIncludeCommentByActor("user1", [], ["user2"])).toBe(true);
|
||||||
|
expect(shouldIncludeCommentByActor("user1", [], ["*[bot]"])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("includes when in include list", () => {
|
||||||
|
expect(shouldIncludeCommentByActor("user1", ["user1", "user2"], [])).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(shouldIncludeCommentByActor("user2", ["user1", "user2"], [])).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("excludes when not in include list", () => {
|
||||||
|
expect(shouldIncludeCommentByActor("user3", ["user1", "user2"], [])).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("exclusion takes priority over inclusion", () => {
|
||||||
|
expect(shouldIncludeCommentByActor("user1", ["user1"], ["user1"])).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
shouldIncludeCommentByActor("bot[bot]", ["*[bot]"], ["*[bot]"]),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles wildcard in include list", () => {
|
||||||
|
expect(shouldIncludeCommentByActor("dependabot[bot]", ["*[bot]"], [])).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(shouldIncludeCommentByActor("renovate[bot]", ["*[bot]"], [])).toBe(
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
expect(shouldIncludeCommentByActor("user1", ["*[bot]"], [])).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles wildcard in exclude list", () => {
|
||||||
|
expect(shouldIncludeCommentByActor("dependabot[bot]", [], ["*[bot]"])).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(shouldIncludeCommentByActor("renovate[bot]", [], ["*[bot]"])).toBe(
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
expect(shouldIncludeCommentByActor("user1", [], ["*[bot]"])).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles mixed include and exclude lists", () => {
|
||||||
|
// Include user1 and user2, but exclude user2
|
||||||
|
expect(
|
||||||
|
shouldIncludeCommentByActor("user1", ["user1", "user2"], ["user2"]),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
shouldIncludeCommentByActor("user2", ["user1", "user2"], ["user2"]),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
shouldIncludeCommentByActor("user3", ["user1", "user2"], ["user2"]),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles complex bot filtering", () => {
|
||||||
|
// Include all bots but exclude dependabot
|
||||||
|
expect(
|
||||||
|
shouldIncludeCommentByActor(
|
||||||
|
"renovate[bot]",
|
||||||
|
["*[bot]"],
|
||||||
|
["dependabot[bot]"],
|
||||||
|
),
|
||||||
|
).toBe(true);
|
||||||
|
expect(
|
||||||
|
shouldIncludeCommentByActor(
|
||||||
|
"dependabot[bot]",
|
||||||
|
["*[bot]"],
|
||||||
|
["dependabot[bot]"],
|
||||||
|
),
|
||||||
|
).toBe(false);
|
||||||
|
expect(
|
||||||
|
shouldIncludeCommentByActor("user1", ["*[bot]"], ["dependabot[bot]"]),
|
||||||
|
).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, expect, it, jest } from "bun:test";
|
import { describe, expect, it, jest, test } from "bun:test";
|
||||||
import {
|
import {
|
||||||
extractTriggerTimestamp,
|
extractTriggerTimestamp,
|
||||||
extractOriginalTitle,
|
extractOriginalTitle,
|
||||||
@@ -1100,3 +1100,101 @@ describe("fetchGitHubData integration with time filtering", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("filterCommentsByActor", () => {
|
||||||
|
test("filters out excluded actors", () => {
|
||||||
|
const comments = [
|
||||||
|
{ author: { login: "user1" }, body: "comment1" },
|
||||||
|
{ author: { login: "bot[bot]" }, body: "comment2" },
|
||||||
|
{ author: { login: "user2" }, body: "comment3" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const { filterCommentsByActor } = require("../src/github/data/fetcher");
|
||||||
|
const filtered = filterCommentsByActor(comments, "", "*[bot]");
|
||||||
|
expect(filtered).toHaveLength(2);
|
||||||
|
expect(filtered.map((c: any) => c.author.login)).toEqual([
|
||||||
|
"user1",
|
||||||
|
"user2",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("includes only specified actors", () => {
|
||||||
|
const comments = [
|
||||||
|
{ author: { login: "user1" }, body: "comment1" },
|
||||||
|
{ author: { login: "user2" }, body: "comment2" },
|
||||||
|
{ author: { login: "user3" }, body: "comment3" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const { filterCommentsByActor } = require("../src/github/data/fetcher");
|
||||||
|
const filtered = filterCommentsByActor(comments, "user1,user2", "");
|
||||||
|
expect(filtered).toHaveLength(2);
|
||||||
|
expect(filtered.map((c: any) => c.author.login)).toEqual([
|
||||||
|
"user1",
|
||||||
|
"user2",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("returns all when no filters", () => {
|
||||||
|
const comments = [
|
||||||
|
{ author: { login: "user1" }, body: "comment1" },
|
||||||
|
{ author: { login: "user2" }, body: "comment2" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const { filterCommentsByActor } = require("../src/github/data/fetcher");
|
||||||
|
const filtered = filterCommentsByActor(comments, "", "");
|
||||||
|
expect(filtered).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("exclusion takes priority", () => {
|
||||||
|
const comments = [
|
||||||
|
{ author: { login: "user1" }, body: "comment1" },
|
||||||
|
{ author: { login: "user2" }, body: "comment2" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const { filterCommentsByActor } = require("../src/github/data/fetcher");
|
||||||
|
const filtered = filterCommentsByActor(comments, "user1,user2", "user1");
|
||||||
|
expect(filtered).toHaveLength(1);
|
||||||
|
expect(filtered[0].author.login).toBe("user2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("filters multiple bot types", () => {
|
||||||
|
const comments = [
|
||||||
|
{ author: { login: "user1" }, body: "comment1" },
|
||||||
|
{ author: { login: "dependabot[bot]" }, body: "comment2" },
|
||||||
|
{ author: { login: "renovate[bot]" }, body: "comment3" },
|
||||||
|
{ author: { login: "user2" }, body: "comment4" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const { filterCommentsByActor } = require("../src/github/data/fetcher");
|
||||||
|
const filtered = filterCommentsByActor(comments, "", "*[bot]");
|
||||||
|
expect(filtered).toHaveLength(2);
|
||||||
|
expect(filtered.map((c: any) => c.author.login)).toEqual([
|
||||||
|
"user1",
|
||||||
|
"user2",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("filters specific bot only", () => {
|
||||||
|
const comments = [
|
||||||
|
{ author: { login: "dependabot[bot]" }, body: "comment1" },
|
||||||
|
{ author: { login: "renovate[bot]" }, body: "comment2" },
|
||||||
|
{ author: { login: "user1" }, body: "comment3" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const { filterCommentsByActor } = require("../src/github/data/fetcher");
|
||||||
|
const filtered = filterCommentsByActor(comments, "", "dependabot[bot]");
|
||||||
|
expect(filtered).toHaveLength(2);
|
||||||
|
expect(filtered.map((c: any) => c.author.login)).toEqual([
|
||||||
|
"renovate[bot]",
|
||||||
|
"user1",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("handles empty comment array", () => {
|
||||||
|
const comments: any[] = [];
|
||||||
|
|
||||||
|
const { filterCommentsByActor } = require("../src/github/data/fetcher");
|
||||||
|
const filtered = filterCommentsByActor(comments, "user1", "");
|
||||||
|
expect(filtered).toHaveLength(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ describe("prepareMcpConfig", () => {
|
|||||||
allowedNonWriteUsers: "",
|
allowedNonWriteUsers: "",
|
||||||
trackProgress: false,
|
trackProgress: false,
|
||||||
includeFixLinks: true,
|
includeFixLinks: true,
|
||||||
|
includeCommentsByActor: "",
|
||||||
|
excludeCommentsByActor: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ const defaultInputs = {
|
|||||||
allowedNonWriteUsers: "",
|
allowedNonWriteUsers: "",
|
||||||
trackProgress: false,
|
trackProgress: false,
|
||||||
includeFixLinks: true,
|
includeFixLinks: true,
|
||||||
|
includeCommentsByActor: "",
|
||||||
|
excludeCommentsByActor: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultRepository = {
|
const defaultRepository = {
|
||||||
@@ -55,7 +57,12 @@ export const createMockContext = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mergedInputs = overrides.inputs
|
const mergedInputs = overrides.inputs
|
||||||
? { ...defaultInputs, ...overrides.inputs }
|
? {
|
||||||
|
...defaultInputs,
|
||||||
|
...overrides.inputs,
|
||||||
|
includeCommentsByActor: overrides.inputs.includeCommentsByActor ?? "",
|
||||||
|
excludeCommentsByActor: overrides.inputs.excludeCommentsByActor ?? "",
|
||||||
|
}
|
||||||
: defaultInputs;
|
: defaultInputs;
|
||||||
|
|
||||||
return { ...baseContext, ...overrides, inputs: mergedInputs };
|
return { ...baseContext, ...overrides, inputs: mergedInputs };
|
||||||
@@ -79,7 +86,12 @@ export const createMockAutomationContext = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mergedInputs = overrides.inputs
|
const mergedInputs = overrides.inputs
|
||||||
? { ...defaultInputs, ...overrides.inputs }
|
? {
|
||||||
|
...defaultInputs,
|
||||||
|
...overrides.inputs,
|
||||||
|
includeCommentsByActor: overrides.inputs.includeCommentsByActor ?? "",
|
||||||
|
excludeCommentsByActor: overrides.inputs.excludeCommentsByActor ?? "",
|
||||||
|
}
|
||||||
: { ...defaultInputs };
|
: { ...defaultInputs };
|
||||||
|
|
||||||
return { ...baseContext, ...overrides, inputs: mergedInputs };
|
return { ...baseContext, ...overrides, inputs: mergedInputs };
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ describe("detectMode with enhanced routing", () => {
|
|||||||
allowedNonWriteUsers: "",
|
allowedNonWriteUsers: "",
|
||||||
trackProgress: false,
|
trackProgress: false,
|
||||||
includeFixLinks: true,
|
includeFixLinks: true,
|
||||||
|
includeCommentsByActor: "",
|
||||||
|
excludeCommentsByActor: "",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ describe("checkWritePermissions", () => {
|
|||||||
allowedNonWriteUsers: "",
|
allowedNonWriteUsers: "",
|
||||||
trackProgress: false,
|
trackProgress: false,
|
||||||
includeFixLinks: true,
|
includeFixLinks: true,
|
||||||
|
includeCommentsByActor: "",
|
||||||
|
excludeCommentsByActor: "",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user