├── .eslintignore ├── .gitignore ├── test ├── index.test.js ├── sampleIssueEvent.json └── samplePullRequestEventNegative.json ├── action.yml ├── .github └── workflows │ ├── automerge-release-pr-bump.yml │ ├── action-test.yml │ └── release.yml ├── LICENSE ├── package.json ├── .eslintrc ├── lib └── index.js └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const { processingEvent } = require('../lib/index'); 2 | 3 | describe('processingEvent()', () => { 4 | it('analyzis should log proper message to user that only given events are supported', async () => { 5 | console.log = jest.fn(); 6 | await processingEvent('newuser', {}); 7 | expect(console.log).toHaveBeenCalledWith('Only the following events are supported by the action: issues, issue_comment, pull_request_review_comment, pull_request, pull_request_review'); 8 | }); 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Code of conduct compliance through sentiments analysis' 2 | description: 'Analyze sentiments in issues and PRs to identify negative content that may violate your code of conduct.' 3 | inputs: 4 | gcp_key: 5 | description: 'Google Cloud Platform API key for accessing the analyzeSentiment API. If not provided, then default sentiment analysis is performed.' 6 | required: false 7 | outputs: 8 | sentiment: 9 | description: 'Information about the sentiment of information. It is up to you to decide weather the score is to low or it is fine.' 10 | source: 11 | description: 'Link to the resource that was evaluated.' 12 | negative: 13 | description: 'Coma-separated list of words that were identified as bad' 14 | runs: 15 | using: 'node12' 16 | main: 'dist/index.js' 17 | branding: 18 | icon: 'bell' 19 | color: red -------------------------------------------------------------------------------- /.github/workflows/automerge-release-pr-bump.yml: -------------------------------------------------------------------------------- 1 | name: Automerge release bump PR 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - labeled 7 | - unlabeled 8 | - synchronize 9 | - opened 10 | - edited 11 | - ready_for_review 12 | - reopened 13 | - unlocked 14 | pull_request_review: 15 | types: 16 | - submitted 17 | check_suite: 18 | types: 19 | - completed 20 | status: {} 21 | 22 | jobs: 23 | 24 | automerge: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Automerging 28 | uses: pascalgn/automerge-action@v0.7.5 29 | if: github.actor == 'github-action' 30 | env: 31 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 32 | GITHUB_LOGIN: github-action 33 | MERGE_LABELS: "" 34 | MERGE_METHOD: "squash" 35 | MERGE_COMMIT_MESSAGE: "pull-request-title" 36 | MERGE_RETRIES: "10" 37 | MERGE_RETRY_SLEEP: "10000" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Lukasz Gornicki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/action-test.yml: -------------------------------------------------------------------------------- 1 | name: 'testing workflow' 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | - edited 8 | issues: 9 | types: 10 | - opened 11 | - edited 12 | pull_request: 13 | types: 14 | - opened 15 | - edited 16 | pull_request_review: 17 | types: 18 | - submitted 19 | - edited 20 | pull_request_review_comment: 21 | types: 22 | - created 23 | - edited 24 | jobs: 25 | test: 26 | name: Checking sentiments 27 | runs-on: ubuntu-latest 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Check sentiment 31 | uses: ./ 32 | id: sentiments 33 | with: 34 | gcp_key: ${{ secrets.GCP_KEY }} 35 | - name: Echo sentiment 36 | run: > 37 | echo 'Sentiment: ${{steps.sentiments.outputs.sentiment}}' 38 | echo 'Source: ${{steps.sentiments.outputs.source}}' 39 | echo 'Negative words: ${{steps.sentiments.outputs.negative}}' 40 | - uses: someimportantcompany/github-actions-slack-message@v1 41 | if: steps.sentiments.outputs.sentiment < 0 42 | with: 43 | webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} 44 | text: Here ${{steps.sentiments.outputs.source}} you can find a potential negative text that requires your attention as the sentiment analysis score is ${{steps.sentiments.outputs.sentiment}} 45 | color: orange -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | release: 10 | name: 'Release GitHub' 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v2 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: 13 19 | - name: Install dependencies 20 | run: npm ci 21 | - name: Run tests 22 | run: npm test 23 | - name: Get version from package.json before release step 24 | id: initversion 25 | run: echo "::set-output name=version::$(npm run get-version --silent)" 26 | - name: Release to GitHub 27 | id: release 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | run: npm run release 31 | - name: Get version from package.json after release step 32 | id: extractver 33 | run: echo "::set-output name=version::$(npm run get-version --silent)" 34 | - name: Create Pull Request with updated package files 35 | if: steps.initversion.outputs.version != steps.extractver.outputs.version 36 | uses: peter-evans/create-pull-request@v2.4.4 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | commit-message: 'chore(release): ${{ steps.extractver.outputs.version }}' 40 | title: 'chore(release): ${{ steps.extractver.outputs.version }}' 41 | body: 'Version bump in package.json and package-lock.json for release [${{ steps.extractver.outputs.version }}](https://github.com/${{github.repository}}/releases/tag/v${{ steps.extractver.outputs.version }})' 42 | branch: version-bump/${{ steps.extractver.outputs.version }} 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "code-of-conduct-sentiment-analysis-github-action", 3 | "version": "1.0.1", 4 | "description": "Use this action to analyze sentiments in issues and pull request to identify emotions that need to be checked against the Code of Conduct", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "release": "semantic-release", 8 | "get-version": "echo $npm_package_version", 9 | "lint": "eslint --config .eslintrc lib", 10 | "package": "npm run test && ncc build lib/index.js -o dist", 11 | "test": "npm run lint && jest", 12 | "start": "node lib/index.js", 13 | "gitAdd": "git add dist/index.js", 14 | "gen-readme-toc": "markdown-toc -i README.md" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/derberg/code-of-conduct-sentiment-analysis-github-action.git" 19 | }, 20 | "author": "Lukasz Gornicki ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/derberg/code-of-conduct-sentiment-analysis-github-action/issues" 24 | }, 25 | "homepage": "https://github.com/derberg/code-of-conduct-sentiment-analysis-github-action#readme", 26 | "devDependencies": { 27 | "@semantic-release/commit-analyzer": "^8.0.1", 28 | "@semantic-release/github": "^7.0.4", 29 | "@semantic-release/npm": "^7.0.5", 30 | "@semantic-release/release-notes-generator": "^9.0.1", 31 | "@vercel/ncc": "^0.24.0", 32 | "conventional-changelog-conventionalcommits": "^4.2.3", 33 | "eslint": "^6.8.0", 34 | "eslint-plugin-jest": "^24.0.0", 35 | "eslint-plugin-sonarjs": "^0.5.0", 36 | "pre-commit": "^1.2.2", 37 | "semantic-release": "^17.0.4", 38 | "markdown-toc": "^1.2.0", 39 | "jest": "^24.9.0" 40 | }, 41 | "dependencies": { 42 | "@actions/core": "^1.2.3", 43 | "axios": "^0.20.0", 44 | "sentiment": "^5.0.2" 45 | }, 46 | "release": { 47 | "branches": [ 48 | "master" 49 | ], 50 | "plugins": [ 51 | [ 52 | "@semantic-release/commit-analyzer", 53 | { 54 | "preset": "conventionalcommits" 55 | } 56 | ], 57 | [ 58 | "@semantic-release/release-notes-generator", 59 | { 60 | "preset": "conventionalcommits" 61 | } 62 | ], 63 | [ 64 | "@semantic-release/npm", 65 | { 66 | "npmPublish": false 67 | } 68 | ], 69 | "@semantic-release/github" 70 | ] 71 | }, 72 | "pre-commit": [ 73 | "package", 74 | "gitAdd" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | es6: true 4 | jest/globals: true 5 | 6 | plugins: 7 | - sonarjs 8 | - jest 9 | 10 | extends: 11 | - plugin:sonarjs/recommended 12 | 13 | parserOptions: 14 | ecmaVersion: 2018 15 | 16 | rules: 17 | # Ignore Rules 18 | strict: 0 19 | no-underscore-dangle: 0 20 | no-mixed-requires: 0 21 | no-process-exit: 0 22 | no-warning-comments: 0 23 | curly: 0 24 | no-multi-spaces: 0 25 | no-alert: 0 26 | consistent-return: 0 27 | consistent-this: [0, self] 28 | func-style: 0 29 | max-nested-callbacks: 0 30 | 31 | # Warnings 32 | no-debugger: 1 33 | no-empty: 1 34 | no-invalid-regexp: 1 35 | no-unused-expressions: 1 36 | no-native-reassign: 1 37 | no-fallthrough: 1 38 | camelcase: 0 39 | 40 | # Errors 41 | eqeqeq: 2 42 | no-undef: 2 43 | no-dupe-keys: 2 44 | no-empty-character-class: 2 45 | no-self-compare: 2 46 | valid-typeof: 2 47 | no-unused-vars: [2, { "args": "none" }] 48 | handle-callback-err: 2 49 | no-shadow-restricted-names: 2 50 | no-new-require: 2 51 | no-mixed-spaces-and-tabs: 2 52 | block-scoped-var: 2 53 | no-else-return: 2 54 | no-throw-literal: 2 55 | no-void: 2 56 | radix: 2 57 | wrap-iife: [2, outside] 58 | no-shadow: 0 59 | no-use-before-define: [2, nofunc] 60 | no-path-concat: 2 61 | valid-jsdoc: [0, {requireReturn: false, requireParamDescription: false, requireReturnDescription: false}] 62 | 63 | # stylistic errors 64 | no-spaced-func: 2 65 | semi-spacing: 2 66 | quotes: [2, 'single'] 67 | key-spacing: [2, { beforeColon: false, afterColon: true }] 68 | indent: [2, 2] 69 | no-lonely-if: 2 70 | no-floating-decimal: 2 71 | brace-style: [2, 1tbs, { allowSingleLine: true }] 72 | comma-style: [2, last] 73 | no-multiple-empty-lines: [2, {max: 1}] 74 | no-nested-ternary: 2 75 | operator-assignment: [2, always] 76 | padded-blocks: [2, never] 77 | quote-props: [2, as-needed] 78 | keyword-spacing: [2, {'before': true, 'after': true, 'overrides': {}}] 79 | space-before-blocks: [2, always] 80 | array-bracket-spacing: [2, never] 81 | computed-property-spacing: [2, never] 82 | space-in-parens: [2, never] 83 | space-unary-ops: [2, {words: true, nonwords: false}] 84 | wrap-regex: 2 85 | linebreak-style: [2, unix] 86 | semi: [2, always] 87 | arrow-spacing: [2, {before: true, after: true}] 88 | no-class-assign: 2 89 | no-const-assign: 2 90 | no-dupe-class-members: 2 91 | no-this-before-super: 2 92 | no-var: 2 93 | object-shorthand: [2, always] 94 | prefer-arrow-callback: 2 95 | prefer-const: 2 96 | prefer-spread: 2 97 | prefer-template: 2 98 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const axios = require('axios'); 3 | const Sentiment = require('sentiment'); 4 | const eventPayload = require(process.env.GITHUB_EVENT_PATH); 5 | const eventName = process.env.GITHUB_EVENT_NAME; 6 | const sentiment = new Sentiment(); 7 | module.exports = { processingEvent }; 8 | 9 | async function run() { 10 | let url, result; 11 | 12 | try { 13 | [url, result] = await processingEvent(eventName, eventPayload); 14 | } catch (e) { 15 | console.log(e); 16 | } 17 | 18 | if (!result) return null; 19 | core.setOutput('source', url); 20 | core.setOutput('sentiment', result.score); 21 | if (result.negative) core.setOutput('negative', result.negative); 22 | } 23 | 24 | async function processingEvent(eventName, eventPayload) { 25 | let content, url, result; 26 | const gcp_key = process.env.GCP_KEY || core.getInput('gcp_key'); 27 | 28 | switch (eventName) { 29 | case 'issues': 30 | content = eventPayload.issue.body; 31 | url = eventPayload.issue.html_url; 32 | result = content && await analyzeSentiments(content, gcp_key); 33 | 34 | break; 35 | case 'issue_comment': 36 | case 'pull_request_review_comment': 37 | content = eventPayload.comment.body; 38 | url = eventPayload.comment.html_url; 39 | result = content && await analyzeSentiments(content, gcp_key); 40 | break; 41 | case 'pull_request': 42 | content = eventPayload.pull_request.body; 43 | url = eventPayload.pull_request.html_url; 44 | result = content && await analyzeSentiments(content, gcp_key); 45 | break; 46 | case 'pull_request_review': 47 | content = eventPayload.review.body; 48 | url = eventPayload.review.html_url; 49 | result = content && await analyzeSentiments(content, gcp_key); 50 | break; 51 | default: 52 | console.log('Only the following events are supported by the action: issues, issue_comment, pull_request_review_comment, pull_request, pull_request_review'); 53 | } 54 | 55 | return [url, result]; 56 | } 57 | 58 | /** 59 | * Triggers proper analyze funcion depending if GCP_KEY is provided or not 60 | * @private 61 | * 62 | * @param {String} content Content that must be checked for the sentiment 63 | * @param {String} key GCP API key 64 | */ 65 | async function analyzeSentiments(content, key) { 66 | if (key) 67 | return await analyzeSentimentsOnGCP(content, key); 68 | return analyzeSentimentsAFINN165(content); 69 | } 70 | 71 | /** 72 | * Calls sentiment package from NPM to do basic sentiment analytics 73 | * @private 74 | * 75 | * @param {String} content Content that must be checked for the sentiment 76 | * @param {String} key GCP API key 77 | * @returns {Object} object with score and negative parameters 78 | */ 79 | function analyzeSentimentsAFINN165(content) { 80 | const result = sentiment.analyze(content); 81 | core.debug(`Full sentiment library evaluation result: ${ result }`); 82 | return { score: result.comparative, negative: result.negative.join(', ') }; 83 | } 84 | 85 | /** 86 | * Calls GCP's Analyze Sentiment API 87 | * @private 88 | * 89 | * @param {String} content Content that must be checked for the sentiment 90 | * @param {String} key GCP API key 91 | * @returns {Object} object with score parameter 92 | */ 93 | async function analyzeSentimentsOnGCP(content, key) { 94 | let res; 95 | 96 | const request = { 97 | method: 'post', 98 | url: `https://language.googleapis.com/v1/documents:analyzeSentiment?key=${ key }`, 99 | headers: { 100 | 'Content-Type': 'application/json; charset=utf-8' 101 | }, 102 | data: { 103 | encodingType: 'UTF8', 104 | document: { 105 | type: 'PLAIN_TEXT', 106 | content 107 | } 108 | } 109 | }; 110 | 111 | try { 112 | res = await axios(request); 113 | } catch (e) { 114 | throw new Error(e.response.status === 400 ? 'Please provide a correct API key in GitHub Repository Secrets with key GCP_KEY' : e.message); 115 | } 116 | 117 | core.debug(`Full GCP response: ${ JSON.stringify(res.data) }`); 118 | return { score: res.data.documentSentiment.score }; 119 | }; 120 | 121 | run(); 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct Compliance Through Sentiments Analysis 2 | 3 | 4 | 5 | - [Overview](#overview) 6 | * [Why this action requires other actions?](#why-this-action-requires-other-actions) 7 | * [How sentiments are analized](#how-sentiments-are-analized) 8 | - [Examples](#examples) 9 | * [Basic example using AFINN wordlist](#basic-example-using-afinn-wordlist) 10 | * [Basic example using Google Natural Language API](#basic-example-using-google-natural-language-api) 11 | * [Example using Google API and Slack action](#example-using-google-api-and-slack-action) 12 | - [Troubleshooting](#troubleshooting) 13 | - [Development](#development) 14 | 15 | 16 | 17 | ## Overview 18 | 19 | Use this action to analyze sentiments in issues and pull requests to identify emotions that need to be checked against the Code of Conduct. 20 | This action only analyzes sentiments. Alone it is not very useful as it is just able to log the sentiment on GitHub Actions log level. It is intended to be used with other Github Actions. 21 | An example use case could be run this action and then send the results to Slack with another action. 22 | 23 | ### Why this action requires other actions? 24 | 25 | It is because it "only" analyzes the sentiment, but it is up to you to decide in the workflow setup the negative sentiment for you and what score you consider as something you should react on. 26 | 27 | ### How sentiments are analyzed 28 | 29 | This action can analyze sentiments using two different solutions: built-in and 3rd party. Out of the box, this action is integrated with [sentiment](https://www.npmjs.com/package/sentiment) package to do analytics based on [AFINN](http://www2.imm.dtu.dk/pubdb/pubs/6010-full.html) wordlist. Instead of such basic analytics, better use this action to communicate with [Google Natural Language API](https://cloud.google.com/natural-language/docs/basics). 30 | 31 | ## Examples 32 | 33 | ### Basic example using AFINN wordlist 34 | 35 | Below you can find a basic example of how you can use the action without any other actions. It analyzes the sentiment and puts the information in the logs. 36 | 37 | ```yaml 38 | name: 'Sentiment analysis' 39 | on: 40 | issue_comment: 41 | types: 42 | - created 43 | - edited 44 | issues: 45 | types: 46 | - opened 47 | - edited 48 | pull_request: 49 | types: 50 | - opened 51 | - edited 52 | pull_request_review: 53 | types: 54 | - submitted 55 | - edited 56 | pull_request_review_comment: 57 | types: 58 | - created 59 | - edited 60 | jobs: 61 | test: 62 | name: Checking sentiments 63 | runs-on: ubuntu-latest 64 | steps: 65 | - uses: actions/checkout@v2 66 | - name: Check sentiment 67 | uses: derberg/code-of-conduct-sentiment-analysis-github-action@v1 68 | id: sentiments 69 | - name: Echo sentiment 70 | run: > 71 | echo 'Sentiment: ${{steps.sentiments.outputs.sentiment}}' 72 | echo 'Source: ${{steps.sentiments.outputs.source}}' 73 | echo 'Negative words: ${{steps.sentiments.outputs.negative}}' 74 | ``` 75 | 76 | ### Basic example using Google Natural Language API 77 | 78 | Below you can find a basic example of how you can use the action with Google API. It analyzes the sentiment and puts the information in the logs. Google API key should be stored in [GitHub secrets](https://docs.github.com/en/actions/configuring-and-managing-workflows/creating-and-storing-encrypted-secrets) under `GCP_KEY` name. 79 | 80 | Google API is [not expensive](https://cloud.google.com/natural-language/pricing). It is free up to 5k requests and 1$ up to 1m requests. 81 | 82 | 83 | ```yaml 84 | name: 'Sentiment analysis' 85 | on: 86 | issue_comment: 87 | types: 88 | - created 89 | - edited 90 | issues: 91 | types: 92 | - opened 93 | - edited 94 | pull_request: 95 | types: 96 | - opened 97 | - edited 98 | pull_request_review: 99 | types: 100 | - submitted 101 | - edited 102 | pull_request_review_comment: 103 | types: 104 | - created 105 | - edited 106 | jobs: 107 | test: 108 | name: Checking sentiments 109 | runs-on: ubuntu-latest 110 | steps: 111 | - uses: actions/checkout@v2 112 | - name: Check sentiment 113 | uses: derberg/code-of-conduct-sentiment-analysis-github-action@v1 114 | id: sentiments 115 | with: 116 | # you can find an instruction on how to setup Google project with access to Natural Language API here https://github.com/BogDAAAMN/copy-sentiment-analysis#gcp_key-get-your-gcp-api-key 117 | gcp_key: ${{ secrets.GCP_KEY }} 118 | - name: Echo sentiment 119 | run: > 120 | echo 'Sentiment: ${{steps.sentiments.outputs.sentiment}}' 121 | echo 'Source: ${{steps.sentiments.outputs.source}}' 122 | echo 'Negative words: ${{steps.sentiments.outputs.negative}}' 123 | ``` 124 | 125 | ### Example using Google API and Slack action 126 | 127 | The below example shows how you can use this action with other actions. In this case, you can use a Slack action to send information about the sentiment to a specific Slack channel. 128 | 129 | ```yaml 130 | name: 'testing workflow' 131 | 132 | on: 133 | issue_comment: 134 | types: 135 | - created 136 | - edited 137 | issues: 138 | types: 139 | - opened 140 | - edited 141 | pull_request: 142 | types: 143 | - opened 144 | - edited 145 | pull_request_review: 146 | types: 147 | - submitted 148 | - edited 149 | pull_request_review_comment: 150 | types: 151 | - created 152 | - edited 153 | jobs: 154 | test: 155 | name: Checking sentiments 156 | runs-on: ubuntu-latest 157 | steps: 158 | - uses: actions/checkout@v2 159 | - name: Check sentiment 160 | uses: derberg/code-of-conduct-sentiment-analysis-github-action@v1 161 | id: sentiments 162 | with: 163 | # you can find an instruction on how to setup Google project with access to Natural Language API here https://github.com/BogDAAAMN/copy-sentiment-analysis#gcp_key-get-your-gcp-api-key 164 | gcp_key: ${{ secrets.GCP_KEY }} 165 | - uses: someimportantcompany/github-actions-slack-message@v1 166 | # this step runs only if sentiment is a negative numner 167 | if: steps.sentiments.outputs.sentiment < 0 168 | with: 169 | # to get a webhook url of your channel use this documentation https://slack.com/intl/en-pl/help/articles/115005265063-Incoming-webhooks-for-Slack 170 | # first register new app here (https://api.slack.com/apps 171 | webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} 172 | text: Here ${{steps.sentiments.outputs.source}} you can find a potential negative text that requires your attention as the sentiment analysis score is ${{steps.sentiments.outputs.sentiment}} 173 | color: orange 174 | ``` 175 | 176 | ## Troubleshooting 177 | 178 | You can enable more log information in GitHub Action by adding `ACTIONS_STEP_DEBUG` secret to the repository where you want to use this action. Set the value of this secret to `true`, and you'll notice more debug logs from this action. 179 | 180 | ## Development 181 | 182 | 1. Produce action distribution `npm run package` 183 | 2. Run the action against a sample event: 184 | ```bash 185 | # good sentiment 186 | GITHUB_EVENT_PATH=../test/sampleIssueEvent.json GITHUB_EVENT_NAME=issues npm run start 187 | # higher than 0.2 so close to being negative 188 | GITHUB_EVENT_PATH=../test/samplePullRequestEventNegative.json GITHUB_EVENT_NAME=pull_request npm run start 189 | ``` -------------------------------------------------------------------------------- /test/sampleIssueEvent.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "edited", 3 | "issue": { 4 | "url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1", 5 | "repository_url": "https://api.github.com/repos/Codertocat/Hello-World", 6 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/labels{/name}", 7 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/comments", 8 | "events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/1/events", 9 | "html_url": "https://github.com/Codertocat/Hello-World/issues/1", 10 | "id": 444500041, 11 | "node_id": "MDU6SXNzdWU0NDQ1MDAwNDE=", 12 | "number": 1, 13 | "title": "Spelling error in the README file", 14 | "user": { 15 | "login": "Codertocat", 16 | "id": 21031067, 17 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 18 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 19 | "gravatar_id": "", 20 | "url": "https://api.github.com/users/Codertocat", 21 | "html_url": "https://github.com/Codertocat", 22 | "followers_url": "https://api.github.com/users/Codertocat/followers", 23 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 24 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 25 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 26 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 27 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 28 | "repos_url": "https://api.github.com/users/Codertocat/repos", 29 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 30 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 31 | "type": "User", 32 | "site_admin": false 33 | }, 34 | "labels": [ 35 | { 36 | "id": 1362934389, 37 | "node_id": "MDU6TGFiZWwxMzYyOTM0Mzg5", 38 | "url": "https://api.github.com/repos/Codertocat/Hello-World/labels/bug", 39 | "name": "bug", 40 | "color": "d73a4a", 41 | "default": true 42 | } 43 | ], 44 | "state": "open", 45 | "locked": false, 46 | "assignee": { 47 | "login": "Codertocat", 48 | "id": 21031067, 49 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 50 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 51 | "gravatar_id": "", 52 | "url": "https://api.github.com/users/Codertocat", 53 | "html_url": "https://github.com/Codertocat", 54 | "followers_url": "https://api.github.com/users/Codertocat/followers", 55 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 56 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 57 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 58 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 59 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 60 | "repos_url": "https://api.github.com/users/Codertocat/repos", 61 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 62 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 63 | "type": "User", 64 | "site_admin": false 65 | }, 66 | "assignees": [ 67 | { 68 | "login": "Codertocat", 69 | "id": 21031067, 70 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 71 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 72 | "gravatar_id": "", 73 | "url": "https://api.github.com/users/Codertocat", 74 | "html_url": "https://github.com/Codertocat", 75 | "followers_url": "https://api.github.com/users/Codertocat/followers", 76 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 77 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 78 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 79 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 80 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 81 | "repos_url": "https://api.github.com/users/Codertocat/repos", 82 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 83 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 84 | "type": "User", 85 | "site_admin": false 86 | } 87 | ], 88 | "milestone": { 89 | "url": "https://api.github.com/repos/Codertocat/Hello-World/milestones/1", 90 | "html_url": "https://github.com/Codertocat/Hello-World/milestone/1", 91 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones/1/labels", 92 | "id": 4317517, 93 | "node_id": "MDk6TWlsZXN0b25lNDMxNzUxNw==", 94 | "number": 1, 95 | "title": "v1.0", 96 | "description": "Add new space flight simulator", 97 | "creator": { 98 | "login": "Codertocat", 99 | "id": 21031067, 100 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 101 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 102 | "gravatar_id": "", 103 | "url": "https://api.github.com/users/Codertocat", 104 | "html_url": "https://github.com/Codertocat", 105 | "followers_url": "https://api.github.com/users/Codertocat/followers", 106 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 107 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 108 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 109 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 110 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 111 | "repos_url": "https://api.github.com/users/Codertocat/repos", 112 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 113 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 114 | "type": "User", 115 | "site_admin": false 116 | }, 117 | "open_issues": 1, 118 | "closed_issues": 0, 119 | "state": "closed", 120 | "created_at": "2019-05-15T15:20:17Z", 121 | "updated_at": "2019-05-15T15:20:18Z", 122 | "due_on": "2019-05-23T07:00:00Z", 123 | "closed_at": "2019-05-15T15:20:18Z" 124 | }, 125 | "comments": 0, 126 | "created_at": "2019-05-15T15:20:18Z", 127 | "updated_at": "2019-05-15T15:20:18Z", 128 | "closed_at": null, 129 | "author_association": "OWNER", 130 | "body": "It looks like you accidently spelled 'commit' with two 't's." 131 | }, 132 | "changes": { 133 | }, 134 | "repository": { 135 | "id": 186853002, 136 | "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", 137 | "name": "Hello-World", 138 | "full_name": "Codertocat/Hello-World", 139 | "private": false, 140 | "owner": { 141 | "login": "Codertocat", 142 | "id": 21031067, 143 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 144 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 145 | "gravatar_id": "", 146 | "url": "https://api.github.com/users/Codertocat", 147 | "html_url": "https://github.com/Codertocat", 148 | "followers_url": "https://api.github.com/users/Codertocat/followers", 149 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 150 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 151 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 152 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 153 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 154 | "repos_url": "https://api.github.com/users/Codertocat/repos", 155 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 156 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 157 | "type": "User", 158 | "site_admin": false 159 | }, 160 | "html_url": "https://github.com/Codertocat/Hello-World", 161 | "description": null, 162 | "fork": false, 163 | "url": "https://api.github.com/repos/Codertocat/Hello-World", 164 | "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", 165 | "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", 166 | "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", 167 | "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", 168 | "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", 169 | "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", 170 | "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", 171 | "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", 172 | "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", 173 | "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", 174 | "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", 175 | "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", 176 | "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", 177 | "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", 178 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", 179 | "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", 180 | "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", 181 | "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", 182 | "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", 183 | "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", 184 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", 185 | "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", 186 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", 187 | "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", 188 | "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", 189 | "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", 190 | "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", 191 | "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", 192 | "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", 193 | "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", 194 | "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", 195 | "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", 196 | "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", 197 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", 198 | "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", 199 | "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", 200 | "created_at": "2019-05-15T15:19:25Z", 201 | "updated_at": "2019-05-15T15:19:27Z", 202 | "pushed_at": "2019-05-15T15:20:13Z", 203 | "git_url": "git://github.com/Codertocat/Hello-World.git", 204 | "ssh_url": "git@github.com:Codertocat/Hello-World.git", 205 | "clone_url": "https://github.com/Codertocat/Hello-World.git", 206 | "svn_url": "https://github.com/Codertocat/Hello-World", 207 | "homepage": null, 208 | "size": 0, 209 | "stargazers_count": 0, 210 | "watchers_count": 0, 211 | "language": null, 212 | "has_issues": true, 213 | "has_projects": true, 214 | "has_downloads": true, 215 | "has_wiki": true, 216 | "has_pages": true, 217 | "forks_count": 0, 218 | "mirror_url": null, 219 | "archived": false, 220 | "disabled": false, 221 | "open_issues_count": 1, 222 | "license": null, 223 | "forks": 0, 224 | "open_issues": 1, 225 | "watchers": 0, 226 | "default_branch": "master" 227 | }, 228 | "sender": { 229 | "login": "Codertocat", 230 | "id": 21031067, 231 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 232 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 233 | "gravatar_id": "", 234 | "url": "https://api.github.com/users/Codertocat", 235 | "html_url": "https://github.com/Codertocat", 236 | "followers_url": "https://api.github.com/users/Codertocat/followers", 237 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 238 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 239 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 240 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 241 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 242 | "repos_url": "https://api.github.com/users/Codertocat/repos", 243 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 244 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 245 | "type": "User", 246 | "site_admin": false 247 | } 248 | } -------------------------------------------------------------------------------- /test/samplePullRequestEventNegative.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "opened", 3 | "number": 2, 4 | "pull_request": { 5 | "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2", 6 | "id": 279147437, 7 | "node_id": "MDExOlB1bGxSZXF1ZXN0Mjc5MTQ3NDM3", 8 | "html_url": "https://github.com/Codertocat/Hello-World/pull/2", 9 | "diff_url": "https://github.com/Codertocat/Hello-World/pull/2.diff", 10 | "patch_url": "https://github.com/Codertocat/Hello-World/pull/2.patch", 11 | "issue_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2", 12 | "number": 2, 13 | "state": "open", 14 | "locked": false, 15 | "title": "Update the README with new information.", 16 | "user": { 17 | "login": "Codertocat", 18 | "id": 21031067, 19 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 20 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 21 | "gravatar_id": "", 22 | "url": "https://api.github.com/users/Codertocat", 23 | "html_url": "https://github.com/Codertocat", 24 | "followers_url": "https://api.github.com/users/Codertocat/followers", 25 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 26 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 27 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 28 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 29 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 30 | "repos_url": "https://api.github.com/users/Codertocat/repos", 31 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 32 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 33 | "type": "User", 34 | "site_admin": false 35 | }, 36 | "body": "This PR is fixing your mistakes", 37 | "created_at": "2019-05-15T15:20:33Z", 38 | "updated_at": "2019-05-15T15:20:33Z", 39 | "closed_at": null, 40 | "merged_at": null, 41 | "merge_commit_sha": null, 42 | "assignee": null, 43 | "assignees": [ 44 | 45 | ], 46 | "requested_reviewers": [ 47 | 48 | ], 49 | "requested_teams": [ 50 | 51 | ], 52 | "labels": [ 53 | 54 | ], 55 | "milestone": null, 56 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2/commits", 57 | "review_comments_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2/comments", 58 | "review_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}", 59 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/2/comments", 60 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/ec26c3e57ca3a959ca5aad62de7213c562f8c821", 61 | "head": { 62 | "label": "Codertocat:changes", 63 | "ref": "changes", 64 | "sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", 65 | "user": { 66 | "login": "Codertocat", 67 | "id": 21031067, 68 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 69 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 70 | "gravatar_id": "", 71 | "url": "https://api.github.com/users/Codertocat", 72 | "html_url": "https://github.com/Codertocat", 73 | "followers_url": "https://api.github.com/users/Codertocat/followers", 74 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 75 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 76 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 77 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 78 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 79 | "repos_url": "https://api.github.com/users/Codertocat/repos", 80 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 81 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 82 | "type": "User", 83 | "site_admin": false 84 | }, 85 | "repo": { 86 | "id": 186853002, 87 | "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", 88 | "name": "Hello-World", 89 | "full_name": "Codertocat/Hello-World", 90 | "private": false, 91 | "owner": { 92 | "login": "Codertocat", 93 | "id": 21031067, 94 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 95 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 96 | "gravatar_id": "", 97 | "url": "https://api.github.com/users/Codertocat", 98 | "html_url": "https://github.com/Codertocat", 99 | "followers_url": "https://api.github.com/users/Codertocat/followers", 100 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 101 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 102 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 103 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 104 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 105 | "repos_url": "https://api.github.com/users/Codertocat/repos", 106 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 107 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 108 | "type": "User", 109 | "site_admin": false 110 | }, 111 | "html_url": "https://github.com/Codertocat/Hello-World", 112 | "description": null, 113 | "fork": false, 114 | "url": "https://api.github.com/repos/Codertocat/Hello-World", 115 | "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", 116 | "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", 117 | "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", 118 | "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", 119 | "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", 120 | "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", 121 | "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", 122 | "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", 123 | "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", 124 | "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", 125 | "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", 126 | "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", 127 | "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", 128 | "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", 129 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", 130 | "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", 131 | "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", 132 | "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", 133 | "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", 134 | "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", 135 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", 136 | "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", 137 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", 138 | "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", 139 | "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", 140 | "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", 141 | "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", 142 | "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", 143 | "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", 144 | "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", 145 | "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", 146 | "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", 147 | "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", 148 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", 149 | "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", 150 | "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", 151 | "created_at": "2019-05-15T15:19:25Z", 152 | "updated_at": "2019-05-15T15:19:27Z", 153 | "pushed_at": "2019-05-15T15:20:32Z", 154 | "git_url": "git://github.com/Codertocat/Hello-World.git", 155 | "ssh_url": "git@github.com:Codertocat/Hello-World.git", 156 | "clone_url": "https://github.com/Codertocat/Hello-World.git", 157 | "svn_url": "https://github.com/Codertocat/Hello-World", 158 | "homepage": null, 159 | "size": 0, 160 | "stargazers_count": 0, 161 | "watchers_count": 0, 162 | "language": null, 163 | "has_issues": true, 164 | "has_projects": true, 165 | "has_downloads": true, 166 | "has_wiki": true, 167 | "has_pages": true, 168 | "forks_count": 0, 169 | "mirror_url": null, 170 | "archived": false, 171 | "disabled": false, 172 | "open_issues_count": 2, 173 | "license": null, 174 | "forks": 0, 175 | "open_issues": 2, 176 | "watchers": 0, 177 | "default_branch": "master" 178 | } 179 | }, 180 | "base": { 181 | "label": "Codertocat:master", 182 | "ref": "master", 183 | "sha": "f95f852bd8fca8fcc58a9a2d6c842781e32a215e", 184 | "user": { 185 | "login": "Codertocat", 186 | "id": 21031067, 187 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 188 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 189 | "gravatar_id": "", 190 | "url": "https://api.github.com/users/Codertocat", 191 | "html_url": "https://github.com/Codertocat", 192 | "followers_url": "https://api.github.com/users/Codertocat/followers", 193 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 194 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 195 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 196 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 197 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 198 | "repos_url": "https://api.github.com/users/Codertocat/repos", 199 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 200 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 201 | "type": "User", 202 | "site_admin": false 203 | }, 204 | "repo": { 205 | "id": 186853002, 206 | "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", 207 | "name": "Hello-World", 208 | "full_name": "Codertocat/Hello-World", 209 | "private": false, 210 | "owner": { 211 | "login": "Codertocat", 212 | "id": 21031067, 213 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 214 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 215 | "gravatar_id": "", 216 | "url": "https://api.github.com/users/Codertocat", 217 | "html_url": "https://github.com/Codertocat", 218 | "followers_url": "https://api.github.com/users/Codertocat/followers", 219 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 220 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 221 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 222 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 223 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 224 | "repos_url": "https://api.github.com/users/Codertocat/repos", 225 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 226 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 227 | "type": "User", 228 | "site_admin": false 229 | }, 230 | "html_url": "https://github.com/Codertocat/Hello-World", 231 | "description": null, 232 | "fork": false, 233 | "url": "https://api.github.com/repos/Codertocat/Hello-World", 234 | "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", 235 | "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", 236 | "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", 237 | "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", 238 | "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", 239 | "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", 240 | "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", 241 | "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", 242 | "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", 243 | "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", 244 | "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", 245 | "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", 246 | "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", 247 | "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", 248 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", 249 | "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", 250 | "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", 251 | "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", 252 | "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", 253 | "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", 254 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", 255 | "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", 256 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", 257 | "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", 258 | "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", 259 | "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", 260 | "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", 261 | "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", 262 | "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", 263 | "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", 264 | "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", 265 | "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", 266 | "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", 267 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", 268 | "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", 269 | "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", 270 | "created_at": "2019-05-15T15:19:25Z", 271 | "updated_at": "2019-05-15T15:19:27Z", 272 | "pushed_at": "2019-05-15T15:20:32Z", 273 | "git_url": "git://github.com/Codertocat/Hello-World.git", 274 | "ssh_url": "git@github.com:Codertocat/Hello-World.git", 275 | "clone_url": "https://github.com/Codertocat/Hello-World.git", 276 | "svn_url": "https://github.com/Codertocat/Hello-World", 277 | "homepage": null, 278 | "size": 0, 279 | "stargazers_count": 0, 280 | "watchers_count": 0, 281 | "language": null, 282 | "has_issues": true, 283 | "has_projects": true, 284 | "has_downloads": true, 285 | "has_wiki": true, 286 | "has_pages": true, 287 | "forks_count": 0, 288 | "mirror_url": null, 289 | "archived": false, 290 | "disabled": false, 291 | "open_issues_count": 2, 292 | "license": null, 293 | "forks": 0, 294 | "open_issues": 2, 295 | "watchers": 0, 296 | "default_branch": "master" 297 | } 298 | }, 299 | "_links": { 300 | "self": { 301 | "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2" 302 | }, 303 | "html": { 304 | "href": "https://github.com/Codertocat/Hello-World/pull/2" 305 | }, 306 | "issue": { 307 | "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/2" 308 | }, 309 | "comments": { 310 | "href": "https://api.github.com/repos/Codertocat/Hello-World/issues/2/comments" 311 | }, 312 | "review_comments": { 313 | "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2/comments" 314 | }, 315 | "review_comment": { 316 | "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/comments{/number}" 317 | }, 318 | "commits": { 319 | "href": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2/commits" 320 | }, 321 | "statuses": { 322 | "href": "https://api.github.com/repos/Codertocat/Hello-World/statuses/ec26c3e57ca3a959ca5aad62de7213c562f8c821" 323 | } 324 | }, 325 | "author_association": "OWNER", 326 | "draft": false, 327 | "merged": false, 328 | "mergeable": null, 329 | "rebaseable": null, 330 | "mergeable_state": "unknown", 331 | "merged_by": null, 332 | "comments": 0, 333 | "review_comments": 0, 334 | "maintainer_can_modify": false, 335 | "commits": 1, 336 | "additions": 1, 337 | "deletions": 1, 338 | "changed_files": 1 339 | }, 340 | "repository": { 341 | "id": 186853002, 342 | "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", 343 | "name": "Hello-World", 344 | "full_name": "Codertocat/Hello-World", 345 | "private": false, 346 | "owner": { 347 | "login": "Codertocat", 348 | "id": 21031067, 349 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 350 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 351 | "gravatar_id": "", 352 | "url": "https://api.github.com/users/Codertocat", 353 | "html_url": "https://github.com/Codertocat", 354 | "followers_url": "https://api.github.com/users/Codertocat/followers", 355 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 356 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 357 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 358 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 359 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 360 | "repos_url": "https://api.github.com/users/Codertocat/repos", 361 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 362 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 363 | "type": "User", 364 | "site_admin": false 365 | }, 366 | "html_url": "https://github.com/Codertocat/Hello-World", 367 | "description": null, 368 | "fork": false, 369 | "url": "https://api.github.com/repos/Codertocat/Hello-World", 370 | "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", 371 | "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", 372 | "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", 373 | "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", 374 | "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", 375 | "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", 376 | "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", 377 | "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", 378 | "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", 379 | "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", 380 | "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", 381 | "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", 382 | "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", 383 | "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", 384 | "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", 385 | "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", 386 | "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", 387 | "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", 388 | "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", 389 | "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", 390 | "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", 391 | "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", 392 | "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", 393 | "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", 394 | "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", 395 | "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", 396 | "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", 397 | "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", 398 | "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", 399 | "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", 400 | "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", 401 | "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", 402 | "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", 403 | "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", 404 | "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", 405 | "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", 406 | "created_at": "2019-05-15T15:19:25Z", 407 | "updated_at": "2019-05-15T15:19:27Z", 408 | "pushed_at": "2019-05-15T15:20:32Z", 409 | "git_url": "git://github.com/Codertocat/Hello-World.git", 410 | "ssh_url": "git@github.com:Codertocat/Hello-World.git", 411 | "clone_url": "https://github.com/Codertocat/Hello-World.git", 412 | "svn_url": "https://github.com/Codertocat/Hello-World", 413 | "homepage": null, 414 | "size": 0, 415 | "stargazers_count": 0, 416 | "watchers_count": 0, 417 | "language": null, 418 | "has_issues": true, 419 | "has_projects": true, 420 | "has_downloads": true, 421 | "has_wiki": true, 422 | "has_pages": true, 423 | "forks_count": 0, 424 | "mirror_url": null, 425 | "archived": false, 426 | "disabled": false, 427 | "open_issues_count": 2, 428 | "license": null, 429 | "forks": 0, 430 | "open_issues": 2, 431 | "watchers": 0, 432 | "default_branch": "master" 433 | }, 434 | "sender": { 435 | "login": "Codertocat", 436 | "id": 21031067, 437 | "node_id": "MDQ6VXNlcjIxMDMxMDY3", 438 | "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", 439 | "gravatar_id": "", 440 | "url": "https://api.github.com/users/Codertocat", 441 | "html_url": "https://github.com/Codertocat", 442 | "followers_url": "https://api.github.com/users/Codertocat/followers", 443 | "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", 444 | "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", 445 | "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", 446 | "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", 447 | "organizations_url": "https://api.github.com/users/Codertocat/orgs", 448 | "repos_url": "https://api.github.com/users/Codertocat/repos", 449 | "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", 450 | "received_events_url": "https://api.github.com/users/Codertocat/received_events", 451 | "type": "User", 452 | "site_admin": false 453 | } 454 | } --------------------------------------------------------------------------------