Support triage and maintain permission levels
This commit is contained in:
@@ -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 |
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
Vendored
+41
-6
@@ -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;
|
||||
}
|
||||
@@ -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]}`);
|
||||
@@ -4720,6 +4722,7 @@ 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 = {};
|
||||
@@ -4727,6 +4730,11 @@ class GitHubHelper {
|
||||
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('/');
|
||||
@@ -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) {
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
@@ -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; } });
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
@@ -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 |
|
||||
|
||||
Generated
+3
-3
@@ -946,9 +946,9 @@
|
||||
}
|
||||
},
|
||||
"@octokit/graphql": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.2.tgz",
|
||||
"integrity": "sha512-SpB/JGdB7bxRj8qowwfAXjMpICUYSJqRDj26MKJAryRQBqp/ZzARsaO2LEFWzDaps0FLQoPYVGppS0HQXkBhdg==",
|
||||
"version": "4.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.5.3.tgz",
|
||||
"integrity": "sha512-JyYvi3j2tOb5ofASEpcg1Advs07H+Ag+I+ez7buuZfNVAmh1IYcDTuxd4gnYH8S2PSGu+f5IdDGxMmkK+5zsdA==",
|
||||
"requires": {
|
||||
"@octokit/request": "^5.3.0",
|
||||
"@octokit/types": "^5.0.0",
|
||||
|
||||
+3
-1
@@ -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",
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]}`)
|
||||
|
||||
+50
-6
@@ -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
|
||||
@@ -23,8 +30,21 @@ 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 = {}
|
||||
@@ -32,6 +52,11 @@ export class GitHubHelper {
|
||||
options.auth = `${token}`
|
||||
}
|
||||
this.octokit = new Octokit(options)
|
||||
this.graphqlClient = graphql.defaults({
|
||||
headers: {
|
||||
authorization: `token ${token}`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private parseRepository(repository: string): Repository {
|
||||
@@ -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(
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user