Skip to content

Commit 40a547a

Browse files
authored
Merge branch 'develop' into feature/custom-folder
2 parents 6e7d597 + 075723d commit 40a547a

6 files changed

Lines changed: 1264 additions & 28 deletions

File tree

src/file-manager.ts

Lines changed: 109 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,93 @@ export class FileManager {
7878
}
7979

8080
public filterIssues(repo: RepositoryTracking, issues: any[]): any[] {
81-
return issues;
81+
let filteredIssues = issues;
82+
83+
// Apply label filtering
84+
if ((repo.enableLabelFilter ?? false) && (repo.labelFilters?.length ?? 0) > 0) {
85+
filteredIssues = this.applyLabelFilter(filteredIssues, repo.labelFilterMode ?? "include", repo.labelFilters ?? []);
86+
}
87+
88+
// Apply assignee filtering
89+
if ((repo.enableAssigneeFilter ?? false)) {
90+
filteredIssues = this.applyAssigneeFilter(filteredIssues, repo.assigneeFilterMode ?? "assigned-to-me", repo.assigneeFilters ?? []);
91+
}
92+
93+
return filteredIssues;
94+
}
95+
96+
private applyLabelFilter(items: any[], filterMode: "include" | "exclude", labelFilters: string[]): any[] {
97+
return items.filter((item) => {
98+
if (!item.labels || !Array.isArray(item.labels)) {
99+
// If no labels, only include in "exclude" mode (since we're excluding specific labels)
100+
return filterMode === "exclude";
101+
}
102+
103+
const itemLabels = item.labels.map((label: any) =>
104+
typeof label === 'string' ? label : label.name
105+
);
106+
107+
const hasMatchingLabel = labelFilters.some(filterLabel =>
108+
itemLabels.includes(filterLabel)
109+
);
110+
111+
// Include mode: only include items that have at least one of the specified labels
112+
// Exclude mode: exclude items that have any of the specified labels
113+
return filterMode === "include" ? hasMatchingLabel : !hasMatchingLabel;
114+
});
115+
}
116+
117+
private applyAssigneeFilter(items: any[], filterMode: "assigned-to-me" | "assigned-to-specific" | "unassigned" | "any-assigned", assigneeFilters: string[]): any[] {
118+
return items.filter((item) => {
119+
const assignees = item.assignees || [];
120+
const assigneeUsernames = assignees.map((assignee: any) => assignee.login || assignee);
121+
122+
switch (filterMode) {
123+
case "assigned-to-me":
124+
// Get current user from the item's context or use a stored current user
125+
const currentUser = this.getCurrentUser();
126+
return assigneeUsernames.includes(currentUser);
127+
128+
case "assigned-to-specific":
129+
// Check if any of the specified assignees are assigned
130+
return assigneeFilters.some(filterUser => assigneeUsernames.includes(filterUser));
131+
132+
case "unassigned":
133+
// Only include items with no assignees
134+
return assigneeUsernames.length === 0;
135+
136+
case "any-assigned":
137+
// Only include items that have at least one assignee
138+
return assigneeUsernames.length > 0;
139+
140+
default:
141+
return true;
142+
}
143+
});
144+
}
145+
146+
private getCurrentUser(): string {
147+
// Access the current user from the GitHubClient through the main plugin
148+
return this.gitHubClient ? this.gitHubClient.getCurrentUser() : "";
82149
}
83150

84151
public filterPullRequests(
85152
repo: RepositoryTracking,
86153
pullRequests: any[],
87154
): any[] {
88-
return pullRequests;
155+
let filteredPullRequests = pullRequests;
156+
157+
// Apply label filtering
158+
if ((repo.enablePrLabelFilter ?? false) && (repo.prLabelFilters?.length ?? 0) > 0) {
159+
filteredPullRequests = this.applyLabelFilter(filteredPullRequests, repo.prLabelFilterMode ?? "include", repo.prLabelFilters ?? []);
160+
}
161+
162+
// Apply assignee filtering
163+
if ((repo.enablePrAssigneeFilter ?? false)) {
164+
filteredPullRequests = this.applyAssigneeFilter(filteredPullRequests, repo.prAssigneeFilterMode ?? "assigned-to-me", repo.prAssigneeFilters ?? []);
165+
}
166+
167+
return filteredPullRequests;
89168
}
90169

91170
public async cleanupEmptyFolders(): Promise<void> {
@@ -274,11 +353,20 @@ export class FileManager {
274353
const file = this.app.vault.getAbstractFileByPath(`${issueFolderPath}/${fileName}`);
275354

276355
const [owner, repoName] = repo.repository.split("/");
277-
const comments = await this.gitHubClient.fetchIssueComments(
278-
owner,
279-
repoName,
280-
issue.number,
281-
);
356+
357+
// Only fetch comments if they should be included
358+
let comments: any[] = [];
359+
if (repo.includeIssueComments) {
360+
comments = await this.gitHubClient.fetchIssueComments(
361+
owner,
362+
repoName,
363+
issue.number,
364+
);
365+
} else {
366+
this.noticeManager.debug(
367+
`Skipping comments for issue ${issue.number}: repository setting disabled`,
368+
);
369+
}
282370

283371
let content = this.createIssueContent(issue, repo, comments);
284372

@@ -366,11 +454,20 @@ export class FileManager {
366454
const file = this.app.vault.getAbstractFileByPath(`${pullRequestFolderPath}/${fileName}`);
367455

368456
const [owner, repoName] = repo.repository.split("/");
369-
const comments = await this.gitHubClient.fetchPullRequestComments(
370-
owner,
371-
repoName,
372-
pr.number,
373-
);
457+
458+
// Only fetch comments if they should be included
459+
let comments: any[] = [];
460+
if (repo.includePullRequestComments) {
461+
comments = await this.gitHubClient.fetchPullRequestComments(
462+
owner,
463+
repoName,
464+
pr.number,
465+
);
466+
} else {
467+
this.noticeManager.debug(
468+
`Skipping comments for PR ${pr.number}: repository setting disabled`,
469+
);
470+
}
374471

375472
let content = this.createPullRequestContent(pr, repo, comments);
376473

src/github-client.ts

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { NoticeManager } from "./notice-manager";
44

55
export class GitHubClient {
66
private octokit: Octokit | null = null;
7+
private currentUser: string = "";
78

89
constructor(
910
private settings: GitHubTrackerSettings,
@@ -54,7 +55,8 @@ export class GitHubClient {
5455

5556
try {
5657
const response = await this.octokit.rest.users.getAuthenticated();
57-
return response.data.login;
58+
this.currentUser = response.data.login;
59+
return this.currentUser;
5860
} catch (error) {
5961
this.noticeManager.error(
6062
"Error fetching authenticated user",
@@ -64,6 +66,13 @@ export class GitHubClient {
6466
}
6567
}
6668

69+
/**
70+
* Get the currently cached authenticated user
71+
*/
72+
public getCurrentUser(): string {
73+
return this.currentUser;
74+
}
75+
6776
/**
6877
* Fetch issues for a repository
6978
*/
@@ -403,7 +412,158 @@ export class GitHubClient {
403412
}
404413
}
405414

415+
/**
416+
* Fetch labels for a repository
417+
*/
418+
public async fetchRepositoryLabels(
419+
owner: string,
420+
repo: string,
421+
): Promise<any[]> {
422+
if (!this.octokit) {
423+
return [];
424+
}
425+
426+
try {
427+
let allLabels: any[] = [];
428+
let page = 1;
429+
let hasMorePages = true;
430+
431+
while (hasMorePages) {
432+
const response = await this.octokit.rest.issues.listLabelsForRepo({
433+
owner,
434+
repo,
435+
per_page: 100,
436+
page,
437+
});
438+
439+
allLabels = [...allLabels, ...response.data];
440+
hasMorePages = response.data.length === 100;
441+
page++;
442+
}
443+
444+
this.noticeManager.debug(
445+
`Fetched ${allLabels.length} labels for ${owner}/${repo}`,
446+
);
447+
return allLabels;
448+
} catch (error) {
449+
this.noticeManager.error(
450+
`Error fetching labels for ${owner}/${repo}`,
451+
error,
452+
);
453+
return [];
454+
}
455+
}
456+
457+
/**
458+
* Fetch repository collaborators/contributors
459+
*/
460+
public async fetchRepositoryCollaborators(
461+
owner: string,
462+
repo: string,
463+
): Promise<any[]> {
464+
if (!this.octokit) {
465+
return [];
466+
}
467+
468+
try {
469+
let allCollaborators: any[] = [];
470+
let page = 1;
471+
let hasMorePages = true;
472+
473+
while (hasMorePages) {
474+
const response = await this.octokit.rest.repos.listCollaborators({
475+
owner,
476+
repo,
477+
per_page: 100,
478+
page,
479+
});
480+
481+
allCollaborators = [...allCollaborators, ...response.data];
482+
hasMorePages = response.data.length === 100;
483+
page++;
484+
}
485+
486+
this.noticeManager.debug(
487+
`Fetched ${allCollaborators.length} collaborators for ${owner}/${repo}`,
488+
);
489+
return allCollaborators;
490+
} catch (error) {
491+
// If collaborators endpoint fails (permissions), try contributors as fallback
492+
try {
493+
let allContributors: any[] = [];
494+
let page = 1;
495+
let hasMorePages = true;
496+
497+
while (hasMorePages) {
498+
const response = await this.octokit.rest.repos.listContributors({
499+
owner,
500+
repo,
501+
per_page: 100,
502+
page,
503+
});
504+
505+
allContributors = [...allContributors, ...response.data];
506+
hasMorePages = response.data.length === 100;
507+
page++;
508+
}
509+
510+
this.noticeManager.debug(
511+
`Fetched ${allContributors.length} contributors for ${owner}/${repo} (fallback)`,
512+
);
513+
return allContributors;
514+
} catch (fallbackError) {
515+
this.noticeManager.error(
516+
`Error fetching collaborators/contributors for ${owner}/${repo}`,
517+
fallbackError,
518+
);
519+
return [];
520+
}
521+
}
522+
}
523+
524+
/**
525+
* Validate the GitHub token and get its scopes
526+
*/
527+
public async validateToken(): Promise<{ valid: boolean; scopes: string[]; user?: string }> {
528+
if (!this.octokit) {
529+
return { valid: false, scopes: [] };
530+
}
531+
532+
try {
533+
const response = await this.octokit.rest.users.getAuthenticated();
534+
const scopes = response.headers['x-oauth-scopes']?.split(', ') || [];
535+
return {
536+
valid: true,
537+
scopes,
538+
user: response.data.login
539+
};
540+
} catch (error) {
541+
return { valid: false, scopes: [] };
542+
}
543+
}
544+
545+
/**
546+
* Get current rate limit information
547+
*/
548+
public async getRateLimit(): Promise<{ remaining: number; limit: number; reset: Date } | null> {
549+
if (!this.octokit) {
550+
return null;
551+
}
552+
553+
try {
554+
const response = await this.octokit.rest.rateLimit.get();
555+
return {
556+
remaining: response.data.rate.remaining,
557+
limit: response.data.rate.limit,
558+
reset: new Date(response.data.rate.reset * 1000)
559+
};
560+
} catch (error) {
561+
return null;
562+
}
563+
}
564+
406565
public dispose(): void {
407566
this.octokit = null;
567+
this.currentUser = "";
408568
}
409569
}

0 commit comments

Comments
 (0)