Skip to content

Commit

Permalink
Merge pull request #19 from volas/teams_support
Browse files Browse the repository at this point in the history
Teams review support
  • Loading branch information
dyladan authored Jul 11, 2024
2 parents a0a1a67 + efd21fb commit 58bd86e
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 27 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ components:
"*.md":
- owner5

# You also can assign ownership for a team (https://docs.github.com/en/organizations/organizing-members-into-teams/about-teams)
src/example.ts: org-name/team-name

# Optionally ignore some PR authors to reduce spam for your component owners
ignored-authors:
- dependabot
Expand Down Expand Up @@ -84,7 +87,8 @@ Path to configuration file.

GitHub personal access token.
Must have permission to read and write pull requests.
The default `github.token` is typically sufficient.

**You must [generate your own access token](#using-own-access-token) if you plan to use it for teams ownership**, otherwise the default `github.token` is typically sufficient.

### `assign-owners`

Expand All @@ -98,6 +102,32 @@ Determines if the component owners should be added to the pull request as assign

Determines pull request reviews should be requested from component owners.

## Using own access token

If you want to use this action to assign reviews for teams, then `github.token` would not be sufficient and you need to generate a new one.

1. Just follow [Github's instructions](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token) and add a new token with the following set of permissions:

| Permissions | Access |
| --- | --- |
Actions | Read-only
Codespaces | Read-only
Commit statuses | Read-only
Contents | Read-only
Environments | Read-only
Issues | Read and write
Metadata | Read-only
Pull requests | Read and write

2. Create a [secret for your repository](https://docs.github.com/en/actions/security-guides/using-secrets-in-github-actions#creating-secrets-for-a-repository), and place your token inside. Name it, for example, ``CODEOWNER_SECRET``.

3. Use the newly created secret as the token in the action yml file:

```
# default: ${{ github.token }}
repo-token: ${{ secrets.CODEOWNER_SECRET }}
```

## Why not use CODEOWNERS?

Great question.
Expand Down
41 changes: 29 additions & 12 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,46 +63,63 @@ function main() {
const changedFiles = yield (0, utils_1.getChangedFiles)(client, base, head);
const owners = (0, utils_1.getOwners)(config, changedFiles);
core.info(`${owners.length} owners found ${owners.join(' ')}`);
let owner_teams = new Set();
let owner_users = new Set();
let team_prefix = github.context.repo.owner + "/";
owners.forEach(owner => {
if (owner.startsWith(team_prefix)) {
owner_teams.add(owner.slice(team_prefix.length));
}
else {
owner_users.add(owner);
}
});
if (assignOwners && owners.length > 0) {
core.info('Adding assignees');
const addAssigneesResult = yield client.rest.issues.addAssignees({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: github.context.issue.number,
assignees: owners,
assignees: Array.from(owner_users),
});
core.debug(util.inspect(addAssigneesResult));
}
const reviewers = new Set(owners);
if (reviewers.has(author) || reviewers.has(author.toLowerCase()))
if (owner_users.has(author) || owner_users.has(author.toLowerCase()))
core.info('PR author is a component owner');
reviewers.delete(author);
reviewers.delete(author.toLowerCase());
owner_users.delete(author);
owner_users.delete(author.toLowerCase());
// Do not want to re-request when reviewers have already been requested
const oldReviewers = yield (0, utils_1.getReviewers)(client);
for (const reviewer of oldReviewers) {
for (const reviewer of oldReviewers.users) {
if (!reviewer)
continue;
core.info(`${reviewer.login} has already been requested`);
reviewers.delete(reviewer.login);
owner_users.delete(reviewer.login);
}
for (const reviewerTeam of oldReviewers.teams) {
if (!reviewerTeam)
continue;
core.info(`${reviewerTeam.slug} team has already been requested`);
owner_teams.delete(reviewerTeam.slug);
}
// Do not want to re-request when reviewers have already approved/rejected
const previousReviews = yield (0, utils_1.getReviews)(client);
for (const review of previousReviews) {
if (!review.user)
continue;
if (!reviewers.has(review.user.login))
if (!owner_users.has(review.user.login))
continue;
core.info(`${review.user.login} has already reviewed`);
reviewers.delete(review.user.login);
owner_users.delete(review.user.login);
}
if (requestOwnerReviews && reviewers.size > 0) {
if (requestOwnerReviews && (owner_users.size > 0 || owner_teams.size > 0)) {
core.info('Adding reviewers');
const requestReviewersResult = yield client.rest.pulls.requestReviewers({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: github.context.issue.number,
reviewers: Array.from(reviewers),
reviewers: Array.from(owner_users),
team_reviewers: Array.from(owner_teams),
}).catch((err) => {
var _a, _b;
// Ignore the case when the owner is not a collaborator.
Expand Down Expand Up @@ -386,7 +403,7 @@ function getReviewers(client) {
if (result.status !== 200) {
throw new Error(`getReviewers failed ${result.status} ${github.context.issue.number}`);
}
return result.data.users;
return result.data;
});
}
exports.getReviewers = getReviewers;
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

43 changes: 31 additions & 12 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,47 +31,66 @@ async function main() {

core.info(`${owners.length} owners found ${owners.join(' ')}`);

let owner_teams = new Set<string>();
let owner_users = new Set<string>();

let team_prefix = github.context.repo.owner + "/";

owners.forEach(owner => {
if(owner.startsWith(team_prefix)) {
owner_teams.add(owner.slice(team_prefix.length));
} else {
owner_users.add(owner);
}
})

if (assignOwners && owners.length > 0) {
core.info('Adding assignees');
const addAssigneesResult = await client.rest.issues.addAssignees({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: github.context.issue.number,
assignees: owners,
assignees: Array.from(owner_users),
});
core.debug(util.inspect(addAssigneesResult));
}


const reviewers = new Set<string>(owners);
if (reviewers.has(author) || reviewers.has(author.toLowerCase())) core.info('PR author is a component owner');
reviewers.delete(author);
reviewers.delete(author.toLowerCase());
if (owner_users.has(author) || owner_users.has(author.toLowerCase())) core.info('PR author is a component owner');
owner_users.delete(author);
owner_users.delete(author.toLowerCase());

// Do not want to re-request when reviewers have already been requested
const oldReviewers = await getReviewers(client);
for (const reviewer of oldReviewers) {

for (const reviewer of oldReviewers.users) {
if (!reviewer) continue;
core.info(`${reviewer.login} has already been requested`);
reviewers.delete(reviewer.login);
owner_users.delete(reviewer.login);
}

for (const reviewerTeam of oldReviewers.teams) {
if (!reviewerTeam) continue;
core.info(`${reviewerTeam.slug} team has already been requested`);
owner_teams.delete(reviewerTeam.slug);
}

// Do not want to re-request when reviewers have already approved/rejected
const previousReviews = await getReviews(client);
for (const review of previousReviews) {
if (!review.user) continue;
if (!reviewers.has(review.user.login)) continue;
if (!owner_users.has(review.user.login)) continue;
core.info(`${review.user.login} has already reviewed`);
reviewers.delete(review.user.login);
owner_users.delete(review.user.login);
}

if (requestOwnerReviews && reviewers.size > 0) {
if (requestOwnerReviews && (owner_users.size > 0 || owner_teams.size > 0)) {
core.info('Adding reviewers');
const requestReviewersResult = await client.rest.pulls.requestReviewers({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: github.context.issue.number,
reviewers: Array.from(reviewers),
reviewers: Array.from(owner_users),
team_reviewers: Array.from(owner_teams),
}).catch((err) => {
// Ignore the case when the owner is not a collaborator.
// Happens in forks and when the user hasn't yet received a write bit on the repo.
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ export async function getReviewers(client: Client) {
);
}

return result.data.users;
return result.data;
}

export async function getReviews(client: Client) {
Expand Down

0 comments on commit 58bd86e

Please sign in to comment.