Buffy

Buffy is an editorial bots generator, a service to provide a bot helping scientific journals manage submission reviews.

Buffy is a configurable Ruby application that –once deployed as a web service listening to incoming GitHub webhooks– provides a bot that interacts during the peer-review process with editors, reviewers and authors to help them perform actions on the review, the software being reviewed and its corresponding paper, automating common editorial tasks like those needed by the Journal of Open Source Software, rOpenSci or Scipy.

Buffy is an Open Source project, the code is hosted at GitHub and released under a MIT license.

Installation

Buffy works listening to events received from GitHub and deciding if/how to reply by passing the received payload to different Responders. You can fork Buffy and configure the responders you want to use for any specific repository. There is no need for the Buffy fork to be located in the same GitHub user/organization as the repo where it will be used. To have Buffy ready and listening to events you can install it locally or deploy it to a server or service platform. You’ll need the following components:

  • A GitHub user to use as the bot with admin permissions on the target repo (usually a member of the organization owning the repo).

  • An instance of Buffy running

  • A webhook configured in the GitHub repo’s settings to send events to Buffy

Create the bot GitHub user

This will be the user responding to commands in the reviews repo.

1. Sign up at GitHub and create the bot user:

GitHub's signup pageGitHub's signup page

2. Go to Settings >> Developer settings >> Personal access tokens and create a new token with these scopes: public_repo, repo:invite, read:org, read:user and, if using the approval responder, admin:org (as the app will create teams and invite members to the ropensci organization). Save that token, it will be your BUFFY_GH_ACCESS_TOKEN.

Settings >> Developer settings >> Personal access tokensSettings >> Developer settings >> Personal access tokens

3. Give the bot admin permissions: add it as member of the organization owning the repo where the reviews will take place:

People at GitHub OrganizationPeople at GitHub Organization

Deploy Buffy

Server requirements

Some applications and services must be available to use by Buffy:

  • Redis: To process background jobs Buffy needs redis installed.

  • Gitinspector: The Respository Checks Responder performs a statistical analysis using it.

  • cloc: The Respository Checks Responder can analyze source code, to run this check cloc is used.

Deployment

We will use here Heroku as an example service to deploy Buffy but you can use any other server or platform.

1. Create a new app in heroku linked to the url of your fork of Buffy. Automatically Heroku will use the heroku/ruby buildpack.

  • To process background jobs Buffy needs redis installed, several add-ons providing it are available: Heroku Redis, RedisGreen, Redis To Go, etc.

  • To install the cloc dependency there’s a buildpack for Heroku available here.

  • Gitinspector can be installed using npm. To do so in Heroku, the heroku/nodejs buildpack can be added.

2. In the app settings add the following Config Vars:

    BUFFY_BOT_GH_USER: <the_github_username_of_the_bot>
    BUFFY_GH_ACCESS_TOKEN: <the_access_token_for_the_bot_created_in_the_previous_step>
    BUFFY_GH_SECRET_TOKEN: <a_random_string>
    RACK_ENV: production

2b. You can set the Ruby version to install using the CUSTOM_RUBY_VERSION env var. Unless you need any other specific version, please add also a Config Var named CUSTOM_RUBY_VERSION with the value of the latest version listed in the Buffy tested Ruby versions.

3. You can set Heroku to automatically redeploy when new commits are added. You can also add heroku as a git remote and deploy manually using

    $ git push heroku main

There are detailed instructions in the Deploy section of your Heroku app.

4. You should now have a public URL with Buffy running. You can test that pointing your browser to /status, like for example: https://your-new-buffy-deploy.herokuapp.com/status It should return a simple up and running message.

Configure a webhook to send events from GitHub to Buffy

1. Go to the settings page of the repository where you want to use buffy. Add a new webhook.

Add webhookAdd webhook

2. Configure the new webhook with:

    Payload URL: /dispatch path at your public buffy url
    Content type: application/json
    Secret: The BUFFY_GH_SECRET_TOKEN you configured in the previous step

Select individual events to trigger: issue comments and issues New webhookNew webhook

If everything went well you should have now your bot responding on the reviews issues. Try @botname help for example.

Configuration

Buffy is configured using a simple YAML file containing all the settings needed. The settings file is located in the /config dir and is named settings-<environment>.yml, where <environment> is the name of the environment Buffy is running in, usually set via the RACK_ENV env var. So for a Buffy instance running in production mode, the configuration file will be /config/settings-production.yml

A sample settings file will look similar to this:

buffy:
  env:
    bot_github_user: <%= ENV['BUFFY_BOT_GH_USER'] %>
    gh_access_token: <%= ENV['BUFFY_GH_ACCESS_TOKEN'] %>
    gh_secret_token: <%= ENV['BUFFY_GH_SECRET_TOKEN'] %>
  teams:
    editors: 3824115
    eics: myorg/editor-in-chief-team
  responders:
    help:
    hello:
      hidden: true
    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:
      - version:
          only: editors
          sample_value: "v1.0.0"
      - archive:
          only: editors
          sample_value: "10.21105/joss.12345"
    welcome:

File Structure

The structure of the settings file starts with a single root node called buffy. It contains three main parts:

  • The env node

  • The teams node

  • The responders node

A detailed description of all of them:

Env: General configuration settings

  env:
    bot_github_user: <%= ENV['BUFFY_BOT_GH_USER'] %>
    gh_access_token: <%= ENV['BUFFY_GH_ACCESS_TOKEN'] %>
    gh_secret_token: <%= ENV['BUFFY_GH_SECRET_TOKEN'] %>
    templates_path: ".templates"

The env section is used to declare general key/value settings. For security reasons is a good practice to load the secret values from your environment instead of hardcoding them in the code.

bot_github_user
The name of the bot. It is the GitHub user that will respond to commands. It should have admin permissions on the reviews repo. The default value is reading it from the BUFFY_BOT_GH_USER environment variable.
gh_access_token
The GitHub developer access token for the bot user. The default value is reading it from the BUFFY_GH_ACCESS_TOKEN environment variable.
gh_secret_token
The GitHub secret token configured for the webhook sending events to Buffy. The default value is reading it from the BUFFY_GH_SECRET_TOKEN environment variable.
templates_path
The relative path in the target repo where templates are located. This path is used by responders replying with a message built from a template. The default value is .buffy/templates.

Teams

  teams:
    editors: 3824117
    eics: myorg/editor-in-chief-team
    reviewers: 45363564
    collaborators:
      - user33
      - user42

The optional teams node includes entries to reference GitHub teams, used later to grant access to responders only to users belonging to specific teams. The teams referred here must be visible teams of the organization owner of the repositories where the reviews will take place. Multiple entries can be added to the teams node. All entries follow this simple format:

key: value
Where key is the name for this team in Buffy and value can be:
- The integer id of the team in GitHub (preferred)
- The reference in format organization/name (for example: openjournals/editors)
- An array of user handles

Responders

  responders:
    help:
    hello:
      hidden: true
    assign_reviewers:
      only: editors

The responders node lists all the responders that will be available. The key for each entry is the name of the responder and nested under it the configuration options for that responder are declared.

Common options

All the responders share some options available to all of them. They can also have their own particular configurable parameters (see docs for each responder). The common parameters are:

hidden
Defaults to false. If true this responder won't be listed in the help provided to users.

Usage:

  ...
  secret_responder:
    hidden: true
  ...
only
List of teams (referred by the name used in the teams node) that can have access to the responder. Used to limit access to the responder. If only and authorized_roles_in_issue are not present the responder is considered public and every user in the repository can invoke it.

Usage:

  public_responder:
  available_for_one_team_responder:
    only: editors
  available_for_two_teams_responder:
    only:
      - editors
      - reviewers
authorized_roles_in_issue
List of values in the body of the issue marked with HTML comments that contains user(s) allowed to run the responder. Used to grant access to the responder per issue. If only and authorized_roles_in_issue are not present the responder is considered public and every user in the repository can invoke it.

Usage:

  public_responder:
  restricted_responder:
    only: editors
    authorized_roles_in_issue:
      - author-handle
      - reviewers-list

(restricted_responder can only be called by members of the editors team and by users listed in the issue in the author-handle and reviewers-list HTML-marked fields)

if
This setting is used to impose conditions on the responder. It can include several options:
title:

<String> or <Regular Expresion> Responder will run only if issue’ title matches this.

body:

<String> or <Regular Expresion> Responder will run only if the body of the issue matches this.

value_exists:

<String> Responder will run only if there is a not empty value for this in the issue (marked with HTML comments).

value_matches:

<Hash> Responder will run only if the param values (marked with HTML comments) in the body of the issue matches the ones specified here.

role_assigned:

<String> Responder will be run only if there is a username assigned for the specified value.

labels:

<Array> Responder will be run only if the issue is labeled with all the labels listed here.

reject_msg:

<String> Optional. The response to send as comment if the conditions are not met

Usage:

  # This responder should be invoked only if there's an editor assigned
  # otherwise will reply with a custom "no editor assigned yet" message
  assign_reviewer:
    if:
      role_assigned: editor
      reject_msg: I can not do that because there is no editor assigned yet

  # This responder will run only if issue title includes '[PRE-REVIEW]' and if
  # there is a value for repo-url, ie: <!--repo-url-->whatever<!--end-repo-url-->
  start_review:
    if:
      title: "^\\[PRE-REVIEW\\]"
      value_exists: repo-url

  # This responder will run only if the value for submission_type in the body of
  # the issue matches 'astro', ie: <!--submission_type-->astro<!--end-submission_type-->
  start_review:
    if:
      value_matches:
        submission_type: astro

  # This responder will run only if issue title includes '[REVIEW]' and
  # the issue is labeled as 'accepted
  start_review:
    if:
      title: "^\\[REVIEW\\]"
      labels:
        - accepted
description
Every responder has a default description to be shown using the help_responder. Use this param if you want to use a custom description.

Usage:

  ...
  custom_responder:
    description: "This responder do something"
  ...
example_invocation
Every responder defines an example string showing the command to invoke it, to be listed using the help_responder. Use this param if for some reason you want to use a custom value for the example invocation.

Usage:

  ...
  custom_responder:
    example_invocation: "@botname run performance checks (please run this only on mondays)"
  ...

A complete example:

  # Two responders are configured here:
  #
  # The assign_reviewers responder will respond only when triggered by a user that is
  # member of any of the editors or editors-in-chief teams. It will also respond only
  # in issues with the text "[REVIEW]" in its title and that have a not empty value
  # in its body marked with HTML comments: <!--editor-->@EDITOR_HANDLE<!--end-editor-->
  # Once invoked, it will label the issue with the 'reviewers-assigned' label.
  #
  # The hello responder is configured as hidden, so when calling the help responder the
  # description and usage example of the hello responder won't be listed in the response.
  ...
  responders:
    assign_reviewers:
      only:
        - editors
        - editors-in-chief
      if:
        title: "^\\[REVIEW\\]"
        role_assigned: editor
      add_labels:
        - reviewers-assigned
      description: "Use this command to assign a reviewers once the editor is assigned"
    hello:
      hidden: true
  ...

Several responders also allow adding or removing labels.

Multiple instances of the same responder

Sometimes you want to use a responder more than once, with different parameters. In that case under the name of the responder you can declare an array of instances, and the key for each instance will be passed to the responder as the name parameter.

Example:

The set_value responder uses a name param to change the value to a variable. If declared in the settings file like this:

  responders:
    set_value:
      name: version

It could be invoked with @botname set 1.0 as version.

If you want to use the same responder to change version but also to allow editors to change url you would declare multiple instances in the settings file like this:

  responders:
    set_value:
      - version:
      - url:
          only: editors

Now @botname set 1.0 as version is a public command and @botname set abcd.efg as url is a command available to editors.

Available Responders

Buffy listens to events in the target repo using responders. Every responder is a subclass of the Responder class. Each responder have a define_listening method where the action and/or regex the responder is listening to are defined. The actions a responder takes if called are defined in the process_message method.

Buffy includes a list of Responders that can be used by configuring them in the YAML settings file.

Help

The help responder provides a customizable command to list all the available actions. It only lists options available to the user triggering the responder and only responders not marked as hidden.

Listens to

@botname help

help is the default command, but it is customizable via params.

Settings key

help

Params

help_command:

Optional. The command triggering this responder. Default value is help.

Examples

Simplest use case:

...
  responders:
    help:
...

Custom command:

...
  responders:
    help:
      help_command: commands
...

Now it will reply to @botname commands.

In action

Help responder in action

Hello

A simple responder to reply to user greetings.

Listens to

Hi @botname
Hello @botname

Settings key

hello

Examples

Simplest use case:

...
  responders:
    hello:
...

Hidden from public commands list

...
  responders:
    hello:
      hidden: true
...

In action

Hello responder in action

Basic command

This responder defines a custom command and replies with text messages, optionally using a template. Allows labeling.

Listens to

@botname <command>

For example, if you configure the command to be list editors, it would respond to:

@botname list editors

Settings key

basic_command

Params

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.

data_from_issue:

<Array> An optional list of values that will be extracted from the issue’s body and used to fill the template.

external_call:

Optional Configuration for a external service call. All available subparams are described in the external_service docs.

Examples

Simplest use case:

Reply with a preconfigured text

...
  responders:
    basic_command:
      command: issue complaint
      message: "Please send an email to reports@open.journal"

...

Multiple instances of the responder, multiple replies, using a template to respond:

...
  responders:
    basic_command:
      - code_of_conduct:
          command: code of conduct
          description: Show our community Code of Conduct and Guidelines
          messages:
            - "Our CoC: https://github.com/openjournals/joss/blob/master/CODE_OF_CONDUCT.md"
            - "It's adapted from the Contributor Covenant: http://contributor-covenant.org"
            - "Reports of abusive or harassing behavior may be reported to reports@theoj.org"
      - editor_list:
          command: list editors
          description: List all current topic editors
          template_file: editors.md
...

In action

  • Multiple responses:

Basic command responder in action: multiple responses

  • Replying with a template - The template file:

Basic command responder in action: template file

  • Replying with a template - In use:

Basic command responder in action: responding with a template

Assign editor

Use this responder to update the value of the editor in the body of the issue. Allows labeling.

Listens to

@botname assign @username as editor

Requirements

The body of the issue should have the editor placeholder marked with HTML comments.

<!--editor-->  <!--end-editor-->

Settings key

assign_editor

Params

add_as_assignee:

<Boolean> If true, the editor user will be added as assignee to the issue. Default value is true.

add_as_collaborator:

<Boolean> If true, the editor user will be added as collaborator to the repo. Default value is false.

external_call:

Optional Configuration for a external service call. All available subparams are described in the external_service docs.

Examples

Simplest use case:

...
  responders:
    assign_editor:
...

Restricted to editors:

...
  teams:
    editors: 1111111
...
  responders:
    assign_editor:
      only: editors
...

Restrict access to editors and add user as assignee and collaborator:

...
  responders:
    assign_editor:
      only: editors
      add_as_collaborator: true
...

In action

  • Initial state:

Assign editor responder in action: Before

  • Invocation:

Assign editor responder in action: Invocation

  • Final state:

Assign editor responder in action: After

Remove editor

This responder removes the assigned editor from the body of the issue (the one that can be assigned using the Assign Editor responder). The user will also be removed from the issue’s assignees. Allows labeling.

Listens to

@botname remove editor

Requirements

In the body of the issue the editor should be enclosed in HTML comments.

...
<!--editor--> @sarah_m_g <!--end-editor-->
...

Settings key

remove_editor

Params

no_editor_text:

The text that will go in the editor place to state there’s no one assigned. The default value is Pending.

Examples

Simplest use case:

...
  responders:
    remove_editor:
...

Action restricted to editors:

...
  teams:
    editors: 1111111
...
  responders:
    remove_editor:
      only: editors
...

Restrict access to editors, use custom text when there’s not editor:

...
  responders:
    remove_editor:
      only: editors
      no_editor_text: To be decided
...

In action

  • Initial state:

Remove editor responder in action: Before

  • Invocation:

Remove editor responder in action: Invocation

  • Final state:

Remove editor responder in action: After

Reviewers list

This responder adds/removes usernames to/from the list of reviewers in the body of the issue. Allows labeling.

Listens to

@botname add <username> as reviewer
@botname add <username> to reviewers
@botname remove <username> from reviewers

Requirements

The body of the issue should have the target field placeholder marked with HTML comments.

<!--reviewers-list-->  <!--end-reviewers-list-->

Settings key

reviewers_list

Params

sample_value:

<String> An optional sample value string for the target field. It is used for documentation purposes when the Help responder lists all available responders. Default value is @username.

no_reviewers_text:

The text that will go in the reviewers list place to state there are no reviewers assigned yet. The default value is Pending.

add_as_assignee:

<Boolean> Optional. If true, when adding a new reviewer will be added as assignee to the issue. Default value is false.

add_as_collaborator:

<Boolean> Optional. If true, when adding a new reviewer will be added as collaborator to the repo. Default value is false.

Examples

Simplest case:

...
  responders:
    reviewers_list:
...

With different options:

...
  responders:
    reviewers_list:
      only: editors
      sample_value: "@reviewer-login"
      add_as_assignee: true
...

In action

  • Initial state:

Reviewers list responder in action: initial state

  • Adding a reviewer:

Reviewers list responder in action: adding a reviewer

  • Reviewer added:

Reviewers list responder in action: added

  • Removing a reviewer:

Reviewers list responder in action: removing a reviewer

  • Reviewer removed:

Reviewers list responder in action: removed

Invite

This responder creates a repo invitation for a user to be added as collaborator so they have the needed permissions to edit comments. Use this responder to send an invitation to a user to collaborate in the review.

Listens to

@botname invite @username

Settings key

invite

Examples

Simplest use case:

...
  responders:
    invite:
...

Action restricted to users in the editors team:

...
  teams:
    editors: 1111111
...
  responders:
    invite:
      only: editors
...

In action

Invite responder in action

Set value

This responder can be used to update the value of any field in the body of the issue. Allows labeling.

Listens to

@botname set <value> as <name>

For example, if you configure this responder to change the value of the version, it would respond to:

@botname set v1.0.3 as version

Requirements

The body of the issue should have the target field placeholder marked with HTML comments.

<!--<name>-->  <!--end-<name>-->

Following the previous example if the name of the field is version:

<!--version-->  <!--end-version-->

Settings key

set_value

Params

name:

Required. The name of the target field in the body of the issue. It can be set using the name: keyword, or via the name of each instance if there are several instances of this responder specified in the settings file.

if_missing:

Optional Strategy when value placeholders are not defined in the body of the issue. Valid options: append (will add the value at the end of the issue body), prepend (will add the value at the beginning of the issue body) , error (will reply a not-found message). If this param is not present nothing will be done if value placeholder is not found.

aliased_as:

Optional. The name of the value to be used in the command, in case it is different from the target field placeholder marked with HTML comments.

heading:

if the value placeholder is missing and the if_missing strategy is set to append or prepend, when adding the value it will include this text as heading instead of just the value name.

sample_value:

A sample value string for the target field. It is used for documentation purposes when the Help responder lists all available responders. Default value is xxxxx.

template_file:

Optional A template file to use to build the response message (name and value are passed to it).

external_call:

Optional Configuration for a external service call. All available subparams are described in the external_service docs.

Examples

Simplest use case:

...
  responders:
    set_value:
      name: version
      sample_value: v1.0.1
...

Multiple instances of the responder, some of them restricted to editors:

...
  responders:
    set_value:
      - version:
          only: editors
          sample_value: "v1.0.0"
      - archive:
          only: editors
          sample_value: "10.21105/joss.12345"
          if_missing: prepend
          heading: "Archive DOI"
      - repository:
          sample_value: "github.com/openjournals/buffy"
...

In action

  • Initial state:

Set value responder in action: Before

  • Invocation:

Set value responder in action: Invocation

  • Final state:

Set value responder in action: After

List of values

This responder adds values to/removes values from a list in the body of the issue. Allows labeling.

Listens to

@botname add <value> to <list-name>
@botname remove <value> from <list-name>

For example, if you configure this responder to add/remove values for the authors list, it would respond to:

@botname add @username to authors

Requirements

The body of the issue should have the target field placeholder marked with HTML comments.

<!--<listname>-list-->  <!--end-<listname>-list-->

Following the previous example if the name of the field is authors:

<!--authors-list-->  <!--end-authors-list-->

Settings key

list_of_values

Params

name:

Required. The name of the list. It can be set using the name: keyword, or via the name of each instance if there are several instances of this responder specified in the settings file.

sample_value:

An optional sample value string for the target field. It is used for documentation purposes when the Help responder lists all available responders. Default value is xxxxx.

add_as_assignee:

<Boolean> Optional. If true and the value is a user name, it will be added as assignee to the issue. Default value is false.

add_as_collaborator:

<Boolean> Optional. If true and the value is a user name, it will be added as collaborator to the repo. Default value is false.

Examples

Simple case: A single list

...
  responders:
    list_of_values:
      name: authors
...

Several lists with different options:

...
  responders:
    list_of_values:
      - versions:
          sample_value: "v1.0.2"
      - authors
          only: editors
          sample_value: "@username"
          add_as_collaborator: true
...

In action

  • Initial state:

List of values responder in action: initial state

  • Adding to the list:

List of values responder in action: adding an element

  • Intermediate state:

List of values responder in action: intermediate state

  • Removing from the list:

List of values responder in action: removing an element

  • Final state:

List of values responder in action: final state

List team members

This responder replies with a list of members from a GitHub team

Listens to

@botname <command>

For example, if you configure the command to be list editors, it would respond to:

@botname list editors

Settings key

list_team_members

Params

command:

The command this responder will listen to.

team_id:

The id of the GitHub team to be listed.

heading:

Optional Heading for the replied list.

description:

Optional String to show when the help command is invoked.

Examples

List editors team members with custom heading

...
  responders:
    list_team_members:
      command: list editors
      team_id: 3824115
      heading: Current journal editors
...

In action

List team members responder in action

Add/Remove assignee

This responder adds and removes users to the assignees list of the issue. Allows labeling.

Listens to

@botname add assignee: @username
@botname remove assignee: @username

Requirements

Only users that are collaborators in the target issue can be added as assignees. Otherwise the responder will reply with a not enough permissions message.

Settings key

add_remove_assignee

Examples

Simplest use case:

...
  responders:
    add_remove_assignee:
...

Hidden from commands list and restricted to editors:

...
  responders:
    add_remove_assignee:
      only: editors
      hidden: true
...

In action

Add/Remove assignee responder in action

Reviewer checklist comment

This responder adds a reviewer checklist editing the comment triggering the responder if the author of the comment is a reviewer. This way of adding checklists (instead of adding them to the body of the issue) does not require the reviewers to be collaborator of the repository, as they will be able to edit their own comments to update the progress of the checklist.

Listens to

@botname generate my checklist

Requirements

The checklist is read from a template file that should be available in the repository.

Settings key

reviewer_checklist_comment

Params

template_file:

Required. The name of the template file to edit the comment with.

data_from_issue:

<Array> An optional list of values that will be extracted from the issue’s body and used to fill the template.

command:

Optional. The command triggering this responder. Default is generate my checklist

Examples

Simplest use case:

...
  responders:
    add_remove_checklist:
      template_file: reviewer_checklist.md
...

Using info from the body to fill in the template. Custom command:

...
  responders:
    add_remove_checklist:
      command: create reviewer checklist
      template_file: reviewer_checklist.md
      data_from_issue:
        - target-repository
        - author-handle
...

In action

  • The template:

Reviewer checklist comment responder in action: The template

  • Invocation:

Reviewer checklist comment responder in action: Invocation

  • Comment edited by the bot:

Reviewer checklist comment responder in action: After

Add/Remove checklist

This responder adds and removes checklists for reviewers at the end of the body of the issue. Allows labeling.

Listens to

@botname add checklist for @username
@botname remove checklist for @username

Requirements

The checklist is read from a template file that should be available in the repository.

Settings key

add_remove_checklist

Params

template_file:

Required. The name of the template file to append to the body.

data_from_issue:

<Array> An optional list of values that will be extracted from the issue’s body and used to fill the template.

Examples

Simplest use case:

...
  responders:
    add_remove_checklist:
      template_file: reviewer_checklist.md
...

Using info from the body to fill in the template. Action restricted to editors:

...
  responders:
    add_remove_checklist:
      only: editors
      template_file: reviewer_checklist.md
      data_from_issue:
        - target-repository
        - author-handle
...

In action

  • The template:

Add/Remove checklist responder in action: The template

  • Initial state:

Add/Remove checklist responder in action: Before

  • Invocation:

Add/Remove checklist responder in action: Invocation

  • Final state:

Add/Remove checklist responder in action: After

Label command

This responder defines a custom command to add and/or remove labels to the issue when invoked.

Listens to

@botname <command>

For example, if you configure the command to be review successful, it would respond to:

@botname review successful

Settings key

label_command

Params

command:

The command this responder will listen to.

add_labels:

<Array> A list of text labels to add to the issue.

remove_labels:

<Array> A list of text labels to remove from the labels of the issue.

Examples

Simplest use case:

Just add a label.

...
  responders:
    label_command:
      command: review successful
      add_labels:
        - recommend publication
...

Multiple instances of the responder, restricted to editors, adding and removing labels:

...
  responders:
    label_command:
      - review_ok:
          only: editors
          command: review successful
          add_labels:
            - reviewed
            - recommend publication
            - pending publication
          remove_labels:
            - ongoing
            - pending review
      - review_nok:
          only: editors
          command: review failed
          add_labels:
            - recommend rejection
...

In action

Label command responder in action

Check references

This responder checks (asynchronously) the validity of the DOIs from a list of BibTeX entries (a paper’s references file).

Listens to

@botname check references

A non-default branch can be specified to look for the paper’s files in it:

@botname check references from branch <custom-branch-name>

Requirements

The target repository should include a paper.md or paper.tex file and its corresponding references file (paper.bib or paper.yml) with the BIbTeX entries.

The body of the issue should have the url of the repository with the paper’s files marked with HTML comments.

<!--target-repository--> URL HERE <!--end-target-repository-->

Settings key

check_references

Params

url_field:

The optional name of the field marked with HTML comments where the URL of the repository with the paper is located. By default if this setting is not present, the value will be target-repository. Meaning Buffy will look for a string in the body of the issue between <!–target-repository–> and <!–end-target-repository–> HTML comments.

branch_field:

The optional name of the field marked with HTML comments where the name of the branch is located. Defaults to branch (so Buffy will look for <!–branch–> and <!–end-branch–> in the body of the issue). If the setting is not present or the branch field is not found in the body of the issue, the default branch of the git repo will be used.

Examples

Simplest case:

...
  check_references:
...

Buffy will clone the git repository specified between <!--target-repository--> and <!--end-target-repository--> marks and check the DOIs for all entries in the paper.bib file.

Example customizing fields:

...
  check_references:
      url_field: software-location
      branch_field: branch-to-review
...

Buffy will clone the git repository specified between <!--software-location--> and <!--end-software-location--> marks, then checkout into the branch specified between <!--branch-to-review--> and <!--end-branch-to-review--> and then check the DOIs for all entries in the paper.bib file.

In action

  • Issue body with the repository's URL:

Check references responder in action: Body of the issue

  • In use:

Check references responder in action: in use

  • With non-default branch:

Check references responder in action with non-default branch: in use

Repository checks

This responder performs (asynchronously) several checks on the target repository.

Listens to

@botname check repository

A non-default branch can be specified to run the checks on it:

@botname check repository from branch <custom-branch-name>

Requirements

The body of the issue should have the url of the repository marked with HTML comments.

<!--target-repository--> URL HERE <!--end-target-repository-->

Settings key

repo_checks

Params

checks:

An optional list (Array) of checks to perform. If non present or empty all available checks will be run (see available checks for the values to use in the config file).

url_field:

The optional name of the field marked with HTML comments where the URL of the repository with the paper is located. By default if this setting is not present, the value will be target-repository. Meaning Buffy will look for a string in the body of the issue between <!–target-repository–> and <!–end-target-repository–> HTML comments.

branch_field:

The optional name of the field marked with HTML comments where the name of the branch is located. Defaults to branch (so Buffy will look for <!–branch–> and <!–end-branch–> in the body of the issue). If the setting is not present or the branch field is not found in the body of the issue, the default branch of the git repo will be used.

Available checks

The following values are valid for the :checks list:

  • repo summary: This check performs an analysis of the source code and list authorship, contributions and file types information.

  • languages: This will detect the languages used in the repository and tagged the issue with the top three used languages.

  • wordcount: This will count the number of words in the paper file.

  • license: This will look for an Open Source License in the target repo and reply an error message if no license is found.

  • statement of need: This check will look for an Statement of need section in the paper content.

Examples

Simplest case:

...
  repo_checks:
...

Buffy will clone the git repository specified between <!--target-repository--> and <!--end-target-repository--> HTML comments and run all available checks.

Run selected checks:

...
  repo_checks:
    checks:
      - repo summary
      - languages
...

Buffy will only run the repo summary and the languages checks.

In action

Repository checks responder in action

Thanks

This responder replies when a user thanks the bot.

Listens to

Thanks @botname
Thank you @botname
@botname thanks
@botname thank you

Settings key

thanks

Params

reply:

The message the bot will send back. Default value is “You are welcome”.

Examples

Simplest use case:

...
  responders:
    thanks:
...

Custom message and hidden from public commands list:

...
  responders:
    thanks:
      reply: "No problem, I'm here to help!"
      hidden: true
...

In action

Thanks responder in action

Reminders

This responder allows to schedule a reminder for the author or a reviewer to return to a review after a certain period of time (supported units: days and weeks). The command will only work if the mentioned user is an author, a reviewer for the submission or the sender of the message (so editors can set reminders for themselves).

Listens to

@botname remind @username in 2 weeks
@botname remind @reviewer in 10 days

Settings key

reminders

Params

reviewers:

Optional. The HTML-comment value name in the body of the issue to look for reviewers. Default value is reviewers-list.

auhors:

Optional. The HTML-comment value name in the body of the issue to look for authors. Default value is author-handle.

Examples

Simple use case:

...
  responders:
    reminders:
      only: editors
...

Custom html fields:

...
  responders:
    reminders:
      only: editors
      authors:
        - author1
        - author2
...

Now it will allow to set a reminder for all the users listed in the body of the issue in reviewers-list, author1 and author2 HTML fields.

In action

  • Scheduling a reminder:

Reminders responder in action: Scheduling a reminder

  • The reminder:

Reminders responder in action: Reviewer reminder

Initial values

This responder acts when a new issue is opened. It checks for the presence of placeholders in the body of the issue for all the configured values.

Listens to

New issue opened event.

Settings key

initial_values

Params

The values parameter is mandatory.

values:

An array of values. Optionally each value can be individually customized.

For each value listed under values this options can be provided:

heading:

Optional. If the value placeholder is missing when adding the value it will include this text as heading instead of just the value name. Default value is the value name capitalized bold.

value:

Optional Value to add inside the HTML comments. Default value is empty string.

action:

Optional Strategy when value placeholders are not defined in the body of the issue. Valid options: append (will add the value at the end of the issue body), prepend (will add the value at the beginning of the issue body). Default value is prepend

warn_if_empty:

Optional If set to true if the placeholder for this value is not present or present but empty a new comment will be replied to the issue warning of the missing value. Default is false.

Examples

Simplest use case:

Verify presence of <!--version--><!--end-version--> and <!--target-repository--><!--end-target-repository--> in the body of the issue:

...
  responders:
    initial_values:
      values:
        - version
        - target-repository
...

Multiple values with custom properties:

...
  responders:
    initial_values:
      values:
        - version:
          - value: "vX.X.X"
          - action: append
        - author1:
          - heading: "Author Github handle:"
          - warn_if_empty: true
        - target-repository
        - archive
        - package-name:
          - warn_if_empty: true
...

In action

Using the Multiple values with custom properties example config:

  • Initial state:

Initial values responder in action: Before

Actual text in the initial body of the issue:

Initial values responder in action: Before: issue body text

  • Final state:

Initial values responder in action: After

Actual text in the final body of the issue:

Initial values responder in action: After: issue body text

Result:

  • version has been appended at the end of the body with a value of vX.X.X

  • author1 has been added with the custom heading

  • target-repository was already present, so nothing has been done with it

  • archive was already present, so nothing has been done with it

  • package-name was not present so it has been prepended.

  • New comment was created with a warning of missing values for package-name and author1

Welcome

This responder acts when a new issue is opened. It can reply with text messages, using a template, create a background job to asynchronously call an external service’s API and/or triggering another responder.

Allows labeling.

Listens to

New issue opened event.

Settings key

welcome

Requirements

When using a template to respond:

When rendering a template a map of values will be passed to it:

  • issue_id: The id of the issue

  • repo: the name of the repository

  • sender: the handle of the user creating the issue

  • bot_name: the name of the bot user responding

If the template needs some other value included in the body of the issue, they can be declared using the data_from_issue param and those values will be passed to the template too, if can be extracted from the body. They can be used in the template using the syntax:

{{variable_name}}

In order to use a template, Buffy will look for the file declared in the template_file param in the target repo, in the location specified with the template_path setting (by default .buffy/templates). In short: the template_file should be located in the template_path.

The values needed by the template that are listed in the data_from_issue param must be extractable: they have to be enclosed in HTML comments:

<!--<name>--> Info to extract <!--end-<name>-->

So, for example, if you want to use the value of version in the template, the body of the issue must include it inside HTML comments:

<!--version--> v2.1 <!--end-version-->

Then it should be declared in the settings file, listed in the data_from_issue param:

  responders:
    welcome:
      template_file: welcome.md
      data_from_issue:
        - version

And can then be used in the template:

Thank you for your submission, we will review the {{version}} release of your software.
When invoking an external service:

Some parameters are required for the external call to work: the name of the service and the url of the call, both configured in the settings YAML file nested under the external_service param.

Similarly to the External Service responder if the call is successful the response is posted as a comment in the issue (optionally using a template).

You can configure a template file as a response after the external API call, this template is configured separately from the previous general response template. The response from the external service should be in JSON format. It will be parsed and the resulting hash values will be passed to the template.

Params

For replying with plain text message(s):

message:

A text message to use as reply.

messages:

<Array> A list of text messages to respond with.

To reply with a template file:

template_file:

The name of the template file to use to build the response message.

data_from_issue:

<Array> An optional list of values that will be extracted from the issue’s body and used to fill the template.

Calling an external service:

external_service:

All the configuration for the service is nested under this param. Posible options are:

name:

Required. The name for this service.

url:

Required. The url to call.

method:

The HTTP method to use. Valid values: [get, post]. Default is post.

template_file:

The optional template file to use to build the response message after the external call.

headers:

<Array> An optional list of key: value pairs to be passed as headers in the external service request.

data_from_issue:

<Array> An optional list of values that will be extracted from the issue’s body and used to fill the template.

query_params:

<Array> An optional list of params to add to the query of the external call. Common place to add API_KEYS or other authentication info.

mapping:

<Array> An optional mapping of variable names in the query of the external service call.

Running other responder(s):

run_responder:

Allows to call a different responder. Subparams are:

responder_key:

Required. The key to find the responder in the config file.

responder_name:

Optional. The name of the responder in the config file if there are several instances under the same responder key.

message:

Optional. The message to trigger the responder with.

If you want to run multiple responders, use an array of these subparams.

General:

close:

<Boolean> Optional parameter, if true the responder will close the issue. Default is false.

check_references:

Optional. If present the validity of the DOIs from the paper’s references file will be checked.

repo_checks:

Optional. If present the responder will perform (asynchronously) several checks on the target repository. You can configure which checks to perform using nested params. Available options are those of the repository_checks responder

hidden:

Is true by default.

Examples

Simplest use case:

...
  responders:
    welcome:
      message: "Thanks for your submission!"
...

Multiple messages and a template:

...
  responders:
    welcome:
      messages:
        - "You can list all the available commands typing `@botsci help`"
        - "The review will start once two reviewers are assigned, please stay tuned."
      template_file: welcome.md
      data_from_issue:
        - repository
        - version
...

Calling an external service:

...
  responders:
    welcome:
      external_service:
        url: https://dummy-external-service.herokuapp.com/code-analysis
        method: post
        query_params:
          secret: A1234567890Z
        data_from_issue:
          - target-repo
        mapping:
          id: issue_id
...

When a new issue is created the responder will send a POST request to https://dummy-external-service.herokuapp.com/code-analysis with a JSON body:

{
 "secret": "A1234567890Z", # declared in the query_params setting
 "target-repo":"...",      # the value is extracted from the body of the issue
 "id":"...",               # the value corresponds to issue_id, it has been mapped to id
 "repo":"...",             # the origin repo where the invocation happend
 "sender":"...",           # the user invoking the command
 "bot_name":"...",         # the bot user name that will be responding
}

And the response from the external service will posted as a comment in the original issue.

In action

Text messages and template file:
  • The template file:

Welcome responder in action, the template

  • In use (template + 2 messages):

Welcome responder in action: text messages and template response

Calling an external service:

Welcome responder in action: external service

Goodbye

This responder acts when a issue is closed. It can reply with text messages, using a template or creating a background job to asynchronously call an external service’s API.

Allows labeling.

Listens to

Issue closed event.

Settings key

goodbye

Requirements

When invoking an external service:

Some parameters are required for the external call to work: the name of the service and the url of the call, both configured in the settings YAML file nested under the external_service param.

Similarly to the External Service responder if the call is successful the response is posted as a comment in the issue (optionally using a template).

You can configure a template file as a response after the external API call, this template is configured separately from the previous general response template. The response from the external service should be in JSON format. It will be parsed and the resulting hash values will be passed to the template.

Params

For replying with plain text message(s):

message:

A text message to use as reply.

messages:

<Array> A list of text messages to respond with.

To reply with a template file:

template_file:

The name of the template file to use to build the response message.

data_from_issue:

<Array> An optional list of values that will be extracted from the issue’s body and used to fill the template.

Calling an external service:

external_service:

All the configuration for the service is nested under this param. Posible options are:

name:

Required. The name for this service.

url:

Required. The url to call.

method:

The HTTP method to use. Valid values: [get, post]. Default is post.

template_file:

The optional template file to use to build the response message after the external call.

headers:

<Array> An optional list of key: value pairs to be passed as headers in the external service request.

data_from_issue:

<Array> An optional list of values that will be extracted from the issue’s body and used to fill the template.

query_params:

<Array> An optional list of params to add to the query of the external call. Common place to add API_KEYS or other authentication info.

mapping:

<Array> An optional mapping of variable names in the query of the external service call.

Examples

Simplest use case:

...
  responders:
    goodbye:
      message: "Congratulations on the acceptance of your paper!"
...

Multiple messages and a template only if label present:

...
  responders:
    goodbye:
      if:
        labels:
          - accepted
      messages:
        - "Congratulations on the acceptance of your paper!"
        - "Review process finished. Closing the issue."
      template_file: goodbye.md
      data_from_issue:
        - repository
        - doi
...

Calling an external service:

...
  responders:
    goodbye:
      external_service:
        url: https://dummy-external-service.herokuapp.com/code-analysis
        method: post
        query_params:
          secret: A1234567890Z
        data_from_issue:
          - target-repo
        mapping:
          id: issue_id
...

When a new issue is closed the responder will send a POST request to https://dummy-external-service.herokuapp.com/code-analysis with a JSON body:

{
 "secret": "A1234567890Z", # declared in the query_params setting
 "target-repo":"...",      # the value is extracted from the body of the issue
 "id":"...",               # the value corresponds to issue_id, it has been mapped to id
 "repo":"...",             # the origin repo where the invocation happend
 "sender":"...",           # the user invoking the command
 "bot_name":"...",         # the bot user name that will be responding
}

And the response from the external service will posted as a comment in the original issue.

In action

Text messages and template file:
  • Replying with a template once the issue is closed:

Goodbye responder in action: template response

Close issue command

This responder replies to a specific command closing the issue and possibly adding some labels. Allows labeling.

Listens to

@botname <command>

For example, if you configure the command to be reject, it would respond to:

@botname reject

Settings key

close_issue_command

Params

command:

The command this responder will listen to.

description:

Optional String to show when the help command is invoked (if the responder is not hidden).

Examples

Simplest use case:

Just close the issue.

...
  responders:
    close_issue_command:
      command: reject
...

Close issue, add labels, restrict access to editors:

...
  responders:
    close_issue_command:
      only: editors
      command: reject
      add_labels:
        - rejected
...

In action

Close issue command responder in action

Update comment

This responder edits the comment triggering the responder updating it with the content of a customizable template file.

These updates of the original comment are useful to add content (for instance: checklists) to be modified/updated by the original author of the comment, as they are always allowed to edit their own comments, not requiring to add them as collaborator of the repository/organization.

Listens to

@botname <command>

For example, if you configure the command to be list pre-acceptance tasks, it would respond to:

@botname list pre-acceptance tasks

Requirements

The response is generated using a template file that should be available in the repository.

Settings key

update_comment

Params

command:

Required. The command this responder will listen to.

template_file:

Required. The name of the template file to edit the comment with.

description:

Optional String to show when the help command is invoked.

Examples

Simplest use case:

...
  responders:
    update_comment:
      command: list tasks
      template_file: tasks.md
...

Limiting use to editors team and adding info to use in the template:

...
  responders:
    add_remove_checklist:
      only: editors
      command: create pre-acceptance steps checklist
      template_file: editor_final_checklist.md
      data_from_issue:
        - target-repository
        - author-handle
...

In action

  • Invocation:

Update comment responder in action: Invocation

  • Comment edited by the bot:

Update comment responder in action: Comment updated by the bot

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

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.

Examples

Restricted to editors, respond with a template and close the issue:

...
  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

External start review responder in action

External service

This responder creates a background job to asynchronously call an external service’s API. If the call is successful the response is posted as a comment in the issue (optionally using a template).

Listens to

@botname <command>

For example, if you configure the command to be analyze code, it would respond to:

@botname analyze code

Requirements

Some parameters are required for the responder to work: the name of the service, the command to invoke it, and the url of the call. All can be set using the settings YAML file.

If using a template

If you want to use a template to respond, Buffy will look for the file declared in the template_file param in the target repo, in the location specified with the template_path setting (by default .buffy/templates). In short: the template_file should be located in the template_path.

The response from the external service should be in JSON format. It will be parsed and the resulting hash values will be passed to the template, where they can be used with the syntax:

{{variable_name}}

Settings key

external_service

Params

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.

Configuring the request

url:

Required. The url to call.

method:

The HTTP method to use. Valid values: [get, post]. Default is post.

headers:

<Array> An optional list of key: value pairs to be passed as headers in the external service request.

query_params:

<Array> An optional list of params to add to the query of the external call. Common place to add API_KEYS or other authentication info.

data_from_issue:

<Array> An optional list of values that will be extracted from the issue’s info or issue’s body and sent as query params to the service call. Available info includes: issue_id, issue_author, repo, sender, bot_name, and any variable included in the body of the issue. Also if the command matches any data it will be available as match_data_1, match_data_2, etc.

mapping:

<Array> An optional mapping of variable names in the query of the external service call.

Configuring the response

template_file:

The optional template file to use to build the response message if the response from the external service is successful.

success_msg:

Optional message to respond with if the service call is successful.

error_msg:

Optional message to respond with if the service call fails with a 400/500 response.

silent:

<Boolean> Optional parameter, if true the responder won’t reply after the external service is called (template_file, success_msg and error_msg will overwrite this if present). Default is false.

add_labels:

<Array> Optional parameter. Labels to add to the issue if the external service call is successful.

remove_labels:

<Array> Optional parameter. Labels to remove from the issue if the external service call is successful.

close:

<Boolean> Optional parameter, if true the responder will close the issue if the external service call is successful. Default is false.

Examples

A simple case using a template:

...
  external_service:
      name: cat_facts
      command: tell me something about cats
      description: Random facts about cats
      url: "https://cat-fact.herokuapp.com/facts/random"
      method: get
      query_params:
          animal_type: cat
          amount: 1
      template_file: cats.md
...

The request will be https://cat-fact.herokuapp.com/facts/random?animal_type=cat&amount=1 and the response will be passed to the cats.md template.

A complete example:

...
  external_service:
      - code_quality:
          only: editors
          command: analyze code
          description: Reports on the quality of the code
          message: Inspecting code...
          url: https://dummy-external-service.herokuapp.com/code-analysis
          method: post
          query_params:
            secret: A1234567890Z
          data_from_issue:
            - target-repo
          mapping:
            id: issue_id
...

Once the responder is invoked it will reply with “Inspecting code…” as a comment in the issue. Later, a POST request will be sent to https://dummy-external-service.herokuapp.com/code-analysis with a JSON body:

{
 "secret": "A1234567890Z", # declared in the query_params setting
 "target-repo":"...",      # the value is extracted from the body of the issue
 "id":"...",               # the value corresponds to issue_id, it has been mapped to id
 "repo":"...",             # the origin repo where the invocation happend
 "sender":"...",           # the user invoking the command
 "bot_name":"...",         # the bot user name that will be responding
}

And the response will posted as a comment in the original issue.

In action

  • In use:

External service responder in action: in use

With a template as response
  • The template file:

External service responder in action with template response: the template

  • The JSON response:

External service responder in action with template response: API response

  • In use:

External service responder in action with template response: in use

GitHub Action

This responder triggers workflow run on a GitHub Action using the GitHub API. Optionally if the call is successful (not the result of the workflow run but the call to trigger it) a reply message can be posted as a comment in the issue. Allows labeling.

Listens to

@botname <command>

For example, if you configure the command to be compile pdf, it will respond to:

@botname compile pdf

Requirements

Some parameters are required for the responder to work: the command to invoke it, and the workflow_repo and workflow_name values to identify the action to run. All can be set using the settings YAML file.

Settings key

github_action

Params

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.

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.

You can use this action to run other responder(s) after after the GitHub action is triggered:

run_responder:

Allows to call a different responder. Subparams are:

responder_key:

Required. The key to find the responder in the config file.

responder_name:

Optional. The name of the responder in the config file if there are several instances under the same responder key.

message:

Optional. The message to trigger the responder with.

If you want to run multiple responders, use an array of these subparams.

Examples

A complete example:

...
  github_action:
    only: editors
    command: compile pdf
    description: Generates a PDF based on the paper.md file in the repository
    workflow_repo: openjournals/reviews
    workflow_name: compile-pdf.yml
    inputs:
      file: paper.md
    data-from-issue:
      - branch
      - target_repository
    mapping:
      repository: target_repository
      number: issue_id
...

Once the responder is invoked it triggers the compile-pdf.yml workflow on the openjournals/reviews repository passing to it the file, repository, branch and number inputs.

Wrong command

This is a special responder that replies when Buffy receives a command directed to the bot that no responder understand. By default it replies with:

I'm sorry human, I don't understand that. You can see what commands I support by typing:

@botname help

But the reply can be configured to be a custom message or to use a template.

Listens to

@botname whatever is not a command to other responder

Settings key

If using default reply this responder doesn’t need to be added to the config file. Otherwise:

wrong_command

Params

ignore:

Optional. If true this responder won’t act. Default value: false.

template_file:

Optional. A template file to use to build the response message.

message:

Optional. A text message to use as reply.

Examples

Simplest use case:

Nothing added to the config file, it will reply the default response

...
  responders:

...

Deactivate responder:

...
  responders:
    wrong_command:
      ignore: true
...

Use custom message:

...
  responders:
    wrong_command:
      message: "Say what?"
...

In action

  • Unknown command:

Wrong command responder in action: default reply

ROpenSci :: Reviewers & due date

This responder can be used to add/remove a user to/from the reviewers list in the body of the issue. It also sets a due date for the review and updates that info in the body of the issue and in the reply comment. This responder will also update Airtable adding entries to the reviewers and reviews tables, and creating if still not present entries in the packages and authors tables. Allows labeling, that will take effect when the second reviewer is assigned.

Listens to

@botname add @username to reviewers
@botname remove @username from reviewers

Requirements

The body of the issue should have a couple of placeholders marked with HTML comments: the reviewers-list and the due-dates-list

<!--reviewers-list-->  <!--end-reviewers-list-->
<!--due-dates-list-->  <!--end-due-dates-list-->

Settings key

ropensci_reviewers

Params

due_date_days:

<Integer> Optional. The number of days from the moment a reviewer is assigned to the due date for the review. Default value is 21 (three weeks).

sample_value:

Optional. A sample value string for the username field. It is used for documentation purposes when the Help responder lists all available responders. Default value is xxxxx.

no_reviewer_text:

Optional. The text that will go in the removed reviewer place to state there’s no one assigned. Default value is TBD.

add_as_assignee:

<Boolean> Optional. If true, the new reviewer will be added as assignee to the issue. Default value is false.

add_as_collaborator:

<Boolean> Optional. If true, the new reviewer it will be added as collaborator to the repo. Default value is false.

reminder:

Used to configure automatic reminders. See next.

Automatic reminders: To configure an automatic reminder for the reviewers the reminder param can be used with two nested options under it:

days_before_deadline:

<Integer> Optional. Configure when the reminder will be posted (how many days before the dealine for the review). Default value: 4

template_file:

The template file to use for the reminder (will receive variables: reviewer, days_before_deadline and due_date).

For the Airtable connection to work two parameters must be present in the env section of the settings file, configured using environment variable:

...
  env:
    airtable_api_key: <%= ENV['AIRTABLE_API_KEY'] %>
    airtable_base_id: <%= ENV['AIRTABLE_BASE_ID'] %>
...

Examples

Simplest case:

...
  responders:
    ropensci_reviewers:
...

With labeling, changing no_reviewer_text, setting a reminder, limiting access and only if there’s an editor already assigned:

...
  responders:
    ropensci_reviewers:
      only:
        - editors
      if:
        role_assigned: editor
      no_reviewer_text: "Pending"
      add_labels:
        - 3/reviewer(s)-assigned
      remove_labels:
        - 2/seeking-reviewer(s)
      reminder:
        days_before_deadline: 4
        template_file: reminder.md
...

In action

  • Initial state:

Issue’s body with placeholders ROpenSci :: Reviewers & due date: Initial state

  • Invocation:

Assigns first reviewer ROpenSci :: Reviewers & due date: first assignment

  • Assigning second reviewer applies labeling: ROpenSci :: Reviewers & due date: second reviewer and labeling

  • Final state:

Issue’s body with reviewers and due dates info ROpenSci :: Reviewers & due date: Final state

ROpenSci :: Set due date

This responder can be used to add or change the review due date for a current reviewer.

Listens to

@botname set due date for @reviewer to YYYY-MM-DD

Requirements

The body of the issue should have the reviewers-list and the due-dates-list placeholders marked with HTML comments:

<!--reviewers-list-->  <!--end-reviewers-list-->
<!--due-dates-list-->  <!--end-due-dates-list-->

The reviewer should be already listed in the reviewers list

The format for the due date must be YYYY-MM-DD

The new due date can not be in the past

Settings key

ropensci_set_due_date

Examples

Restricted to editors:

...
  responders:
    ropensci_set_due_date:
      only:
        - editors
...

In action

  • Initial state:

Initial issue’s body ROpenSci :: Set due date: Initial state

  • Invocation:

Set new due date for a reviewer ROpenSci :: Set due date: Invocation

  • Final state:

Issue’s body with new due date info

ROpenSci :: Set due date: Final state, info updated

ROpenSci :: Seeking reviewers

This responder changes the review to a seeking reviewers mode, adding and removing appropiate labels and responding with a message with further instructions for authors. This responder will also call Airtable to create an entry in the packages table and entries for all authors (author1 and author-others) in the authors tables.

Listens to

@botname seeking reviewers

Settings key

ropensci_seeking_reviewers

Params

template_file:

The optional template file to use to build the response message.

add_labels:

<Array> Optional parameter. Labels to add to the issue.

remove_labels:

<Array> Optional parameter. Labels to remove from the issue.

As with any responder interacting with Airtable, two parameters must be present in the env section of the settings file, configured using environment variables:

...
  env:
    airtable_api_key: <%= ENV['AIRTABLE_API_KEY'] %>
    airtable_base_id: <%= ENV['AIRTABLE_BASE_ID'] %>
...

Examples

Restricted to editors:

...
  responders:
    ropensci_seeking_reviewers:
      only:
        - editors
      template_file: badge.md
      remove_labels:
        - 1/editor-checks
      add_labels:
        - 2/seeking-reviewer(s)
...

In action

Run by an editor:

ROpenSci :: Seeking reviewers in action

ROpenSci :: Approve

This responder is used to approve a package. It performs a series of tasks:

  • Adds date-accepted to the body of the issue

  • Clears reviewers’ current_assignment in AirTable

  • Creates a new team named like the package-name and invites the creator of the issue to it (owner right needed)

  • Can reply with a template

  • Allows labeling

  • Closes the issue

  • If the submission-type is stats it checks if stasgrade is present and if so adds the proper label

Listens to

@botname approve package-name

Requirements

The package-name must be specified in the command, otherwise an error message will be sent as reply.

If the submission-type of the issue is stats, then for the responder to work there must be a valid value for a statsgrade variable (marked with HTML comments) in the body of the issue:

# the responder will add the label: '6/approved-silver'
<!--statsgrade-->silver<!--end-statsgrade-->

Settings key

ropensci_approve

Params

For the Airtable connection to work two parameters must be present in the env section of the settings file, configured using environment variable:

...
  env:
    airtable_api_key: <%= ENV['AIRTABLE_API_KEY'] %>
    airtable_base_id: <%= ENV['AIRTABLE_BASE_ID'] %>
...

For labeling the approved stats submissions an external service is used to get the proper versioned label. The url for the external service is by default: http://138.68.123.59:8000/stats_badge. This value can be changed using the optional :stats_badge_url param:

...
  responders:
    ropensci_approve:
      only: editors
      stats_badge_url: https://test.ropensci:3030
...

Examples

Simplest case:

...
  responders:
    ropensci_approve:
...

With labeling, template response, limiting access and only if there’s an editor already assigned:

...
  responders:
    ropensci_approve:
      only: editors
      template_file: approved.md
      data_from_issue:
        - reviewers-list
      remove_labels:
        - 5/awaiting-reviewer(s)-response
      add_labels:
        - 6/approved
...

ROpenSci :: Finalize transfer

This responder is used to assing a recent approved and transfered package to a rOpenSci team. It needs owner rights to work. It performs a series of tasks:

  • Checks for the presence of the package-name repo in the rOpenSci GitHub organization

  • Creates a new team named like the package-name and invites the creator of the issue to it, if the team does not exists already.

  • Adds the package-name repo to the package-name team with admin rights so the members of the team can manage it

Listens to

@botname finalize transfer of package-name

Requirements

The package-name must be specified in the command, otherwise an error message will be sent as reply. The bot must have owner rights.

Settings key

ropensci_finalize_transfer

Example:

...
  responders:
    ropensci_finalize_transfer:
      only: editors
...

ROpenSci :: Invite author

This responder is used by the author of an approved package to receive an invitation to join the team that will manage the package and will allow them to transfer it to rOpenSci. Usually this invitation is sent automatically when the package is approved but it expires in a week. This responder allows the author to have the invitation sent again.

Listens to

@botname invite me to ropensci/package-name

Requirements

The command must be run by the author of the package (the user that created the review issue), otherwise an error message will be sent as reply.

Settings key

ropensci_invite_author

Example:

Allow the command to run only if package is already approved:

...
  responders:
    ropensci_invite_author:
      if:
        labels: 6/approved
        reject_msg: "Can't invite author because the package is not approved yet"
...

In action

ROpenSci :: Invite author responder in action

ROpenSci :: Mint

This responder mints a submission: it can be used to add a valid badge grade value (currently bronze/silver/gold) to the kind of submissions accepting them (currently stats)

Listens to

@botname mint <grade>

Where <grade> must be a valid value. For example:

@botname mint silver

Requirements

The responder will read the value of submission-type in the body of the issue, for it to work this value must equal stats, then it will update (or add) the value of the statsgrade in the body of the issue.

Settings key

ropensci_mint

Examples

Only available to editors:

...
  responders:
    ropensci_mint:
      only: editors
...

In action

  • Initial state:

Issue’s body with correct submission type

ROpenSci :: Mint: Initial state

  • Invocation:

ROpenSci :: Mint: grade gold

  • Final state:

Issue’s body updated with the badge grade

ROpenSci :: Mint: Final state

ROpenSci :: Submit review

This responder can be used to update Airtable entries with a review url, duration and date in the reviews table. Once the number of reviews in Airtable equals the number of reviewers in the issue a message will be configured for 12 days later to remind authors to submit their response.

Listens to

@botname submit review <REVIEW_URL> time <REVIEW_HOURS>

Where <REVIEW_URL> must be a valid link to a comment in the issue and <REVIEW_HOURS> is numeric. For example:

@botname submit review https://github.com/ropensci/software-review/issues/338#issuecomment-536199121 time 7.5

Requirements

REVIEW_URL must be a complete url pointing to a comment in the review issue.

REVIEW_HOURS is numeric. Example of valid values: 4, 10.5, 7,5

Settings key

ropensci_submit_reviews

Params

label_when_all_reviews_in:

Optional Labeling to add to the issue once the number of reviews in Airtable equals the number of reviewers in the issue.

unlabel_when_all_reviews_in:

Optional Labeling to remove from the issue once the number of reviews in Airtable equals the number of reviewers in the issue.

For the Airtable connection to work two parameters must be present in the env section of the settings file, configured using environment variable:

...
  env:
    airtable_api_key: <%= ENV['AIRTABLE_API_KEY'] %>
    airtable_base_id: <%= ENV['AIRTABLE_BASE_ID'] %>
...

Examples

Simplest case:

...
  responders:
    ropensci_submit_reviews:
...

With labeling once all reviews are completed and limiting access to editors:

...
  responders:
    ropensci_submit_reviews:
      only:
        - editors
      label_when_all_reviews_in: "4/review-in-awaiting-changes"
      unlabel_when_all_reviews_in: "3/reviewer(s)-assigned"
...

In action

  • Invocation: Log first review

ROpenSci :: Submit review: first review in

  • Logging last review applies labeling: ROpenSci :: Submit review: last review and labeling

ROpenSci :: Submit author response

Used by the authors of a submission after responding to a review or to reviewer’s comments, this responder can be used to create Airtable entries with a response url and a date for the reviewed package.

Listens to

@botname submit response <AUTHOR_RESPONSE_URL>

Where <AUTHOR_RESPONSE_URL> must be a valid link to a comment in the issue. For example:

@botname submit response https://github.com/ropensci/software-review/issues/550#issuecomment-1229032049

Requirements

AUTHOR_RESPONSE_URL must be a complete url pointing to a comment in the review issue.

Settings key

ropensci_submit_author_response

Params

For the Airtable connection to work two parameters must be present in the env section of the settings file, configured using environment variable:

...
  env:
    airtable_api_key: <%= ENV['AIRTABLE_API_KEY'] %>
    airtable_base_id: <%= ENV['AIRTABLE_BASE_ID'] %>
...

Examples

Simplest case:

...
  responders:
    ropensci_submit_author_response:
...

Use restricted to authors set in the body of the issue:

...
  responders:
    ropensci_submit_author_response:
      authorized_roles_in_issue:
        - author1
        - author-others
...

In action

  • Invocation: Log author's response

ROpenSci :: Submit author response

ROpenSci :: On hold

This responder is used by an editor to put the submission on hold (by default for 90 days, but that is configurable). The responder will label the issue with the holding label and once the time limit is reached the editor will be pinged to review the holding status and possibly close the issue.

Listens to

@botname put on hold

Settings key

ropensci_on_hold

Params

on_hold_label:

Optional. The label to add to the issue. By default is holding

on_hold_days:

Integer An optional number of days to have the issue on hold before reminding the editor. Default value is 90.

Example:

Allow the command to be run only by editors, and set reminder to 26 days:

...
  responders:
    ropensci_on_hold:
      only: editors
      on_hold_days: 26
...

In action

ROpenSci :: On hold responder in action

Labeling

Several Buffy responders allow labeling. A responder allowing labeling means that if the responder finish successfully its main task, it can add and/or remove labels to the issue if they are specified in the settings file.

Settings

Responders allowing labeling will accept in their settings two keys:

add_labels:

an optional Array of labels to add

remove_labels:

an optional Array of labels to remove

Example:

...
  responders:
    example_responder:
      add_labels:
        - review-finished
        - recommend publication
      remove_labels:
        - pending-review
...

If the example responder is successfull the review-finished and recommend publication labels will be added and the pending-review label will be removed from the issue.

Responders listening to Add/Remove actions

Some responders listen to two opposite add and remove actions (for instance the add_remove_assignee responder). In these cases, the add action will process the labeling normally –adding the specified :add_labels and removing the :remove_labels– and the remove action will undo that labeling, i.e. removing the :add_labels and adding the labels from the :remove_labels setting.

Using templates

Several Buffy responders can reply with a template. Please read each Responder documentation to know if a specific Responder allows this option.

Template files

Templates must be created in the repository using Buffy. Every template is a different file in the repo. To make use of them Buffy needs to know where the templates are located, and the individual name of each template file. As the comments in GitHub issues are rendered using markdown, usually the templates will be plain text or .md files, but that is not mandatory for Buffy to use them.

Location

Buffy will look for the templates in the target repository. By default it will look under the .buffy/templates dir. This value can be modified in the settings file with the templates_path setting. If present, the value of this setting will be considered the relative value in the target repo where templates are located.

Name

In the responders allowing templates for replies, the template is specified using the template_file setting for that responder. Value should be the name of the file including the extension if it has one.

Example

If Buffy is configured to work on a repo with address https://github.com/scientific-journal/astronomy and the settings.yml file has the following value for template_path:

buffy:
  templates_path: .templates
...

and you declare a template in a responder using template_file with this value:

...
responders:
  welcome_template:
    template_file: welcome.md
...

Buffy will use the content of https://github.com/scientific-journal/astronomy/.templates/welcome.md to respond.

Populating templates

The content of a template can include placeholders to be filled with the actual values of a variable. The syntax is:

{{variable_name}}

When rendering a template, Buffy will use a hash of key:value pairs. When a placeholder is found in the template, it will look up for the corresponding key in the hash and insert the value in the template. The hash will always include at least:

  • issue_id: The id of the issue

  • issue_author: The handle of the user that opened the issue

  • repo: the name of the repository

  • sender: the handle of the user creating the comment/issue triggering the responder

  • bot_name: the name of the bot user responding

The hash can also include fields extracted from the body of the issue. To add fields use the data_from_issue setting. For example, to have the target-repository and author values from the issue available in the template this would do:

...
responders:
  welcome_template:
    template_file: welcome.md
    data_from_issue:
        - target-repository
        - author
...

Check each responder documentation for details on other values available to use in templates.

Creating a custom responder

Buffy will load and make available any responder that is located in the app/responders directory. The simplest way to organize your responders is to add them in a subfolder inside the responders dir, defining a module for the custom responders.

During this guide as an example, we’ll create a simple responder to get the time.

Responder structure

A responder is a ruby class containing five elements:

  • keyname: the handle for the responder in the configuration file

  • define_listening method: a place to declare what events the responder is listening to

  • process_message method: the code to perform whatever the responder does

  • description method: to add a short description of the responder for documenting purposes

  • example_invocation method: to show users how to invoke the responder

The Responder Ruby class

A responder object is a class inheriting from the Responder class, so you should require the Responder class located in /lib and create a child class.

When initialized, a responder will have accessor methods for the name of the bot (bot_name) and for the parameters of the responder coming from the config file (params).

For our example we add a clock_responder.rb file to the new app/responders/myorganization dir.
It declares the responder class in the myorganization module.
require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
  end
end

Keyname

Using keyname you can define the handle for the responder to be used in the configuration file. Using a symbol is ok.

For our example we’ll just use clock:

require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
    keyname :clock
  end
end

Now we can use the responder adding it to the config.yml file:

...
  responders:
    clock:
...

Define listening

The define_listening method is the place to specify what the responder is listening to. You can set values for two instance variables here:

  • @event_action: the action that triggered the event the responder will listen to

  • @event_regex: (optional) a regular expression the text body of the event (a comment or the body of an issue) should match for the responder to respond

When an event is sent from the reviews repository to Buffy, only responders that match action and regex (if present) will be run.

Event action
  • If you are listening to creation of issues, @event_action should be "issues.opened".

  • If you are listening to new comments, @event_action should be "issue_comment.created"".

Event regex

The @event_regex variable is where the syntax of every specific command is declared. If it is nil the responder will respond to every event that matches @event_action.

Inside this method you have available the name of the bot in the @botname instace variable and all the parameters for this responder from the config file in the @params instance variable.

For our example, we will be listening to comments and we want the command to be “what time is it?”:

require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
    keyname :clock

    def define_listening
      @event_action = "issue_comment.created"
      @event_regex = /\A@#{bot_name} what time is it\?\s*\z/i
    end
  end
end
Mandatory parameters

You can also declare inside this method which parameters are required in the configuration using required_params. This will create a reader method for every required parameter.

For example, we could make the command for invoking our responder mandatory and declared in the config.yml file instead that in our regex, that way the command for our responder can be changed and be easily configured:

require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
    keyname :clock

    def define_listening
      required_params :command

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

now the command must be added to the config file or the responder will error and not run:

...
  responders:
    clock:
      command: tell me the time
...

But we don’t want to be too strict so, we’ll allow the command to be changed but by default we’ll have one. For that we’ll use an auxiliary instance method:

require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
    keyname :clock

    def define_listening
      @event_action = "issue_comment.created"
      @event_regex = /\A@#{bot_name} #{clock_command}\s*\z/i
    end

    def clock_command
      params[:command] || "what time is it\\?"
    end
  end
end

Process message

The process_message method will be called if an event reaches Buffy and it matches the action and the regex in the define_listening method. It accepts a single argument: the message that triggered the call.

This method is the place of all the custom Ruby code needed to perform whatever is the responder does. To interact back with the reviews repository there are several methods available:

  • respond(message): will post a comment with the specified message string

  • respond_external_template(template_name, locals): will post a comment using a template and passing it the locals variables

  • update_body(mark, end_mark, text): will update the body of the issue between marks with the passed text

  • add_assignee(user): will add the passed user to the issue’s assignees

  • remove_assignee(user): will remove the passed user from the issue’s assignees

  • replace_assignee(old_user, new_user): will replace the passed old_user with new_user in the issue’s assignees

  • process_labeling: will add/remove labels as specified in the responder config params

If you need to access any matched data from the @event_regex you have them available via the match_data array.

For our example we’ll just reply a comment with the time:

require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
    ...

    def process_message(message)
      respond(Time.now.strftime("⏱ The time is %H:%M:%S %Z, today is %d-%m-%Y ⏱"))
    end

  end
end

Description

Use the description method to add a short description of what the responder does.

Our example responder replies with the current time:

require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
    ...

    def description
      "Get the current time"
    end
  end
end

Example invocation

To help users understand how to use the responder, use the example_invocation to add an example of how the responder is triggered.

In our example responder we’ll use the command declared via config or the default one:

require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
    ...

    def example_invocation
      "@#{bot_name} #{params[:command] || 'what time is it?'}"
    end
  end
end

Sample custom responder

The final version of our clock responder (in app/responders/myorganization/clock_responder.rb):

require_relative '../../lib/responder'

module Myorganization
  class ClockResponder < Responder
    keyname :clock

    def define_listening
      @event_action = "issue_comment.created"
      @event_regex = /\A@#{bot_name} #{clock_command}\s*\z/i
    end

    def process_message(message)
      respond(Time.now.strftime("⏱ The time is %H:%M:%S %Z, today is %d-%m-%Y ⏱"))
    end

    def clock_command
      params[:command] || "what time is it\\?"
    end

    def description
      "Get the current time"
    end

    def example_invocation
      "@#{bot_name} #{params[:command] || 'what time is it?'}"
    end
  end
end

Adding its key to the configuration file in the responder settings:

buffy:
  responders:
    clock:
...

The responder should be available and ready to use:

Custom responder in action: clock responder

Tests

Don’t forget to add tests for any new Responder you create. Buffy uses the RSpec test framework.

For our sample responder, we would create spec/responders/myorganization/clock_responder_spec.rb

require_relative "../../spec_helper.rb"

describe Myorganization::ClockResponder do

  subject do
    described_class
  end

  describe "listening" do
    before { @responder = subject.new({env: {bot_github_user: "testbot"}}, {}) }

    it "should listen to new comments" do
      expect(@responder.event_action).to eq("issue_comment.created")
    end

    it "should define regex" do
      expect(@responder.event_regex).to match("@testbot what time is it?")
      expect(@responder.event_regex).to_not match("@testbot whatever")
    end

    it "should allow invocation with custom command" do
      custom_responder = subject.new({env: {bot_github_user: "testbot"}},
                                     {command: "tell me the time"})
      expect(custom_responder.event_regex).to match("@testbot tell me the time")
      expect(custom_responder.event_regex).to_not match("@botsci what time is it?")
    end
  end

  describe "#process_message" do
    before do
      @responder = subject.new({env: {bot_github_user: "botsci"}}, {})
      disable_github_calls_for(@responder)
    end

    it "should respond to github" do
      timenow = Time.now
      expected_response = timenow.strftime("⏱ The time is %H:%M:%S %Z, today is %d-%m-%Y ⏱")
      expect(Time).to receive(:now).and_respond(timenow)
      expect(@responder).to receive(:respond).with(expected_response)
      @responder.process_message("@testbot what time is it?")
    end
  end
end

You can find more examples of responder specs in the /spec/responders directory.