Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix location we get org from #95

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ reviewers:
designer_b:
- lead_desinger # username
- desinger_a # username
team:engineering-managers:
- engineers

files:
# Keys are glob expressions.
Expand Down
47 changes: 44 additions & 3 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,20 @@ async function assign_reviewers(reviewers) {
});
}

// https://docs.github.com/en/rest/teams/members?apiVersion=2022-11-28#list-team-members
async function get_team_members(team) {
const context = get_context();
const octokit = get_octokit();
const org = context.payload.repository.owner.login;

const { data } = await octokit.teams.listMembersInOrg({
org,
team_slug: team,
});

return data?.map((member) => member.login);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If data can't be null or undefined, I wouldn't add ? since it can hide some unexpected errors. In what case can data be null or undefined?

}
Comment on lines +116 to +127
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What will it happen if you call this function in a repository under an individual account?


/* Private */

let context_cache;
Expand Down Expand Up @@ -158,4 +172,5 @@ module.exports = {
fetch_changed_files,
assign_reviewers,
clear_cache,
get_team_members,
};
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async function run() {
const reviewers_based_on_files = identify_reviewers_by_changed_files({ config, changed_files, excludes: [ author ] });

core.info('Identifying reviewers based on the author');
const reviewers_based_on_author = identify_reviewers_by_author({ config, author });
const reviewers_based_on_author = await identify_reviewers_by_author({ config, author });

core.info('Adding other group members to reviewers if group assignment feature is on');
const reviewers_from_same_teams = fetch_other_group_members({ config, author });
Expand Down
30 changes: 28 additions & 2 deletions src/reviewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const core = require('@actions/core');
const minimatch = require('minimatch');
const sample_size = require('lodash/sampleSize');
const github = require('./github'); // Don't destructure this object to stub with sinon in tests

function fetch_other_group_members({ author, config }) {
const DEFAULT_OPTIONS = {
Expand Down Expand Up @@ -65,21 +66,46 @@ function identify_reviewers_by_changed_files({ config, changed_files, excludes =
return [ ...new Set(individuals) ].filter((reviewer) => !excludes.includes(reviewer));
}

function identify_reviewers_by_author({ config, 'author': specified_author }) {
async function identify_reviewers_by_author({ config, 'author': specified_author }) {
if (!(config.reviewers && config.reviewers.per_author)) {
core.info('"per_author" is not set; returning no reviewers for the author.');
return [];
}

// async behavior must happen first
const team_member_promises = await Object.keys(config.reviewers.per_author).map(async (author) => {
if (author.startsWith('team:')) {
const team = author.replace('team:', '');

return {
author,
members: await github.get_team_members(team) || [],
};
}

return {
author,
members: [],
};
});
const team_members = await Promise.all(team_member_promises);

// More than one author can be matched because groups are set as authors
const matching_authors = Object.keys(config.reviewers.per_author).filter((author) => {
if (author === specified_author) {
return true;
}

if (author.startsWith('team:')) {
const { members } = team_members.find((team) => team.author === author);
if (members.includes(specified_author)) {
return true;
}
}

const individuals_in_author_setting = replace_groups_with_individuals({ reviewers: [ author ], config });

if (individuals_in_author_setting.includes(specified_author)) {
if (individuals_in_author_setting?.includes(specified_author)) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you need to add ?? Is your changing adding something that can make individuals_in_author_setting null or undefined?

return true;
}

Expand Down
35 changes: 35 additions & 0 deletions test/github.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
fetch_changed_files,
assign_reviewers,
clear_cache,
get_team_members,
} = require('../src/github');

describe('github', function() {
Expand Down Expand Up @@ -155,4 +156,38 @@ describe('github', function() {
});
});
});

describe('get_team_members()', function() {
const stub = sinon.stub();
const octokit = {
teams: {
listMembersInOrg: stub,
},
};

beforeEach(function() {
github.getOctokit.resetBehavior();
github.getOctokit.returns(octokit);
});

it('gets team members', async function() {
stub.returns({
data: [
{ login: 'bowser' },
{ login: 'king-boo' },
{ login: 'goomboss' },
],
});

const team = 'koopa-troop';
const actual = await get_team_members(team);

expect(stub.calledOnce).to.be.true;
expect(stub.lastCall.args[0]).to.deep.equal({
org: 'necojackarc',
team_slug: 'koopa-troop',
});
expect(actual).to.deep.equal([ 'bowser', 'king-boo', 'goomboss' ]);
});
});
});
37 changes: 24 additions & 13 deletions test/reviewer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const {
randomly_pick_reviewers,
} = require('../src/reviewer');
const { expect } = require('chai');
const github = require('../src/github');
const sinon = require('sinon');

describe('reviewer', function() {
describe('fetch_other_group_members()', function() {
Expand Down Expand Up @@ -136,36 +138,45 @@ describe('reviewer', function() {
designers: [ 'mario', 'princess-peach', 'princess-daisy' ],
},
per_author: {
engineers: [ 'engineers', 'dr-mario' ],
designers: [ 'designers' ],
yoshi: [ 'mario', 'luige' ],
'engineers': [ 'engineers', 'dr-mario' ],
'designers': [ 'designers' ],
'yoshi': [ 'mario', 'luige' ],
'team:koopa-troop': [ 'mario' ],
},
},
};

it('returns nothing when config does not have a "per-author" key', function() {
const stub = sinon.stub(github, 'get_team_members');
stub.withArgs('koopa-troop').returns([ 'bowser', 'king-boo', 'goomboss' ]);

it('returns nothing when config does not have a "per-author" key', async function() {
const author = 'THIS DOES NOT MATTER';
expect(identify_reviewers_by_author({ config: { reviewers: {} }, author })).to.deep.equal([]);
expect(await identify_reviewers_by_author({ config: { reviewers: {} }, author })).to.deep.equal([]);
});

it('returns nothing when the author does not exist in the "per-author" settings', function() {
it('returns nothing when the author does not exist in the "per-author" settings', async function() {
const author = 'toad';
expect(identify_reviewers_by_author({ config, author })).to.deep.equal([]);
expect(await identify_reviewers_by_author({ config, author })).to.deep.equal([]);
});

it('returns the reviewers for the author', function() {
it('returns the reviewers for the author', async function() {
const author = 'yoshi';
expect(identify_reviewers_by_author({ config, author })).to.have.members([ 'mario', 'luige' ]);
expect(await identify_reviewers_by_author({ config, author })).to.have.members([ 'mario', 'luige' ]);
});

it('works when a author setting is specified with a group', function() {
it('works when a author setting is specified with a group', async function() {
const author = 'luigi';
expect(identify_reviewers_by_author({ config, author })).to.have.members([ 'mario', 'wario', 'waluigi', 'dr-mario' ]);
expect(await identify_reviewers_by_author({ config, author })).to.have.members([ 'mario', 'wario', 'waluigi', 'dr-mario' ]);
});

it('works when the author belongs to more than one group', function() {
it('works when the author belongs to more than one group', async function() {
const author = 'mario';
expect(identify_reviewers_by_author({ config, author })).to.have.members([ 'dr-mario', 'luigi', 'wario', 'waluigi', 'princess-peach', 'princess-daisy' ]);
expect(await identify_reviewers_by_author({ config, author })).to.have.members([ 'dr-mario', 'luigi', 'wario', 'waluigi', 'princess-peach', 'princess-daisy' ]);
});

it('works when gh team slug used for auther', async function() {
const author = 'bowser';
expect(await identify_reviewers_by_author({ config, author })).to.have.members([ 'mario' ]);
});
});

Expand Down