Skip to content

Commit

Permalink
Merge pull request #74 from peter-evans/dev
Browse files Browse the repository at this point in the history
Support triage and maintain permission levels
  • Loading branch information
peter-evans authored Aug 2, 2020
2 parents 0495b44 + d07bfe3 commit bc81a15
Show file tree
Hide file tree
Showing 10 changed files with 170 additions and 26 deletions.
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ This action also features [advanced configuration](docs/advanced-configuration.m
| `reaction-token` | `GITHUB_TOKEN` or a `repo` scoped [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). See [reaction-token](#reaction-token) for further details. | `GITHUB_TOKEN` |
| `reactions` | Add reactions. :eyes: = seen, :rocket: = dispatched | `true` |
| `commands` | (**required**) A comma or newline separated list of commands. | |
| `permission` | The repository permission level required by the user to dispatch commands. (`none`, `read`, `write`, `admin`) | `write` |
| `permission` | The repository permission level required by the user to dispatch commands. See [permission](#permission) for further details. (`none`, `read`, `triage`, `write`, `maintain`, `admin`) | `write` |
| `issue-type` | The issue type required for commands. (`issue`, `pull-request`, `both`) | `both` |
| `allow-edits` | Allow edited comments to trigger command dispatches. | `false` |
| `repository` | The full name of the repository to send the dispatch events. | Current repository |
Expand Down Expand Up @@ -109,6 +109,17 @@ You can use a [PAT](https://help.github.com/en/github/authenticating-to-github/c
build-docs
```

#### `permission`

This input sets the repository permission level required by the user to dispatch commands.
It expects one of the [five repository permission levels](https://docs.github.com/en/github/setting-up-and-managing-organizations-and-teams/repository-permission-levels-for-an-organization#permission-levels-for-repositories-owned-by-an-organization), or `none`.
From the least to greatest permission level they are `none`, `read`, `triage`, `write`, `maintain` and `admin`.

Setting `write` as the required permission level means that any user with `write`, `maintain` or `admin` will be able to execute commands.

Note that `read`, `triage` and `maintain` only make sense for organization repositories.
For repositories owned by a user account there are only two permission levels, the repository owner (`admin`) and collaborators (`write`).

#### `dispatch-type`

By default, the action creates [repository_dispatch](https://developer.github.com/v3/repos/#create-a-repository-dispatch-event) events.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,16 @@ describe('command-helper tests', () => {

test('actor does not have permission', async () => {
expect(actorHasPermission('none', 'read')).toBeFalsy()
expect(actorHasPermission('read', 'write')).toBeFalsy()
expect(actorHasPermission('write', 'admin')).toBeFalsy()
expect(actorHasPermission('read', 'triage')).toBeFalsy()
expect(actorHasPermission('triage', 'write')).toBeFalsy()
expect(actorHasPermission('write', 'maintain')).toBeFalsy()
expect(actorHasPermission('maintain', 'admin')).toBeFalsy()
})

test('actor has permission', async () => {
expect(actorHasPermission('read', 'none')).toBeTruthy()
expect(actorHasPermission('write', 'read')).toBeTruthy()
expect(actorHasPermission('triage', 'read')).toBeTruthy()
expect(actorHasPermission('write', 'triage')).toBeTruthy()
expect(actorHasPermission('admin', 'write')).toBeTruthy()
expect(actorHasPermission('write', 'write')).toBeTruthy()
})
Expand Down
41 changes: 41 additions & 0 deletions __test__/github-helper.int.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {GitHubHelper} from '../lib/github-helper'

const token: string = process.env['REPO_SCOPED_PAT'] || 'not set'

describe('github-helper tests', () => {
it('tests getActorPermission returns "none" for non-existent collaborators', async () => {
const githubHelper = new GitHubHelper(token)
const actorPermission = await githubHelper.getActorPermission(
{owner: 'peter-evans', repo: 'slash-command-dispatch'},
'collaborator-does-not-exist'
)
expect(actorPermission).toEqual('none')
})

it('tests getActorPermission returns "admin"', async () => {
const githubHelper = new GitHubHelper(token)
const actorPermission = await githubHelper.getActorPermission(
{owner: 'peter-evans', repo: 'slash-command-dispatch'},
'peter-evans'
)
expect(actorPermission).toEqual('admin')
})

it('tests getActorPermission returns "write"', async () => {
const githubHelper = new GitHubHelper(token)
const actorPermission = await githubHelper.getActorPermission(
{owner: 'peter-evans', repo: 'slash-command-dispatch'},
'actions-bot'
)
expect(actorPermission).toEqual('write')
})

it('tests getActorPermission returns "triage" for an org repository collaborator', async () => {
const githubHelper = new GitHubHelper(token)
const actorPermission = await githubHelper.getActorPermission(
{owner: 'slash-command-dispatch', repo: 'integration-test-fixture'},
'test-case-machine-user'
)
expect(actorPermission).toEqual('triage')
})
})
47 changes: 41 additions & 6 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1052,7 +1052,7 @@ function getCommandsConfigFromJson(json) {
exports.getCommandsConfigFromJson = getCommandsConfigFromJson;
function configIsValid(config) {
for (const command of config) {
if (!['none', 'read', 'write', 'admin'].includes(command.permission)) {
if (!['none', 'read', 'triage', 'write', 'maintain', 'admin'].includes(command.permission)) {
core.setFailed(`'${command.permission}' is not a valid 'permission'.`);
return false;
}
Expand All @@ -1072,8 +1072,10 @@ function actorHasPermission(actorPermission, commandPermission) {
const permissionLevels = Object.freeze({
none: 1,
read: 2,
write: 3,
admin: 4
triage: 3,
write: 4,
maintain: 5,
admin: 6
});
core.debug(`Actor permission level: ${permissionLevels[actorPermission]}`);
core.debug(`Command permission level: ${permissionLevels[commandPermission]}`);
Expand Down Expand Up @@ -4720,13 +4722,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.GitHubHelper = void 0;
const core = __importStar(__webpack_require__(470));
const octokit_client_1 = __webpack_require__(921);
const util_1 = __webpack_require__(669);
class GitHubHelper {
constructor(token) {
const options = {};
if (token) {
options.auth = `${token}`;
}
this.octokit = new octokit_client_1.Octokit(options);
this.graphqlClient = octokit_client_1.graphql.defaults({
headers: {
authorization: `token ${token}`
}
});
}
parseRepository(repository) {
const [owner, repo] = repository.split('/');
Expand All @@ -4737,8 +4745,23 @@ class GitHubHelper {
}
getActorPermission(repo, actor) {
return __awaiter(this, void 0, void 0, function* () {
const { data: { permission } } = yield this.octokit.repos.getCollaboratorPermissionLevel(Object.assign(Object.assign({}, repo), { username: actor }));
return permission;
// https://docs.github.com/en/graphql/reference/enums#repositorypermission
// https://docs.github.com/en/graphql/reference/objects#repositorycollaboratoredge
// Returns 'READ', 'TRIAGE', 'WRITE', 'MAINTAIN', 'ADMIN'
const query = `query CollaboratorPermission($owner: String!, $repo: String!, $collaborator: String) {
repository(owner:$owner, name:$repo) {
collaborators(query: $collaborator) {
edges {
permission
}
}
}
}`;
const collaboratorPermission = yield this.graphqlClient(query, Object.assign(Object.assign({}, repo), { collaborator: actor }));
core.debug(`CollaboratorPermission: ${util_1.inspect(collaboratorPermission.repository.collaborators.edges)}`);
return collaboratorPermission.repository.collaborators.edges.length > 0
? collaboratorPermission.repository.collaborators.edges[0].permission.toLowerCase()
: 'none';
});
}
tryAddReaction(repo, commentId, reaction) {
Expand Down Expand Up @@ -6327,13 +6350,16 @@ Object.defineProperty(exports, '__esModule', { value: true });
var request = __webpack_require__(753);
var universalUserAgent = __webpack_require__(796);

const VERSION = "4.5.2";
const VERSION = "4.5.3";

class GraphqlError extends Error {
constructor(request, response) {
const message = response.data.errors[0].message;
super(message);
Object.assign(this, response.data);
Object.assign(this, {
headers: response.headers
});
this.name = "GraphqlError";
this.request = request; // Maintains proper stack trace (only available on V8)

Expand Down Expand Up @@ -6366,7 +6392,14 @@ function graphql(request, query, options) {
}, {});
return request(requestOptions).then(response => {
if (response.data.errors) {
const headers = {};

for (const key of Object.keys(response.headers)) {
headers[key] = response.headers[key];
}

throw new GraphqlError(requestOptions, {
headers,
data: response.data
});
}
Expand Down Expand Up @@ -6420,6 +6453,8 @@ const core_1 = __webpack_require__(448);
const plugin_paginate_rest_1 = __webpack_require__(299);
const plugin_rest_endpoint_methods_1 = __webpack_require__(842);
exports.Octokit = core_1.Octokit.plugin(plugin_paginate_rest_1.paginateRest, plugin_rest_endpoint_methods_1.restEndpointMethods);
var graphql_1 = __webpack_require__(898);
Object.defineProperty(exports, "graphql", { enumerable: true, get: function () { return graphql_1.graphql; } });


/***/ }),
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Advanced configuration requires a combination of yaml based inputs and JSON conf
| `reaction-token` | | `GITHUB_TOKEN` or a `repo` scoped [Personal Access Token (PAT)](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). See [reaction-token](https://github.com/peter-evans/slash-command-dispatch#reaction-token) for further details. | `GITHUB_TOKEN` |
| `reactions` | | Add reactions. :eyes: = seen, :rocket: = dispatched | `true` |
| | `command` | (**required**) The slash command. | |
| | `permission` | The repository permission level required by the user to dispatch the command. (`none`, `read`, `write`, `admin`) | `write` |
| | `permission` | The repository permission level required by the user to dispatch the command. (`none`, `read`, `triage`, `write`, `maintain`, `admin`) | `write` |
| | `issue_type` | The issue type required for the command. (`issue`, `pull-request`, `both`) | `both` |
| | `allow_edits` | Allow edited comments to trigger command dispatches. | `false` |
| | `repository` | The full name of the repository to send the dispatch events. | Current repository |
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

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

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"format": "prettier --write '**/*.ts'",
"format-check": "prettier --check '**/*.ts'",
"lint": "eslint src/**/*.ts",
"test": "jest"
"test": "jest unit",
"test:int": "jest int"
},
"repository": {
"type": "git",
Expand All @@ -30,6 +31,7 @@
"@actions/core": "1.2.4",
"@actions/github": "4.0.0",
"@octokit/core": "3.1.1",
"@octokit/graphql": "4.5.3",
"@octokit/plugin-paginate-rest": "2.2.3",
"@octokit/plugin-rest-endpoint-methods": "4.1.0",
"@octokit/types": "5.1.0"
Expand Down
12 changes: 9 additions & 3 deletions src/command-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ export function getCommandsConfigFromJson(json: string): Command[] {

export function configIsValid(config: Command[]): boolean {
for (const command of config) {
if (!['none', 'read', 'write', 'admin'].includes(command.permission)) {
if (
!['none', 'read', 'triage', 'write', 'maintain', 'admin'].includes(
command.permission
)
) {
core.setFailed(`'${command.permission}' is not a valid 'permission'.`)
return false
}
Expand All @@ -181,8 +185,10 @@ export function actorHasPermission(
const permissionLevels = Object.freeze({
none: 1,
read: 2,
write: 3,
admin: 4
triage: 3,
write: 4,
maintain: 5,
admin: 6
})
core.debug(`Actor permission level: ${permissionLevels[actorPermission]}`)
core.debug(`Command permission level: ${permissionLevels[commandPermission]}`)
Expand Down
56 changes: 50 additions & 6 deletions src/github-helper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import * as core from '@actions/core'
import {Octokit, OctokitOptions, PullsGetResponseData} from './octokit-client'
import {
graphql,
Graphql,
Octokit,
OctokitOptions,
PullsGetResponseData
} from './octokit-client'
import {Command, SlashCommandPayload} from './command-helper'
import {inspect} from 'util'

type ReposCreateDispatchEventParamsClientPayload = {
[key: string]: ReposCreateDispatchEventParamsClientPayloadKeyString
Expand All @@ -23,15 +30,33 @@ interface Repository {
repo: string
}

type CollaboratorPermission = {
repository: {
collaborators: {
edges: [
{
permission: string
}
]
}
}
}

export class GitHubHelper {
private octokit: InstanceType<typeof Octokit>
private graphqlClient: Graphql

constructor(token: string) {
const options: OctokitOptions = {}
if (token) {
options.auth = `${token}`
}
this.octokit = new Octokit(options)
this.graphqlClient = graphql.defaults({
headers: {
authorization: `token ${token}`
}
})
}

private parseRepository(repository: string): Repository {
Expand All @@ -43,13 +68,32 @@ export class GitHubHelper {
}

async getActorPermission(repo: Repository, actor: string): Promise<string> {
const {
data: {permission}
} = await this.octokit.repos.getCollaboratorPermissionLevel({
// https://docs.github.com/en/graphql/reference/enums#repositorypermission
// https://docs.github.com/en/graphql/reference/objects#repositorycollaboratoredge
// Returns 'READ', 'TRIAGE', 'WRITE', 'MAINTAIN', 'ADMIN'
const query = `query CollaboratorPermission($owner: String!, $repo: String!, $collaborator: String) {
repository(owner:$owner, name:$repo) {
collaborators(query: $collaborator) {
edges {
permission
}
}
}
}`
const collaboratorPermission = await this.graphqlClient<
CollaboratorPermission
>(query, {
...repo,
username: actor
collaborator: actor
})
return permission
core.debug(
`CollaboratorPermission: ${inspect(
collaboratorPermission.repository.collaborators.edges
)}`
)
return collaboratorPermission.repository.collaborators.edges.length > 0
? collaboratorPermission.repository.collaborators.edges[0].permission.toLowerCase()
: 'none'
}

async tryAddReaction(
Expand Down
6 changes: 4 additions & 2 deletions src/octokit-client.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import {Octokit as Core} from '@octokit/core'
import {paginateRest} from '@octokit/plugin-paginate-rest'
import {restEndpointMethods} from '@octokit/plugin-rest-endpoint-methods'

export {RestEndpointMethodTypes} from '@octokit/plugin-rest-endpoint-methods'
export {OctokitOptions} from '@octokit/core/dist-types/types'

export const Octokit = Core.plugin(paginateRest, restEndpointMethods)

export {PullsGetResponseData} from '@octokit/types'

export {graphql} from '@octokit/graphql'
export {graphql as Graphql} from '@octokit/graphql/dist-types/types'

0 comments on commit bc81a15

Please sign in to comment.