Skip to content

Commit 302daa7

Browse files
authored
Merge pull request #16 from peter-evans/named-args
Named arguments
2 parents 335c3a1 + 21d0d04 commit 302daa7

File tree

9 files changed

+267
-113
lines changed

9 files changed

+267
-113
lines changed

.github/slash-command-dispatch.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"command": "create",
44
"permission": "write",
55
"issue_type": "issue",
6-
"event_type_suffix": "-cmd"
6+
"event_type_suffix": "-cmd",
7+
"named_args": true
78
},
89
{
910
"command": "delete",

README.md

Lines changed: 54 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,7 @@ When a valid command is found it creates a repository dispatch event that includ
1313
"ChatOps" with slash commands can work in a basic way by parsing the commands during `issue_comment` events and immediately processing the command.
1414
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.
1515

16-
Dispatching commands to be processed elsewhere keeps the workflow queue moving quickly. It essentially allows you to run multiple workflow queues in parallel.
17-
18-
### Key features
19-
20-
- Easy configuration of "ChatOps" slash commands
21-
- Enables separating the queue of `issue_comment` events from the queue of dispatched commands to keep it fast moving
22-
- Users receive faster feedback that commands have been seen and are waiting to be processed
23-
- The ability to handle processing commands in multiple repositories in parallel
24-
- Long running workloads can be processed in a repository workflow queue of their own
25-
- 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
16+
Dispatching commands to be processed elsewhere keeps the workflow queue moving quickly. It essentially enables parallel processing of workflows.
2617

2718
### Demo and examples
2819

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

36-
See [examples](examples.md) for command patterns and example workflows.
27+
See [examples](docs/examples.md) for command patterns and example workflows.
3728

3829
## Dispatching commands
3930

40-
### Basic configuration
31+
### Configuration
32+
33+
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`.
4134

4235
```yml
4336
name: Slash Command Dispatch
@@ -55,24 +48,24 @@ jobs:
5548
commands: rebase, integration-test, create-ticket
5649
```
5750
51+
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.
52+
5853
### Action inputs
5954
60-
For basic configuration, use the inputs in the leftmost column.
61-
Use the JSON properties for [Advanced configuration](#advanced-configuration).
62-
63-
| Input | JSON Property | Description | Default |
64-
| --- | --- | --- | --- |
65-
| `token` | | (**required**) A `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). | |
66-
| `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). | |
67-
| `reactions` | | Add reactions. :eyes: = seen, :rocket: = dispatched | `true` |
68-
| `commands` | `command` | (**required**) Input: A comma separated list of commands to dispatch. JSON property: A single command. | |
69-
| `permission` | `permission` | The repository permission level required by the user to dispatch commands. (`none`, `read`, `write`, `admin`) | `write` |
70-
| `issue-type` | `issue_type` | The issue type required for commands. (`issue`, `pull-request`, `both`) | `both` |
71-
| `allow-edits` | `allow_edits` | Allow edited comments to trigger command dispatches. | `false` |
72-
| `repository` | `repository` | The full name of the repository to send the dispatch events. | Current repository |
73-
| `event-type-suffix` | `event_type_suffix` | The repository dispatch event type suffix for the commands. | `-command` |
74-
| `config` | | JSON configuration for commands. See [Advanced configuration](#advanced-configuration) | |
75-
| `config-from-file` | | JSON configuration from a file for commands. See [Advanced configuration](#advanced-configuration) | |
55+
| Input | Description | Default |
56+
| --- | --- | --- |
57+
| `token` | (**required**) A `repo` scoped [PAT](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line). | |
58+
| `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). | |
59+
| `reactions` | Add reactions. :eyes: = seen, :rocket: = dispatched | `true` |
60+
| `commands` | (**required**) A comma separated list of commands. | |
61+
| `permission` | The repository permission level required by the user to dispatch commands. (`none`, `read`, `write`, `admin`) | `write` |
62+
| `issue-type` | The issue type required for commands. (`issue`, `pull-request`, `both`) | `both` |
63+
| `allow-edits` | Allow edited comments to trigger command dispatches. | `false` |
64+
| `repository` | The full name of the repository to send the dispatch events. | Current repository |
65+
| `event-type-suffix` | The repository dispatch event type suffix for the commands. | `-command` |
66+
| `named-args` | Parse named arguments and add them to the command payload. | `false` |
67+
| `config` | | JSON configuration for commands. See [Advanced configuration](docs/advanced-configuration.md) | |
68+
| `config-from-file` | | JSON configuration from a file for commands. See [Advanced configuration](docs/advanced-configuration.md) | |
7669

7770
### What is the reaction-token?
7871

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

91-
### Advanced configuration
92-
93-
Using JSON configuration allows the options for each command to be specified individually.
94-
95-
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.
96-
97-
Here is an example workflow. Take care to use the correct JSON property names.
98-
99-
```yml
100-
name: Slash Command Dispatch
101-
on:
102-
issue_comment:
103-
types: [created]
104-
jobs:
105-
slashCommandDispatch:
106-
runs-on: ubuntu-latest
107-
steps:
108-
- name: Slash Command Dispatch
109-
uses: peter-evans/slash-command-dispatch@v1
110-
with:
111-
token: ${{ secrets.REPO_ACCESS_TOKEN }}
112-
reaction-token: ${{ secrets.GITHUB_TOKEN }}
113-
config: >
114-
[
115-
{
116-
"command": "rebase",
117-
"permission": "admin",
118-
"issue_type": "pull-request",
119-
"repository": "peter-evans/slash-command-dispatch-processor"
120-
},
121-
{
122-
"command": "integration-test",
123-
"permission": "write",
124-
"issue_type": "both",
125-
"repository": "peter-evans/slash-command-dispatch-processor"
126-
},
127-
{
128-
"command": "create-ticket",
129-
"permission": "write",
130-
"issue_type": "issue",
131-
"allow_edits": true,
132-
"event_type_suffix": "-cmd"
133-
}
134-
]
135-
```
136-
137-
The following workflow is an example using the `config-from-file` input to set JSON configuration.
138-
Note that `actions/checkout` is required to access the file.
139-
140-
```yml
141-
name: Slash Command Dispatch
142-
on:
143-
issue_comment:
144-
types: [created]
145-
jobs:
146-
slashCommandDispatch:
147-
runs-on: ubuntu-latest
148-
steps:
149-
- uses: actions/checkout@v2
150-
- name: Slash Command Dispatch
151-
uses: peter-evans/slash-command-dispatch@v1
152-
with:
153-
token: ${{ secrets.REPO_ACCESS_TOKEN }}
154-
config-from-file: .github/slash-command-dispatch.json
155-
```
156-
15784
## Handling dispatched commands
15885

15986
### Event types
@@ -168,10 +95,12 @@ on:
16895
types: [integration-test-command]
16996
```
17097

171-
### Accessing command contexts
98+
### Accessing contexts
17299

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

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

119+
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.
120+
121+
For example, the slash command `/deploy branch=master env=prod some other args` will be set in the JSON payload as follows.
122+
123+
```json
124+
"slash_command": {
125+
"command": "deploy",
126+
"args": "branch=master env=prod some other args",
127+
"unnamed_args": "some other args",
128+
"branch": "master",
129+
"env": "prod",
130+
"arg1": "some",
131+
"arg2": "other",
132+
"arg3": "args"
133+
}
134+
```
135+
136+
These named arguments can be accessed in a workflow as follows.
137+
138+
```yml
139+
- name: Output command and named arguments
140+
run: |
141+
echo ${{ github.event.client_payload.slash_command.command }}
142+
echo ${{ github.event.client_payload.slash_command.branch }}
143+
echo ${{ github.event.client_payload.slash_command.env }}
144+
echo ${{ github.event.client_payload.slash_command.unnamed_args }}
145+
```
146+
147+
#### `github` and `pull_request` contexts
148+
190149
The payload contains the complete `github` context of the `issue_comment` event at path `github.event.client_payload.github`.
191150
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`.
192151

action.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ inputs:
99
reactions:
1010
description: 'Add reactions to comments containing commands.'
1111
commands:
12-
description: 'A comma separated list of commands to dispatch.'
12+
description: 'A comma separated list of commands.'
1313
required: true
1414
permission:
1515
description: 'The repository permission level required by the user to dispatch commands.'
@@ -21,6 +21,8 @@ inputs:
2121
description: 'The full name of the repository to send the dispatch events.'
2222
event-type-suffix:
2323
description: 'The repository dispatch event type suffix for the commands.'
24+
named-args:
25+
description: 'Parse named arguments and add them to the command payload.'
2426
config:
2527
description: 'JSON configuration for commands.'
2628
config-from-file:

dist/index.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4665,13 +4665,15 @@ var fs = __webpack_require__(747);
46654665
const core = __webpack_require__(470);
46664666

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

46694670
const commandDefaults = Object.freeze({
46704671
permission: "write",
46714672
issue_type: "both",
46724673
allow_edits: false,
46734674
repository: process.env.GITHUB_REPOSITORY,
4674-
event_type_suffix: "-command"
4675+
event_type_suffix: "-command",
4676+
named_args: false
46754677
});
46764678

46774679
function toBool(input, defaultVal) {
@@ -4695,6 +4697,7 @@ function getInputs() {
46954697
allowEdits: core.getInput("allow-edits"),
46964698
repository: core.getInput("repository"),
46974699
eventTypeSuffix: core.getInput("event-type-suffix"),
4700+
namedArgs: core.getInput("named-args"),
46984701
config: core.getInput("config"),
46994702
configFromFile: core.getInput("config-from-file")
47004703
};
@@ -4738,6 +4741,7 @@ function getCommandsConfigFromInputs(inputs) {
47384741
cmd.event_type_suffix = inputs.eventTypeSuffix
47394742
? inputs.eventTypeSuffix
47404743
: cmd.event_type_suffix;
4744+
cmd.named_args = toBool(inputs.namedArgs, cmd.named_args);
47414745
config.push(cmd);
47424746
}
47434747
return config;
@@ -4759,6 +4763,7 @@ function getCommandsConfigFromJson(json) {
47594763
cmd.event_type_suffix = jc.event_type_suffix
47604764
? jc.event_type_suffix
47614765
: cmd.event_type_suffix;
4766+
cmd.named_args = toBool(jc.named_args, cmd.named_args);
47624767
config.push(cmd);
47634768
}
47644769
return config;
@@ -4817,16 +4822,30 @@ async function addReaction(octokit, repo, commentId, reaction) {
48174822
}
48184823
}
48194824

4820-
function getSlashCommandPayload(commentWords) {
4825+
function getSlashCommandPayload(commentWords, namedArgs) {
48214826
var payload = {
48224827
command: commentWords[0],
48234828
args: ""
48244829
};
48254830
if (commentWords.length > 1) {
48264831
const argWords = commentWords.slice(1, MAX_ARGS + 1);
48274832
payload.args = argWords.join(" ");
4828-
for (var i = 0; i < argWords.length; i++) {
4829-
payload[`arg${i + 1}`] = argWords[i];
4833+
// Parse named and unnamed args
4834+
var unnamedCount = 1;
4835+
var unnamedArgs = [];
4836+
for (var argWord of argWords) {
4837+
if (namedArgs && namedArgPattern.test(argWord)) {
4838+
const { groups: { name, value } } = namedArgPattern.exec(argWord);
4839+
payload[`${name}`] = value;
4840+
} else {
4841+
unnamedArgs.push(argWord)
4842+
payload[`arg${unnamedCount}`] = argWord;
4843+
unnamedCount += 1;
4844+
}
4845+
}
4846+
// Add a string of only the unnamed args
4847+
if (namedArgs && unnamedArgs.length > 0) {
4848+
payload["unnamed_args"] = unnamedArgs.join(" ");
48304849
}
48314850
}
48324851
return payload;
@@ -8049,10 +8068,8 @@ async function run() {
80498068
core.info(`Command '${commentWords[0]}' to be dispatched.`);
80508069

80518070
// Define payload
8052-
const slashCommandPayload = getSlashCommandPayload(commentWords);
8053-
core.debug(`Slash command payload: ${inspect(slashCommandPayload)}`);
80548071
var clientPayload = {
8055-
slash_command: slashCommandPayload,
8072+
slash_command: {},
80568073
github: github.context
80578074
};
80588075

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

80688085
// Dispatch for each matching configuration
80698086
for (const cmd of configMatches) {
8087+
// Generate slash command payload
8088+
clientPayload.slash_command = getSlashCommandPayload(
8089+
commentWords,
8090+
cmd.named_args
8091+
);
8092+
core.debug(
8093+
`Slash command payload: ${inspect(clientPayload.slash_command)}`
8094+
);
8095+
// Dispatch the command
80708096
const dispatchRepo = cmd.repository.split("/");
80718097
const eventType = cmd.command + cmd.event_type_suffix;
80728098
await octokit.repos.createDispatchEvent({

0 commit comments

Comments
 (0)