More Eyes, Plz! Hackathon entry + update
Tags: actionshackathon, help
Published: 2020-08-22 19:02:53

[Instructions]: # (To submit to the GitHub Actions x DEV Hackathon, please fill out all sections.)

*Here are the updates made since the original progress post:*
- *added support for bot commands, starting with the `[meep close]` command*
- *the site now fetches labels created dynamically for new meep requests*
- *the site is now responsive on mobile/tablet*
- *meeps are only filed at most once (in the event the same commit is pushed to multiple branches)*

*Thanks to everyone who took it for a spin and/or submitted feedback!*

### My Workflow

Getting started with a new programming language or framework? Trying to learn a new paradigm like functional programming? Perhaps you're getting started with a new discipline, like compiler engineering or graphics programming? Normally, we'd learn by authoring code in a "sandbox" and see what works while following along a book or tutorial. But what if you would like some pointers or feedback from someone more experienced? Stackoverflow could work, but the site encourages questions with a high degree of specificity. You can't just ask for holistic feedback for code.

[More Eyes, Plz!](https://moreeyesplz.com) is a service powered by Github actions to easily crowdsource commits made to any repository. To get started simply install the [meep scanner](https://github.com/marketplace/actions/meep-scanner) action to the repo you'd like to get feedback on, or copy the following snippet to `.github/workflows/meep.yml`:

```yml
name: meep_scanner
on: [push]

jobs:
  meep_scan:
    runs-on: ubuntu-latest
    steps:
    - uses: moreeyesplz/meep_scanner@master
      with:
        github-token: ${{ secrets.GITHUB_TOKEN }}
```

After the action is installed, simply write `[meep]` or `[MEEP]` somewhere in the commit notes. Here's an example commit message:

![commitmessage](https://dev-to-uploads.s3.amazonaws.com/i/ert3831mltx9m7tvzrco.png)


Shortly after, the bot will post a comment that looks like the following to the commit itself:

![commitcomment](https://dev-to-uploads.s3.amazonaws.com/i/j8734jlj7zgcorhwd6sv.PNG)

The commit link will also be indexed on the [website](https://moreeyesplz.com) and show up as follows for others to see:

![meep](https://dev-to-uploads.s3.amazonaws.com/i/u7pd5cm8hmrq4nr27643.png)

Afterwards, expect to get some feedback!

![feedback](https://dev-to-uploads.s3.amazonaws.com/i/fysw1je00zdvllecyitg.png)

When you're satisfied with the feedback, simply write `[meep close]` in the commit thread to unlist the meep from the database.

### Submission Category: 

Maintainer Must Haves


### Yaml File or Link to Code

Only one repository is relevant for actually integrating with the **More Eyes, Plz!** service: 
{% github https://github.com/moreeyesplz/meep_scanner %}

The action that responds to the scan to write the commit comment and also file an issue to the meeps tracker is here:
{% github https://github.com/moreeyesplz/meeper %}

Another action is used to respond to `workflow_dispatch` events needed to power bot commands such as `[meep close]`:
{% github https://github.com/moreeyesplz/themeepbot %}

Finally, the website itself is hosted via github pages and is itself open source as well:
{% github https://github.com/moreeyesplz/moreeyesplz.github.io %}

Even the deployment of the site (created via `create-react-app`) is automated using a workflow which may be useful to others. The code used is pasted below:

```yml
name: deploy
on: ['workflow_dispatch']

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - name: checkout
      uses: actions/checkout@v2
      with:
        ref: master
        fetch-depth: 0
        clean: false
    - name: cache dependencies
      id: cache
      uses: actions/cache@v2
      with:
        path: node_modules
        key: ${{ runner.os }}-${{ hashFiles('package.json') }}
    - name: install dependencies
      if: steps.cache.outputs.cache-hit != 'true'
      run: npm install
    - name: build site
      run: |
        npm run build
        git config --local user.email "bot@moreeyesplz.com"
        git config --local user.name "themeepbot"
        git checkout site
        cp -r build/* ./
        rm -rf build
        git add .
        git commit -m'Automated build'
        git push origin site
```
Feel free to adapt this for your needs! The only thing you would probably want to change is the identifying `user.email` and `user.name` local git config (it needs to push to the `site` branch).

### Additional Resources / Info

This project was done in collaboration with @duchesstoffee (my wife). She did the frontend, responsible for querying the meeps database (hosted using the issue tracker of this [meta repo](https://github.com/moreeyesplz/meeps)), fetch labels, and enabling tag-based searching. The frontend is built as a single page application, using the `fetch` API and OAuth credentials to pull data from Github.

Here is a list of dependencies used for the frontend:

- [React](https://reactjs.org/)
- [Material UI](https://material-ui.com/)
- [Octokit Rest](https://github.com/octokit/rest.js)
  - Github API v3 REST client

The backend is largely driven via actions and Github itself, with no database or dedicated servers. However, a few Google Cloud functions are used simply to proxy information in a secure manner in cases where we could not ship a developer secret (with write credentials to the meeps repo) to the client. A Google Cloud function is also used to handle the oauth redirect. Here's the code for the Oauth handling for example, which runs on vanilla nodejs with no dependencies:

```js
const https = require('https');

const HOST = process.env.HOST;

exports.auth = (req, res) => {
    if (!req.query.code) {
        return res.sendStatus(401);
    }

    https.request({
        host: 'github.com',
        path: `/login/oauth/access_token?code=${req.query.code}&client_id=${process.env.OAUTH_ID}&client_secret=${process.env.OAUTH_SECRET}`,
        method: 'POST',
        headers: {
            Accept: 'application/json',
        }
    }, (login_res) => {
        login_res.setEncoding('utf8');
        const chunks = [];

        login_res.on('data', chunks.push);
        login_res.on('error', console.error);

        login_res.on('end', () => {
            const payload = JSON.parse(chunks.join(''));
            if (payload.error) {
                res.status(401).redirect(`${HOST}?login_failed`);
            } else {
                res.status(200).redirect(`${HOST}?access_token=${payload.access_token}&scope=${payload.scope}`);
            }
        });
    }).end();
};
```

Here's the code for the Google Cloud function which handles the Github Webhook and forwards it as a workflow dispatch event to [themeepbot bot workflow](https://github.com/moreeyesplz/themeepbot/blob/master/.github/workflows/bot.yml):

```js
const https = require('https');

exports.webhook = (req, res) => {
    // Validate github user-agent
    if (!req.headers['user-agent'].startsWith('GitHub-Hookshot')) {
        return res.sendStatus(404);
    }

    const event = req.headers['x-github-event'];
    if (!event) {
        console.error('Missing event from request payload');
        return res.sendStatus(404);
    }

    const body = JSON.stringify({
        ref: 'master',
        inputs: {
            payload: Buffer.from(JSON.stringify(req.body)).toString('base64'),
            event,
        }
    });
    const request = https.request({
        host: 'api.github.com',
        path: `/repos/moreeyesplz/themeepbot/actions/workflows/bot.yml/dispatches`,
        method: 'POST',
        headers: {
            Accept: 'application/vnd.github.v3+json',
            Authorization: `token ${process.env.GITHUB_TOKEN}`,
            'User-Agent': 'moreeyesplz',
            'Content-Type': 'application/json',
            'Content-Length': body.length,
        }
    }, (response) => {
        response.setEncoding('utf8');
        const chunks = [];
        response.on('data', (chunk) => chunks.push(chunk));
        response.on('end', () => {
            if (response.statusCode !== 204) {
                console.error(`Workflow not dispatched: ${chunks.join('')}`);
            }
            res.sendStatus(response.statusCode);
        })
    });
    request.write(body);
    request.end();
}
```
The main "implementation detail" here is that workflow event payloads have far more keys and nested objects than what the workflow_dispatch can receive as inputs (limit is 10). As a workaround, this event proxy simply base64 encodes the entire payload and forwards it as a single event for the downstream action to parse later. Thus, the "heavy lifting" that needs to interact with the Github APIs can be done in action-based workflows.

All the actions and workflow code are very light on dependencies, using only the actions toolkit.

- [actions/core](https://github.com/actions/toolkit/tree/master/packages/core)
- [actions/github](https://github.com/actions/toolkit/tree/master/packages/github)