Skip to content

Commit

Permalink
Merge pull request #16 from peter-evans/named-args
Browse files Browse the repository at this point in the history
Named arguments
  • Loading branch information
peter-evans authored Jan 4, 2020
2 parents 335c3a1 + 21d0d04 commit 302daa7
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 113 deletions.
3 changes: 2 additions & 1 deletion .github/slash-command-dispatch.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"command": "create",
"permission": "write",
"issue_type": "issue",
"event_type_suffix": "-cmd"
"event_type_suffix": "-cmd",
"named_args": true
},
{
"command": "delete",
Expand Down
149 changes: 54 additions & 95 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,7 @@ When a valid command is found it creates a repository dispatch event that includ
"ChatOps" with slash commands can work in a basic way by parsing the commands during `issue_comment` events and immediately processing the command.
In repositories with a lot of activity, the workflow queue will get backed up very quickly if it is trying to handle new comments for commands *and* process the commands themselves.

Dispatching commands to be processed elsewhere keeps the workflow queue moving quickly. It essentially allows you to run multiple workflow queues in parallel.

### Key features

- Easy configuration of "ChatOps" slash commands
- Enables separating the queue of `issue_comment` events from the queue of dispatched commands to keep it fast moving
- Users receive faster feedback that commands have been seen and are waiting to be processed
- The ability to handle processing commands in multiple repositories in parallel
- Long running workloads can be processed in a repository workflow queue of their own
- Even if commands are dispatched and processed in the same repository, separation of comment parsing and command processing makes workflows more maintainable, and with less duplication
Dispatching commands to be processed elsewhere keeps the workflow queue moving quickly. It essentially enables parallel processing of workflows.

### Demo and examples

Expand All @@ -33,11 +24,13 @@ Check out the following demos.
- [ChatOps Demo in Pull Requests](https://github.com/peter-evans/slash-command-dispatch/pull/8)
- [Slash command code formatting - Python](https://github.com/peter-evans/slash-command-dispatch/pull/11)

See [examples](examples.md) for command patterns and example workflows.
See [examples](docs/examples.md) for command patterns and example workflows.

## Dispatching commands

### Basic configuration
### Configuration

The following workflow should be configured in the repository where commands will be dispatched from. This example will respond to comments containing the slash commands `/rebase`, `/integration-test` and `/create-ticket`.

```yml
name: Slash Command Dispatch
Expand All @@ -55,24 +48,24 @@ jobs:
commands: rebase, integration-test, create-ticket
```
This action also features [advanced configuration](docs/advanced-configuration.md) that allows each command to be configured individually if necessary. Use the standard configuration shown above unless you require advanced features.
### Action inputs
For basic configuration, use the inputs in the leftmost column.
Use the JSON properties for [Advanced configuration](#advanced-configuration).
| Input | JSON Property | Description | Default |
| --- | --- | --- | --- |
| `token` | | (**required**) A `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). | |
| `reaction-token` | | `GITHUB_TOKEN` or a `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). | |
| `reactions` | | Add reactions. :eyes: = seen, :rocket: = dispatched | `true` |
| `commands` | `command` | (**required**) Input: A comma separated list of commands to dispatch. JSON property: A single command. | |
| `permission` | `permission` | The repository permission level required by the user to dispatch commands. (`none`, `read`, `write`, `admin`) | `write` |
| `issue-type` | `issue_type` | The issue type required for commands. (`issue`, `pull-request`, `both`) | `both` |
| `allow-edits` | `allow_edits` | Allow edited comments to trigger command dispatches. | `false` |
| `repository` | `repository` | The full name of the repository to send the dispatch events. | Current repository |
| `event-type-suffix` | `event_type_suffix` | The repository dispatch event type suffix for the commands. | `-command` |
| `config` | | JSON configuration for commands. See [Advanced configuration](#advanced-configuration) | |
| `config-from-file` | | JSON configuration from a file for commands. See [Advanced configuration](#advanced-configuration) | |
| Input | Description | Default |
| --- | --- | --- |
| `token` | (**required**) A `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). | |
| `reaction-token` | `GITHUB_TOKEN` or a `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). | |
| `reactions` | Add reactions. :eyes: = seen, :rocket: = dispatched | `true` |
| `commands` | (**required**) A comma separated list of commands. | |
| `permission` | The repository permission level required by the user to dispatch commands. (`none`, `read`, `write`, `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 |
| `event-type-suffix` | The repository dispatch event type suffix for the commands. | `-command` |
| `named-args` | Parse named arguments and add them to the command payload. | `false` |
| `config` | | JSON configuration for commands. See [Advanced configuration](docs/advanced-configuration.md) | |
| `config-from-file` | | JSON configuration from a file for commands. See [Advanced configuration](docs/advanced-configuration.md) | |

### What is the reaction-token?

Expand All @@ -88,72 +81,6 @@ This means that reactions to comments will appear to be made by the user account
commands: rebase, integration-test, create-ticket
```

### Advanced configuration

Using JSON configuration allows the options for each command to be specified individually.

Note that it's recommended to write the JSON configuration directly in the workflow rather than use a file. Using the `config-from-file` input will be slightly slower due to requiring the repository to be checked out with `actions/checkout` so the file can be accessed.

Here is an example workflow. Take care to use the correct JSON property names.

```yml
name: Slash Command Dispatch
on:
issue_comment:
types: [created]
jobs:
slashCommandDispatch:
runs-on: ubuntu-latest
steps:
- name: Slash Command Dispatch
uses: peter-evans/slash-command-dispatch@v1
with:
token: ${{ secrets.REPO_ACCESS_TOKEN }}
reaction-token: ${{ secrets.GITHUB_TOKEN }}
config: >
[
{
"command": "rebase",
"permission": "admin",
"issue_type": "pull-request",
"repository": "peter-evans/slash-command-dispatch-processor"
},
{
"command": "integration-test",
"permission": "write",
"issue_type": "both",
"repository": "peter-evans/slash-command-dispatch-processor"
},
{
"command": "create-ticket",
"permission": "write",
"issue_type": "issue",
"allow_edits": true,
"event_type_suffix": "-cmd"
}
]
```

The following workflow is an example using the `config-from-file` input to set JSON configuration.
Note that `actions/checkout` is required to access the file.

```yml
name: Slash Command Dispatch
on:
issue_comment:
types: [created]
jobs:
slashCommandDispatch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Slash Command Dispatch
uses: peter-evans/slash-command-dispatch@v1
with:
token: ${{ secrets.REPO_ACCESS_TOKEN }}
config-from-file: .github/slash-command-dispatch.json
```

## Handling dispatched commands

### Event types
Expand All @@ -168,10 +95,12 @@ on:
types: [integration-test-command]
```

### Accessing command contexts
### Accessing contexts

Commands are dispatched with a payload containing a number of contexts.

#### `slash_command` context

The slash command context can be accessed as follows.
`args` is a space separated string of all the supplied arguments.
Each argument is also supplied in a numbered property, i.e. `arg1`, `arg2`, `arg3`, etc.
Expand All @@ -187,6 +116,36 @@ Each argument is also supplied in a numbered property, i.e. `arg1`, `arg2`, `arg
# etc.
```

If the `named-args` input is set to `true`, any arguments that are prefixed in the format `name=argument` will be parsed and added to the payload.

For example, the slash command `/deploy branch=master env=prod some other args` will be set in the JSON payload as follows.

```json
"slash_command": {
"command": "deploy",
"args": "branch=master env=prod some other args",
"unnamed_args": "some other args",
"branch": "master",
"env": "prod",
"arg1": "some",
"arg2": "other",
"arg3": "args"
}
```

These named arguments can be accessed in a workflow as follows.

```yml
- name: Output command and named arguments
run: |
echo ${{ github.event.client_payload.slash_command.command }}
echo ${{ github.event.client_payload.slash_command.branch }}
echo ${{ github.event.client_payload.slash_command.env }}
echo ${{ github.event.client_payload.slash_command.unnamed_args }}
```

#### `github` and `pull_request` contexts

The payload contains the complete `github` context of the `issue_comment` event at path `github.event.client_payload.github`.
Additionally, if the comment was made in a pull request, the action calls the [GitHub API to fetch the pull request detail](https://developer.github.com/v3/pulls/#get-a-single-pull-request) and attach it to the payload at path `github.event.client_payload.pull_request`.

Expand Down
4 changes: 3 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ inputs:
reactions:
description: 'Add reactions to comments containing commands.'
commands:
description: 'A comma separated list of commands to dispatch.'
description: 'A comma separated list of commands.'
required: true
permission:
description: 'The repository permission level required by the user to dispatch commands.'
Expand All @@ -21,6 +21,8 @@ inputs:
description: 'The full name of the repository to send the dispatch events.'
event-type-suffix:
description: 'The repository dispatch event type suffix for the commands.'
named-args:
description: 'Parse named arguments and add them to the command payload.'
config:
description: 'JSON configuration for commands.'
config-from-file:
Expand Down
40 changes: 33 additions & 7 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4665,13 +4665,15 @@ var fs = __webpack_require__(747);
const core = __webpack_require__(470);

const MAX_ARGS = 50;
const namedArgPattern = /^(?<name>[a-zA-Z0-9_]+)=(?<value>[^\s]+)$/;

const commandDefaults = Object.freeze({
permission: "write",
issue_type: "both",
allow_edits: false,
repository: process.env.GITHUB_REPOSITORY,
event_type_suffix: "-command"
event_type_suffix: "-command",
named_args: false
});

function toBool(input, defaultVal) {
Expand All @@ -4695,6 +4697,7 @@ function getInputs() {
allowEdits: core.getInput("allow-edits"),
repository: core.getInput("repository"),
eventTypeSuffix: core.getInput("event-type-suffix"),
namedArgs: core.getInput("named-args"),
config: core.getInput("config"),
configFromFile: core.getInput("config-from-file")
};
Expand Down Expand Up @@ -4738,6 +4741,7 @@ function getCommandsConfigFromInputs(inputs) {
cmd.event_type_suffix = inputs.eventTypeSuffix
? inputs.eventTypeSuffix
: cmd.event_type_suffix;
cmd.named_args = toBool(inputs.namedArgs, cmd.named_args);
config.push(cmd);
}
return config;
Expand All @@ -4759,6 +4763,7 @@ function getCommandsConfigFromJson(json) {
cmd.event_type_suffix = jc.event_type_suffix
? jc.event_type_suffix
: cmd.event_type_suffix;
cmd.named_args = toBool(jc.named_args, cmd.named_args);
config.push(cmd);
}
return config;
Expand Down Expand Up @@ -4817,16 +4822,30 @@ async function addReaction(octokit, repo, commentId, reaction) {
}
}

function getSlashCommandPayload(commentWords) {
function getSlashCommandPayload(commentWords, namedArgs) {
var payload = {
command: commentWords[0],
args: ""
};
if (commentWords.length > 1) {
const argWords = commentWords.slice(1, MAX_ARGS + 1);
payload.args = argWords.join(" ");
for (var i = 0; i < argWords.length; i++) {
payload[`arg${i + 1}`] = argWords[i];
// Parse named and unnamed args
var unnamedCount = 1;
var unnamedArgs = [];
for (var argWord of argWords) {
if (namedArgs && namedArgPattern.test(argWord)) {
const { groups: { name, value } } = namedArgPattern.exec(argWord);
payload[`${name}`] = value;
} else {
unnamedArgs.push(argWord)
payload[`arg${unnamedCount}`] = argWord;
unnamedCount += 1;
}
}
// Add a string of only the unnamed args
if (namedArgs && unnamedArgs.length > 0) {
payload["unnamed_args"] = unnamedArgs.join(" ");
}
}
return payload;
Expand Down Expand Up @@ -8049,10 +8068,8 @@ async function run() {
core.info(`Command '${commentWords[0]}' to be dispatched.`);

// Define payload
const slashCommandPayload = getSlashCommandPayload(commentWords);
core.debug(`Slash command payload: ${inspect(slashCommandPayload)}`);
var clientPayload = {
slash_command: slashCommandPayload,
slash_command: {},
github: github.context
};

Expand All @@ -8067,6 +8084,15 @@ async function run() {

// Dispatch for each matching configuration
for (const cmd of configMatches) {
// Generate slash command payload
clientPayload.slash_command = getSlashCommandPayload(
commentWords,
cmd.named_args
);
core.debug(
`Slash command payload: ${inspect(clientPayload.slash_command)}`
);
// Dispatch the command
const dispatchRepo = cmd.repository.split("/");
const eventType = cmd.command + cmd.event_type_suffix;
await octokit.repos.createDispatchEvent({
Expand Down
Loading

0 comments on commit 302daa7

Please sign in to comment.