@@ -65,11 +65,32 @@ jobs:
6565 return Boolean(login && login.endsWith('[bot]'));
6666 }
6767
68- const TRUSTED_ASSOCIATIONS = new Set(['OWNER', 'MEMBER', 'COLLABORATOR']);
68+ const TRUSTED_PERMISSIONS = new Set(['admin', 'maintain', 'write', 'triage']);
69+ const trustedReviewerCache = new Map();
6970
70- function isTrustedReviewer(actor) {
71- const association = actor?.author_association || actor?.authorAssociation;
72- return Boolean(actor?.user?.login) && TRUSTED_ASSOCIATIONS.has(association);
71+ async function isTrustedReviewer(login) {
72+ if (!login || isBot(login)) {
73+ return false;
74+ }
75+
76+ if (trustedReviewerCache.has(login)) {
77+ return trustedReviewerCache.get(login);
78+ }
79+
80+ try {
81+ const response = await github.rest.repos.getCollaboratorPermissionLevel({
82+ owner: context.repo.owner,
83+ repo: context.repo.repo,
84+ username: login,
85+ });
86+
87+ const trusted = TRUSTED_PERMISSIONS.has(response.data.permission);
88+ trustedReviewerCache.set(login, trusted);
89+ return trusted;
90+ } catch (error) {
91+ trustedReviewerCache.set(login, false);
92+ return false;
93+ }
7394 }
7495
7596 async function paginate(method, params) {
@@ -84,11 +105,11 @@ jobs:
84105 direction: 'asc',
85106 });
86107
87- function collectFeedbackAt(author, collection, getLogin, getTimestamp, predicate = () => true) {
108+ function collectFeedbackAt(author, trustedLogins, collection, getLogin, getTimestamp, predicate = () => true) {
88109 return collection
89110 .filter((item) => {
90111 const login = getLogin(item);
91- if (!login || login === author || isBot(login) || !isTrustedReviewer(item )) {
112+ if (!login || login === author || isBot(login) || !trustedLogins.has(login )) {
92113 return false;
93114 }
94115 return predicate(item);
@@ -158,6 +179,39 @@ jobs:
158179 }
159180 }
160181
182+ const candidateTrustedLogins = new Set();
183+ for (const comment of comments) {
184+ const login = comment.user?.login;
185+ if (login && login !== author && !isBot(login)) {
186+ candidateTrustedLogins.add(login);
187+ }
188+ }
189+ for (const review of reviews) {
190+ const login = review.user?.login;
191+ if (login && login !== author && !isBot(login)) {
192+ candidateTrustedLogins.add(login);
193+ }
194+ }
195+ for (const reviewComment of reviewComments) {
196+ const login = reviewComment.user?.login;
197+ if (login && login !== author && !isBot(login)) {
198+ candidateTrustedLogins.add(login);
199+ }
200+ }
201+
202+ const trustedLogins = new Set();
203+ await Promise.all(
204+ [...candidateTrustedLogins].map(async (login) => {
205+ if (await isTrustedReviewer(login)) {
206+ trustedLogins.add(login);
207+ }
208+ }),
209+ );
210+
211+ if (DRY_RUN && pr.number === 642) {
212+ core.info(`DEBUG #642 trustedLogins ${JSON.stringify([...trustedLogins])}`);
213+ }
214+
161215 let latestAuthorResponseAt = 0;
162216
163217 for (const comment of comments) {
@@ -195,9 +249,10 @@ jobs:
195249 }
196250
197251 const feedbackAts = [
198- ...collectFeedbackAt(author, comments, (comment) => comment.user?.login, (comment) => ts(comment.created_at)),
252+ ...collectFeedbackAt(author, trustedLogins, comments, (comment) => comment.user?.login, (comment) => ts(comment.created_at)),
199253 ...collectFeedbackAt(
200254 author,
255+ trustedLogins,
201256 reviews,
202257 (review) => review.user?.login,
203258 (review) => ts(review.submitted_at || review.created_at),
@@ -206,7 +261,7 @@ jobs:
206261 return state !== 'APPROVED' && state !== 'DISMISSED' && state !== 'PENDING';
207262 },
208263 ),
209- ...collectFeedbackAt(author, reviewComments, (reviewComment) => reviewComment.user?.login, (reviewComment) => ts(reviewComment.created_at)),
264+ ...collectFeedbackAt(author, trustedLogins, reviewComments, (reviewComment) => reviewComment.user?.login, (reviewComment) => ts(reviewComment.created_at)),
210265 ].filter((feedbackAt) => feedbackAt > latestAuthorResponseAt);
211266
212267 if (feedbackAts.length === 0) {
0 commit comments