Skip to content

Commit

Permalink
Merge branch 'main' into ropensci
Browse files Browse the repository at this point in the history
  • Loading branch information
xuanxu committed Dec 23, 2021
2 parents 5deea12 + 07a58fd commit a86460b
Show file tree
Hide file tree
Showing 22 changed files with 365 additions and 22 deletions.
15 changes: 14 additions & 1 deletion app/lib/responder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,20 @@ def get_data_from_command_regex
# Create background workers to perform external calls
def process_external_service(service_config, service_data)
unless service_config.nil? || service_config.empty?
ExternalServiceWorker.perform_async(service_config, service_data)
service_locals = get_data_from_issue(service_config[:data_from_issue]).merge(service_data)
ExternalServiceWorker.perform_async(service_config, service_locals)
end
end

# Call a different responder bypassing authorization
def process_other_responder(other_responder={})
matching_responder = ResponderRegistry.get_responder(@settings, other_responder[:responder_key], other_responder[:responder_name])

if matching_responder
matching_responder.context = context
msg = other_responder[:message] || ""
matching_responder.match_data = matching_responder.event_regex.match(msg) unless msg.empty?
matching_responder.process_message(msg)
end
end

Expand Down
23 changes: 23 additions & 0 deletions app/lib/responder_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,29 @@ def self.available_responders
available_responders
end

# Get an instance of one of the responders in the configuration
def self.get_responder(config={}, responder_key=nil, responder_name=nil)
return nil if config.empty?
return nil if responder_key.nil?
return nil unless config[:responders].keys.include?(responder_key)

key = nil
responder_params = config[:responders][responder_key] || {}

if responder_name && responder_params.is_a?(Array)
if responder_instance = responder_params.select {|r| r.keys.first.to_s == responder_name.to_s}.first
key = responder_key
params = responder_instance[responder_name] || {}
params = Sinatra::IndifferentHash[name: responder_name.to_s].merge(params)
end
elsif responder_name.nil? && responder_params.is_a?(Hash)
key = responder_key
params = responder_params
end

return key.nil? ? nil : ResponderRegistry.available_responders[key.to_s].new(config, params)
end

def log_error(responder, error)
logger.warn("Error calling #{responder.class}: #{error.message}")
end
Expand Down
2 changes: 1 addition & 1 deletion app/responders/basic_command_responder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ def description
end

def example_invocation
"@#{bot_name} #{command}"
params[:example_invocation] || "@#{bot_name} #{command}"
end
end
2 changes: 1 addition & 1 deletion app/responders/external_service_responder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ def description
end

def example_invocation
"@#{bot_name} #{command}"
params[:example_invocation] || "@#{bot_name} #{command}"
end
end
74 changes: 74 additions & 0 deletions app/responders/external_start_review_responder.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
require_relative "../lib/responder"


class ExternalStartReviewResponder < Responder

keyname :external_start_review

def define_listening
required_params :external_call

@event_action = "issue_comment.created"
@event_regex = /\A@#{bot_name} start review\.?\s*\z/i
end

def process_message(message)
return unless roles_and_issue?
process_external_service(params[:external_call], locals_with_editor_and_reviewers)
end

def roles_and_issue?
unless username?(reviewers_usernames.first.to_s)
respond("Can't start a review without reviewers")
return false
end

unless username?(editor_username)
respond("Can't start a review without an editor")
return false
end

if context.issue_title.match(title_regex)
respond("Can't start a review when the review has already started")
return false
end

true
end

def reviewers_usernames
@reviewers_usernames ||= read_value_from_body("reviewers-list").split(",").map(&:strip)
end

def reviewers_logins
@reviewers_logins ||= reviewers_usernames.map {|reviewer_username| user_login(reviewer_username)}.join(",")
end

def editor_username
@editor_username ||= read_value_from_body("editor")
end

def editor_login
@editor_login ||= user_login(editor_username)
end

def title_regex
params[:review_title_regex] || /^\[REVIEW\]:/
end

def locals_with_editor_and_reviewers
locals.merge({ reviewers_usernames: reviewers_usernames,
reviewers_logins: reviewers_logins,
editor_username: editor_username,
editor_login: editor_login })
end

def description
"Open the review issue"
end

def example_invocation
"@#{@bot_name} start review"
end
end

2 changes: 1 addition & 1 deletion app/responders/github_action_responder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,6 @@ def description
end

def example_invocation
"@#{bot_name} #{command}"
params[:example_invocation] || "@#{bot_name} #{command}"
end
end
4 changes: 1 addition & 3 deletions app/responders/welcome_responder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,7 @@ def process_message(message)

def external_service(service_params)
check_required_params(service_params)
locals_with_issue_data = get_data_from_issue(service_params[:data_from_issue]).merge(locals)

ExternalServiceWorker.perform_async(service_params, locals_with_issue_data)
process_external_service(service_params, locals)
end

def check_required_params(service_params)
Expand Down
5 changes: 0 additions & 5 deletions config/settings-development.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ buffy:
hidden: true
thanks:
hidden: true
assign_reviewer_n:
only: editors
remove_reviewer_n:
only: editors
no_reviewer_text: "TBD"
assign_editor:
only: editors
remove_editor:
Expand Down
1 change: 1 addition & 0 deletions docs/available_responders.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Buffy includes a list of Responders that can be used by configuring them in the
responders/initial_values
responders/welcome
responders/close_issue_command
responders/external_start_review
responders/external_service
responders/github_action
```
Expand Down
13 changes: 8 additions & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,19 @@ buffy:
help:
hello:
hidden: true
assign_reviewer_n:
only: editors
remove_reviewer_n:
only: editors
no_reviewer_text: "TBD"
assign_editor:
only: editors
remove_editor:
only: editors
no_editor_text: "TBD"
list_of_values:
- reviewers:
only: editors
if:
role_assigned: editor
reject_msg: "Can't assign reviewer because there is no editor assigned for this submission yet"
sample_value: "@username"
add_as_assignee: true
invite:
only: eics
set_value:
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/responders/basic_command.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ For example, if you configure the command to be _list editors_, it would respond
```eval_rst
:command: The command this responder will listen to.
:description: *Optional* String to show when the help command is invoked.
:example_invocation: *Optional* String to show as an example of the command being used when the help command is invoked.
:message: *Optional* A text message to use as reply.
:messages: *Optional <Array>* A list of text messages to respond with.
:template_file: *Optional* A template file to use to build the response message.
Expand Down
1 change: 1 addition & 0 deletions docs/responders/external_service.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ _General_
:name: *Required*. The name for this service.
:command: *Required*. The command this responder will listen to.
:description: The description of the service. It will show in the help command if the responder is not hidden.
:example_invocation: Optional string to show as an example of the command being used when the help command is invoked.
:message: An optional message to reply when the command is received, before the external service is called.
```
Expand Down
59 changes: 59 additions & 0 deletions docs/responders/external_start_review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
External start review
=====================

This responder checks for the presence of editor and reviewers in an issue and then delegates the creation of a new review isuue to an external API call.

## Listens to

```
@botname start review
```

## Requirements

The parameters required for the responder to work are the ones configuring the external API call, nested under the `external_call` parameter.


## Settings key

`external_start_review`

## Params

```eval_rst
:external_call: *Required*. Nested under this parameter is the configuration for the external call that will start the review. All available subparams are described in the `external_service docs`_.
:review_title_regex: *Optional*. By default the responder will check that this command has not been triggered from a review issue by checking the title. If it starts with `[REVIEW]:` the command will be rejected. This parameter allows to specify a different string/regex to identify a review issue matching the title.
.. _`external_service docs`: ./external_service.html#params
```


## Examples

**Restricted to editors, respond with a template and close the issue:**
```yaml
...
external_start_review:
only: editors
external_call:
url: "https://test.joss.theoj.org/papers/api_start_review"
query_params:
secret: <%= ENV['TEST_SECRET'] %>
mapping:
id: issue_id
editor: editor_login
reviewers: reviewers_logins
silent: true
template_file: "review_started.md"
close: true
...
```

The responder will call https://test.joss.theoj.org/papers/api_start_review and the response will be passed to the _review_started.md_ template.

## In action

![](../images/responders/external_start_review.png "External start review responder in action")

3 changes: 2 additions & 1 deletion docs/responders/github_action.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ Some parameters are required for the responder to work: the `command` to invoke
## Params
```eval_rst
:command: *Required*. The command this responder will listen to.
:description: The description of the action this command runs. It will show in the help command if the responder is not hidden.
:example_invocation: *Optional* String to show as an example of the command being used when the help command is invoked.
:workflow_repo: *Required*. The repo to run the action on, in *org/name* format.
:workflow_name: *Required*. Name of the workflow to run.
:workflow_ref: Optional. The git ref for the GitHub action to use. Defaults to *main*.
:message: An optional message to reply with once the workflow is triggered.
:description: The description of the action this command runs. It will show in the help command if the responder is not hidden.
:inputs: *<Map>* An optional list of params/values to pass as inputs to the GitHub Action.
:data_from_issue: *<Array>* An optional list of fields from the body of the issue to pass as inputs to the GitHub Action.
:mapping: *<Map>* An optional mapping of variable names to add to the inputs.
Expand Down
33 changes: 30 additions & 3 deletions spec/responder_registry_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
describe ResponderRegistry do

before do
@config = { responders: { "hello" => { hidden: true },
@config = Sinatra::IndifferentHash[responders: { "hello" => { hidden: true },
"assign_editor" => { only: "editors" },
"set_value" => [
{ version: { only: "editors" }},
{ archival: { name: "archive", sample_value: "doi42" }},
{ url: nil }
]
}
}
]
end

describe "initialization" do
Expand All @@ -25,7 +25,8 @@
single_responder = registry.responders.select { |r| r.kind_of?(AssignEditorResponder) }

expect(single_responder.size).to eq(1)
expect(single_responder[0].params).to eq({ only: "editors" })
expect(single_responder[0].params).to eq("only" => "editors")
expect(single_responder[0].params[:only]).to eq("editors")
end

it "should load multiple instances of the same responder" do
Expand Down Expand Up @@ -78,4 +79,30 @@
end
end

describe ".get_responder" do
it "should return a responder by key" do
responder = ResponderRegistry.get_responder(@config, "assign_editor")
expect(responder).to_not be_nil
expect(responder).to be_a(AssignEditorResponder)
expect(responder.params).to eq({ "only" => "editors" })
end

it "should return an instance by name" do
responder = ResponderRegistry.get_responder(@config, "set_value", "archival")
expect(responder).to_not be_nil
expect(responder).to be_a(SetValueResponder)
expect(responder.params).to eq({ "name" => "archive", "sample_value" => "doi42" })
end

it "should return nil if no instance matching key+name" do
expect(ResponderRegistry.get_responder(@config, "set_value")).to be_nil
expect(ResponderRegistry.get_responder(@config, "set_value", "branch")).to be_nil
expect(ResponderRegistry.get_responder(@config, "assign_editor", "topic")).to be_nil
end

it "should return nil if no key or config" do
expect(ResponderRegistry.get_responder({}, :set_value)).to be_nil
expect(ResponderRegistry.get_responder(@config, nil)).to be_nil
end
end
end

0 comments on commit a86460b

Please sign in to comment.