From 0158c1f16756f7d3b6f235173f34e740a4b01d2d Mon Sep 17 00:00:00 2001 From: Brandon Keepers Date: Fri, 18 Aug 2017 07:46:23 -0500 Subject: [PATCH] Rework docs for website --- docs/README.md | 19 ++++++++++ docs/best-practices.md | 2 +- docs/deployment.md | 8 +++-- docs/development.md | 70 ++++++++++++++++++++++++++++++++++--- docs/github-api.md | 27 ++++++++++++++ docs/hello-world.md | 15 ++++++++ docs/http.md | 28 +++++++++++++++ docs/pagination.md | 15 ++++++++ docs/simulating-webhooks.md | 17 +++++++++ docs/testing.md | 52 +++++++++++++++++++++++++++ docs/webhooks.md | 30 ++++++++++++++++ 11 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 docs/README.md create mode 100644 docs/github-api.md create mode 100644 docs/hello-world.md create mode 100644 docs/http.md create mode 100644 docs/pagination.md create mode 100644 docs/simulating-webhooks.md create mode 100644 docs/testing.md create mode 100644 docs/webhooks.md diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..fb74a0d2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,19 @@ +--- +next: docs/hello-world.md +permalink: /docs/ +--- + +# Introduction + +If you've ever thought, "wouldn't it be cool if GitHub could…"; imma stop you right there. Most features can actually be added via [GitHub Apps](https://developer.github.com/apps/), which extend GitHub and can be installed directly on organizations and user accounts and granted access to specific repositories. They come with granular permissions and built-in webhooks. Apps are first class actors within GitHub. + +**Probot is a framework for building [GitHub Apps](http://developer.github.com/apps) in [Node.js](https://nodejs.org/)**. It aims to eliminate all the drudgery–like receiving and validating webhooks, and doing authentication handstands–so you can focus on the features you want to build. + +Probot apps are easy to write, deploy, and share. Many of the most popular Probot apps are hosted, so there's nothing for you to deploy and manage. Here are just a few examples of things that have been built with Probot: + +- [stale](/apps/stale/) - closes abandoned issues after a period of inactivity. +- [settings](/apps/settings/) - syncs repository settings defined in `.github/config.yml` to GitHub, enabling Pull Requests for repository settings. +- [request-info](/apps/request-info/) - requests more info from newly opened Pull Requests and Issues that contain either default titles or whose description is left blank. +- [Browse more examples](https://github.com/search?q=topic%3Aprobot-plugin&type=Repositories) + +Ready to get started? diff --git a/docs/best-practices.md b/docs/best-practices.md index 96bfeef1..06da2252 100644 --- a/docs/best-practices.md +++ b/docs/best-practices.md @@ -1,4 +1,4 @@ -# Best practices for Probot plugins +# Best Practices First and foremost, your plugin must obey the [The Three Laws of Robotics](https://en.wikipedia.org/wiki/Three_Laws_of_Robotics): diff --git a/docs/deployment.md b/docs/deployment.md index 3cecd229..ba0206ff 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,4 +1,8 @@ -# Deploy +--- +next: docs/best-practices.md +--- + +# Deployment Every plugin can either be deployed as a stand-alone bot, or combined with other plugins in one deployment. @@ -72,7 +76,7 @@ Probot runs like [any other Node app](https://devcenter.heroku.com/articles/depl -----> Launching... done http://arcane-lowlands-8408.herokuapp.com deployed to Heroku -1. Your plugin should be up and running! To verify that your plugin +1. Your plugin should be up and running! To verify that your plugin is receiving webhook data, you can tail your app's logs: $ heroku config:set LOG_LEVEL=trace diff --git a/docs/development.md b/docs/development.md index 757dd356..9dc7018c 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,8 +1,57 @@ -# Development +--- +next: docs/webhooks.md +--- -To run a plugin locally, you'll need to create a GitHub App and configure it to deliver webhooks to your local machine. +# Developing a Plugin + +To develop a Probot plugin, you will first need a recent version of [Node.js](https://nodejs.org/) installed. Probot uses the `async/await` keywords, so Node.js 7.6 is the minimum required version. + +## Generating a new plugin + +[create-probot-plugin](https://github.com/probot/create-probot-plugin) is the best way to start building a new plugin. It will generate a new plugin with everything you need to get started and run your plugin in production. + +To get started, install the module from npm: + +``` +$ npm install -g create-probot-plugin +``` + +Next, run the app: + +``` +$ create-probot-plugin my-first-plugin +``` + +This will ask you a series of questions about your plugin, which should look something like this: + +``` +Let's create a Probot plugin! +? Plugin's package name: my-first-plugin +? Description of plugin: A "Hello World" GitHub App built with Probot +? Plugin author's full name: Brandon Keepers +? Plugin author's email address: bkeepers@github.com +? Plugin author's homepage: +? Plugin's GitHub user or org name: bkeepers +? Plugin's repo name: my-first-plugin +created file: my-first-plugin/.env.example +created file: my-first-plugin/.gitignore +created file: my-first-plugin/.travis.yml +created file: my-first-plugin/LICENSE +created file: my-first-plugin/README.md +created file: my-first-plugin/app.json +created file: my-first-plugin/index.js +created file: my-first-plugin/package-lock.json +created file: my-first-plugin/package.json +created file: my-first-plugin/docs/deploy.md +Done! +``` + +The most important files note here are `index.js`, which is where the code for your plugin will go, and `package.json`, which makes this a standard [npm module](https://docs.npmjs.com/files/package.json). + +## Configure a GitHub App + +To run your plugin in development, you will need to configure a GitHub App to deliver webhooks to your local machine. -1. Make sure you have a recent version of [Node.js](https://nodejs.org/) installed 1. [Create a new GitHub App](https://github.com/settings/apps/new) with: - **Webhook URL**: Set to `https://example.com/` and we'll update it in a minute. - **Webhook Secret:** `development` @@ -14,11 +63,22 @@ To run a plugin locally, you'll need to create a GitHub App and configure it to You'll need to create a test repository and install your app by clicking the "Install" button on the settings page of your app. -Whenever you come back to work on the app after you've already had it running once, you should only need to run `$ npm start`. +## Running the plugin + +Once you've set the `APP_ID` of your GitHub app in `.env` and downloaded the private key, you're ready to run your bot. + +``` +$ npm start +> probot run ./index.js + +Yay, the plugin was loaded! +18:11:55.838Z DEBUG PRobot: Loaded plugin: ./index.js +Listening on https://bkeepers.localtunnel.me +``` Optionally, you can also run your plugin through [nodemon](https://github.com/remy/nodemon#nodemon) which will listen on any files changes in your local development environment and automatically restart the server. After installing nodemon, you can run `nodemon --exec "npm start"` and from there the server will automatically restart upon file changes. ## Debugging -1. Always run `$ npm install` and restart the server if package.json has changed. +1. Always run `$ npm install` and restart the server if `package.json` has changed. 1. To turn on verbose logging, start server by running: `$ LOG_LEVEL=trace npm start` diff --git a/docs/github-api.md b/docs/github-api.md new file mode 100644 index 00000000..67a29cd9 --- /dev/null +++ b/docs/github-api.md @@ -0,0 +1,27 @@ +--- +next: docs/http.md +--- + +# Interacting with GitHub + +Probot uses [GitHub Apps](https://developer.github.com/apps/). An app is a first-class actor on GitHub, like a user (e.g. [@defunkt](https://github/defunkt)) or an organization (e.g. [@github](https://github.com/github)). The app is given access to a repository or repositories by being "installed" on a user or organization account and can perform actions through the API like [commenting on an issue](https://developer.github.com/v3/issues/comments/#create-a-comment) or [creating a status](https://developer.github.com/v3/repos/statuses/#create-a-status). + +`context.github` is an authenticated GitHub client that can be used to make API calls. It is an instance of the [github Node.js module](https://github.com/mikedeboer/node-github), which wraps the [GitHub API](https://developer.github.com/v3/) and allows you to do almost anything programmatically that you can do through a web browser. + +Here is an example of an autoresponder plugin that comments on opened issues: + +```js +module.exports = robot => { + robot.on('issues.opened', async context => { + // `context` extracts information from the event, which can be passed to + // GitHub API calls. This will return: + // {owner: 'yourname', repo: 'yourrepo', number: 123, body: 'Hello World!} + const params = context.issue({body: 'Hello World!'}) + + // Post a comment on the issue + return context.github.issues.createComment(params); + }); +} +``` + +See the [full API docs](https://mikedeboer.github.io/node-github/) to see all the ways you can interact with GitHub. Some API endpoints are not available on GitHub Apps yet, so check [which ones are available](https://developer.github.com/v3/apps/available-endpoints/) first. diff --git a/docs/hello-world.md b/docs/hello-world.md new file mode 100644 index 00000000..69f53986 --- /dev/null +++ b/docs/hello-world.md @@ -0,0 +1,15 @@ +--- +next: docs/development.md +--- + +# Hello World + +A Probot plugin is just a [Node.js module](https://nodejs.org/api/modules.html) that exports a function: + +```js +module.exports = robot => { + // your code here +}; +``` + +The `robot` parameter is an instance of [`Robot`](https://probot.github.io/probot/latest/Robot.html) and gives you access to all of the bot goodness. diff --git a/docs/http.md b/docs/http.md new file mode 100644 index 00000000..1922a5ec --- /dev/null +++ b/docs/http.md @@ -0,0 +1,28 @@ +--- +next: docs/simulating-webhooks.md +--- + +# HTTP Routes + +Calling `robot.route('/my-plugin')` will return an [express](http://expressjs.com/) router that you can use to expose HTTP endpoints from your plugin. + +```js +module.exports = robot => { + // Get an express router to expose new HTTP endpoints + const app = robot.route('/my-plugin'); + + // Use any middleware + app.use(require('express').static(__dirname + '/public')); + + // Add a new route + app.get('/hello-world', (req, res) => { + res.end('Hello World'); + }); +}; +``` + +Visit https://localhost:3000/my-plugin/hello-world to access the endpoint. + +It is strongly encouraged to use the name of your package as the prefix so none of your routes or middleware conflict with other plugins. For example, if [`probot/owners`](https://github.com/probot/owners) exposed an endpoint, the plugin would call `robot.route('/owners')` to prefix all endpoints with `/owners`. + +See the [express documentation](http://expressjs.com/en/guide/routing.html) for more information. diff --git a/docs/pagination.md b/docs/pagination.md new file mode 100644 index 00000000..556e256c --- /dev/null +++ b/docs/pagination.md @@ -0,0 +1,15 @@ +--- +next: docs/deployment.md +--- + +# Pagination + +Many GitHub API endpoints are paginated. The `github.paginate` method can be used to get each page of the results. + +```js +context.github.paginate(context.github.issues.getAll(context.repo()), res => { + res.data.issues.forEach(issue => { + robot.console.log('Issue: %s', issue.title); + }); +}); +``` diff --git a/docs/simulating-webhooks.md b/docs/simulating-webhooks.md new file mode 100644 index 00000000..e4476649 --- /dev/null +++ b/docs/simulating-webhooks.md @@ -0,0 +1,17 @@ +--- +next: docs/pagination.md +--- + +# Simulating Webhooks + +As you are developing your plugin, you will likely want to test it by repeatedly trigging the same webhook. You can simulate a webhook being delivered by saving the payload to a file, and then calling `probot simulate` from the command line. + +To save a copy of the payload, go to the [settings](https://github.com/settings/apps) page for your App, and go to the **Advanced** tab. Click on one of the **Recent Deliveries** to expand it and see the details of the webhook event. Copy the JSON from the the **Payload** and save it to a new file. (`test/fixtures/issues.labeled.json` in this example). + +![](https://user-images.githubusercontent.com/173/28491924-e03e91f2-6ebe-11e7-9570-6d48da68c6ca.png) + +Next, simulate this event being delivered by running: + +``` +$ node_modules/.bin/probot simulate issues test/fixtures/issues.labeled.json ./index.js +``` diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 00000000..8a343555 --- /dev/null +++ b/docs/testing.md @@ -0,0 +1,52 @@ +--- +next: docs/pagination.md +--- + +# Testing + +We highly recommend working in the style of [test-driven development](http://agiledata.org/essays/tdd.html) when creating probot plugins. It frustrating to constantly create real GitHub events in order to test a plugin. Redelivering webhooks is possible and can be accessed in your app's [settings](https://github.com/settings/apps) page under the **Advanced** tab. We do offer the above documented `simulate` method to help make this easier; however, by writing your tests first, you can avoid repeatedly recreating actual events from GitHub to check if your code is working. + +For our testing examples, we use [mocha](https://mochajs.org/) and [expect](https://github.com/mjackson/expect), but there are other options that can perform similar operations. Here's an example of creating a robot instance and mocking out the GitHub API: + +```js +// Requiring our testing framework +const expect = require('expect'); +// Requiring probot allows us to mock out a robot instance +const {createRobot} = require('probot'); +// Create a fixtures folder in your test folder +// Then put any larger testing payloads in there +const payload = require('./fixtures/payload'); + +describe('your-plugin', () => { + let robot; + let github; + + beforeEach(() => { + // Here we create a robot instance + robot = createRobot(); + // Here we initialize the plugin on the robot instance + plugin(robot); + // This is an easy way to mock out the GitHub API + github = { + issues: { + createComment: expect.createSpy().andReturn(Promise.resolve({ + // Whatever the GitHub API should return + })) + } + } + // Passes the mocked out GitHub API into out robot instance + robot.auth = () => Promise.resolve(github); + }); + + describe('your functionality', () => { + it('performs an action', async () => { + // Simulates delivery of a payload + await robot.receive(payload); + // This test would pass if in your main code you called `context.github.issues.createComment` + expect(github.issues.createComment).toHaveBeenCalled(); + }); + }); +}); +``` + +A good testing example from [update-docs](github.com/behaviorbot/update-docs) can be found [here](https://github.com/behaviorbot/update-docs/blob/master/test/index.js), and another one from [owners](github.com/probot/owners) can be found [here](https://github.com/probot/owners/blob/master/test/owner-notifier.js). diff --git a/docs/webhooks.md b/docs/webhooks.md new file mode 100644 index 00000000..d79606dd --- /dev/null +++ b/docs/webhooks.md @@ -0,0 +1,30 @@ +--- +next: docs/github-api.md +--- + +# Receiving Webhooks + +[GitHub webhooks](https://developer.github.com/webhooks/) are fired for almost every significant action that users take on GitHub, whether it's pushes to code, opening or closing issues, opening or merging pull requests, or commenting on a discussion. + +Many robots will spend their entire day responding to these actions. `robot.on` will listen for any GitHub webhook events: + +```js +module.exports = robot => { + robot.on('push', async context => { + // Code was pushed to the repo, what should we do with it? + robot.log(context); + }); +}; +``` + +The robot can listen to any of the [GitHub webhook events](https://developer.github.com/webhooks/#events). The `context` object includes all of the information about the event that was triggered, and `context.payload` has the payload delivered by GitHub. + +Most events also include an "action". For example, the [`issues`](https://developer.github.com/v3/activity/events/types/#issuesevent) event has actions of `assigned`, `unassigned`, `labeled`, `unlabeled`, `opened`, `edited`, `milestoned`, `demilestoned`, `closed`, and `reopened`. Often, your bot will only care about one type of action, so you can append it to the event name with a `.`: + +```js +module.exports = robot => { + robot.on('issues.opened', async context => { + // An issue was just opened. + }); +}; +```