feat: import contribute tools (#38509)
* first commit
* add: adding all files
* added package-lock.json
* fix: labler and getOpenPrs working
* fix: fixed bug with prFilter function
* feat: can check for multiple errors
* add: install more libraries
* refactor: uses getOpenPrs to get PRs
* packages: modified
* feature: added addComment feature
* feature: added ability to check guide errors
* add: added guideFolderChecks
* fix: corrected issue with open/close
* update: installed new libraries
* fix: updated guide checker
* feat: create license
* fix: update the comment template (#2)
* fix: comment section
* fix: add org name keys
* fix: corrected guide error logic
* fix: removed console.log
Co-Authored-By: RandellDawson <5313213+RandellDawson@users.noreply.github.com>
* fix: corrected guideFolderChecks reduce
* refactor: removed setInterval logic
* feat: added new utilities module
* fix: addComment and labler issues
* feat: changed to module.exports
* fix: improved log file data
* fix: use config var for rateLimiter
Co-Authored-By: RandellDawson <5313213+RandellDawson@users.noreply.github.com>
* fix: used config var for rateLimiter
Co-Authored-By: RandellDawson <5313213+RandellDawson@users.noreply.github.com>
* fix: add vars for production
* fix: add work-logs config
* fix: update gitconfig for work-logs
* fix: update path to work-logs
* docs(readme): add exception for admin v. user
* fix: dotenv will coerce boolean from string (#10)
* fix: changed to sync file save
* feat: updated label script
* feat: installed new libraries
* feat: renamed labelOpenPrs.js to sweeper.js
* feat: 1st attempt repo restructure
* feat: added descriptions to one-off scripts
* fix: rewrote the missing frontmatter message
* feat: created one-off scripts to find frontmatter issues
* fix: add links to relevant style guide
* fix: corrected gitignore input-files
* feat: renamed files/folders to lowercase
* fix: changed prStat.js to pr-stats.js
* fix: renamed prStat.js to pr-stats.js
* fix: renamed addLabels.js to add-labels.js
* fix: deleted typo
* feat: changed .env variables
* fix: added rateLimiter after retrieving pr files
* feat: improved logging functionality
* fix: added catch for unexpected frontmatter error
* fix: changed verbiage unexpected frontmatter error
* fix: correct logging bug
* feat: improved findFailures.js and closeOpen.js
* feat: modified the close/open scripts
* refac: moved labeler.js to pr-tasks
* fix: changed rateLimter time in close-open
* fix: changed delay between close/open
* install: added cli-progress library
* feat: added 2 progress bars for sweeper
* fix: changed rateLimiter for pr-relations script
* fix: corrected directory path for renaming of file to work
* first commit
* first commit
* test travis setup
* more travis setup
* get payload
* test
* test
* test
* app yml pull_requests write
* non-working pullRequest createComment
* problem setting permissions
* create-react-app setup.
* Added styled-components dependency.
* Merge pull request #1 from RandellDawson/feature/prototype
Created prototype.
* Merge pull request #3 from RandellDawson/feat/add-links-to-results
fix: improved input elem behavior
* wip
* test on repo
* tests passing
* fix test title
* issues--write in app.yml
* fix getLabel to createLabel pipeline
* this github scope
* Merge pull request #5 from RandellDawson/refactor/input-react-component
Implemented ref forwarding for Input component. Minor clean up.
* Merge pull request #6 from RandellDawson/feature/search-with-enter-keypress
Added the ability to search by pressing Enter. Associated refactoring.
* Merge pull request #7 from RandellDawson/feature/display-username-with-prs
Added the ability for usernames to be displayed along with found PRs.
* Merge pull request #8 from RandellDawson/feat/report-improvements
Feature: Added Pareto Report and Tabs Navigation
* Merge pull request #9 from RandellDawson/feature/styled-tab-borders
Restyled tab borders to remove doubled-up borders.
* Merge pull request #10 from RandellDawson/feature/styled-components-theme
Implemented styled-components theme.
* Merge pull request #11 from RandellDawson/feature/root-package-json
Adde `package.json` and `.gitignore` to the root folder.
* Merge pull request #14 from RandellDawson/feat/create-search-component
[feature] created Search component
* Merge pull request #16 from RandellDawson/feat/create-search-component
[Feature] Finalized Search Component functionality
* Merge pull request #18 from RandellDawson/fix/correct-search-result-messages
[Fix] Correct search result messages and add info footer
* Merge pull request #19 from RandellDawson/fix/change-link-color
[Fix] Changed links from blue to 006400
* refactor: move sweeper logic to its own directory
* feat: lernafy the sweeper
* refactor: prepare for dashboard backend move
* docs: re-arrange files for cleaner structure
* refactor: prepare @tbushman 's probot for move
* fix: prepare for importing probot
* chore(.gitignore): update root level and remove redundant copy
* refactor: prepare dashboard-client for move
* refactor: prepare repo for bringing in dashboard client
* chore: install deps, remove clutter
* feat: improve efficiency of pr relations script (#23)
* chore: update lock file (#24)
* Feature: API endpoint constants (#30)
* Implemented expeced behaviour for text input.
* Added constants for API endpoints. Refactored Footer, Pareto and Search components accordingly.
* fix: fixed several issues after move to mono repo (#31)
This PR has the following items:
1. Introduces a couple of fixes to the sweeper script from issues right before the move to the mono repo. These were related to moving labeler out of the root of the sweeper folder and placing into the pr-tasks sub-folder.
2. Combined two scripts which were being used to update the data manually on Glitch (pr-relations.glitch.me). The new one-off script (get-pr-relations-data.js) downloads the current data.json and then pulls down applicable Github data which updates the data.json and automatically uploads it back to the Glitch server.
3. In an effort to use the same log file across all current one-off scripts and sweeper.js, the ProcessLog class was modified, so now every log created has the same JSON structure as is uploaded to the Glitch server. This removed a lot of redundant code across 3 files.
4. During a pair-coding session with @honmanyau, we tweaked the UI for the Dashboard app to give it a more consistent look across all views.
5. Based on feedback from a couple of the moderators using the new Dashboard app, the PR title was added along with some other styling.
6. Added some environment variables for running dashboard-api and dashboard-client in a development mode.
7. Added a sample_data.json file as a starter file, so someone does not have to do a full data pull to get things up and running.
8. Modified Pareto view to only display files with 2 or more PRs to speed up the report.
* fix(.env): update paths relative from root of repo (#37)
* docs: update notes on the setup
* feat(tools): add eslint and prettier (#19)
* refactor(lint): apply linting from the tools (#39)
* Revert "refactor(lint): apply linting from the tools (#39)" (#40)
This reverts commit 8be7d12cb3
.
* chore: made changes to move off of Glitch
* fix: removed extra lines from sample.env
* docs: added local setup instructions
* docs: update local setup instruction
* docs: update contributing
* docs: create readme.md
* fix(client): CRA config conflicts with the babel-eslint from root (#42)
* feat: add routes and mongodb (#34)
* fix: removed dependace on data.json
* docs: updated README.md
* fix: corrected issues with pareto.js and pr.js
* refactor: simplified mongoose related logic
* feat: added seed shortcut to package.json
* fix: corrected typo
* fix: fixed bug with info.js
* fix: simplified info.js
* fix: corrected updatedAt Schema issue
* fix: corrected typo in comment
* chore(lockfile): update after install from root
* fix: add a seed script to root
* Fix/restructuring and unify code logic (#48)
* fix: first pass at restructure linting cleanup
* fix: second pass of restructure
* fix: corrected path for index.html
* Fix/restructuring (#49)
* moving server scripts to probot/index.js
* non-working express.static config
* config move for seed
* change constant API_HOST to /contribute/
* remove winston
* remove error middleware
* put back client in lerna.json
* fix: fixed CRA static folder issue
* fix: added work-logs folder for one-offs
* small changes
* change /contribute route to /dashboard
* fix: use cross-env in package.json
* formatted with prettier (#50)
* encode mongo uri (#51)
* fix: removed ref to probot/client/.env
* Fixed the format command (#55)
* chore: update lockfile (#56)
* fix: corrected config.oneoff.productionRun issue (#52)
In an earlier PR I use `joi` to make PRODUCTION_RUN environment variables a Boolean, but did not change to the correct reference and did not change the code logic to compare to a Boolean instead of a String value (as it had been before). This PR corrects that logic, so that the one-off scripts will work in a production mode again. Also, removed the now unnecessary ` SKIP_PREFLIGHT_CHECK` environment variable from the `probot/.env` file.
* fix: ran npm run format
* fix: corrected some require paths for update-db.js
* fix: drop only specified collections in models.js
* fix: removed lerna for two run scripts
* remove unnecessary environment variables and npm scripts (#59)
* fix: removed config variables not needed
* fix: added pem files to gitignore
* feat: added one-off-script to add english labels
* fix: modified script description
* fix: updated script to use aggregate function
* fix: added note about needing to change url
* init tests
* .toHaveBeenCalledTimes(0) for unrelated PR files
* run tests from root
* lerna run test --scope determined by 'name' field in each package.json
* run jsx in node with .mjs
* shut down mongoose afterEach test
* npm-run-all
* mongoose in tests
* comment out failing script for now
* use probot to get filenames
* remove jsdom and uncomment mongo updateOne test
* only connect mongoose once and better test for PRtest.delete
* fix: added cross-env on npm test and start
* fix: ignore PEM files
* fix: get probot to add stff to db
* fix: changed to aggregate to get info
* fix: renamed update-db to reflect probot
* dashboard route working
* fix: remove calls to update db for now
* fix: update db script for crontab
* Fix: Removal of seed-db (#64)
* fix: removed seeding from production
* fix: removed npm start from instructions
* Fix: Simplify log files for one off scripts (#65)
* fix: removed seeding from production
* fix: removed npm start from instructions
* fix: removed indices from log file
* fix: update lockfile (#66)
* docs: create a CoC
* fix: increased the number of files pulled per PR to Github max of 300 (#70)
* fix: made CRA work with local server running
* added run command just for CRA dev mode
* feat: started working on adding Pareto filters
* fix: added missing fonts for landing page
* fix: filter functionality complete
* fix: removed other as an option
* fix: make language all if no language for filetype
* feat: add some develop scripts
* responsive css (#73)
* Fix broke link of contribution guide (#75)
* [Fix] Update landing page links and make styling for landing page and Dashboard match (#79)
* fix: corrected links and changed header links
* fix: added fonts
* fix: changed layout of Pareto options
* fix: match main page styling
* fix: adjusted padding on menu
* fix: correct link href for Home
* fix: changed title of Dashbaord
* [Fix] Corrected client page title typo (#83)
* fix: corrected typo in page title
* fix: add info record if no collection exists
* docs: updated docs relating to local dev ports
* fix: add count increment when adding
* fix: be able to specify lanuage
* feat: new script to find merge conflicts
* fix: added recheck for unknown status
* fix: add pr number to no results message
* fix: changed from notifying to adding label
* chore: update lockfiles (#92)
* docs: update setup docs (#93)
* fix: update lerna config to install in ci mode
* docs: update readme
* fix/update-local-setup-doc (#98)
* fix/update-local-setup-doc
* fix/capitalization
* Update docs/README.md
Co-Authored-By: Randell Dawson <5313213+RandellDawson@users.noreply.github.com>
* Update docs/README.md
Co-Authored-By: Randell Dawson <5313213+RandellDawson@users.noreply.github.com>
* Update docs/README.md
Co-Authored-By: Randell Dawson <5313213+RandellDawson@users.noreply.github.com>
* Update docs/README.md
Co-Authored-By: Randell Dawson <5313213+RandellDawson@users.noreply.github.com>
* fix/update-homepage-instruction
* fix: remove all guide related code
* chore: remove eslint config from dashboard
* fix: temporarily pause linting of tools/dashboard
Remove this when we have time to configure it properly.
* fix: pause testing on tools/dashboard
Co-authored-by: Randell Dawson <rdawson@onepathtech.com>
Co-authored-by: RandellDawson <5313213+RandellDawson@users.noreply.github.com>
Co-authored-by: tbushman <tbushman@pu.bli.sh>
Co-authored-by: Honman Yau <honmanyau@users.noreply.github.com>
Co-authored-by: Valeriy <ValeraS@users.noreply.github.com>
Co-authored-by: Tracey Bushman <tracey.bushman@gmail.com>
Co-authored-by: Niraj Nandish <nirajnandish@icloud.com>
Co-authored-by: Musthaq Ahamad <musthu.gm@gmail.com>
Co-authored-by: Tom <20648924+moT01@users.noreply.github.com>
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
committed by
GitHub
parent
7473bcc40e
commit
21b27cb7c1
@ -4,3 +4,4 @@ client/static/**
|
|||||||
client/public/**
|
client/public/**
|
||||||
api-server/public/**
|
api-server/public/**
|
||||||
api-server/lib/**
|
api-server/lib/**
|
||||||
|
tools/dashboard/**
|
||||||
|
5
jest.config.js
Normal file
5
jest.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// TODO: remove this once /tools/dashboard's tests are configured correctly
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
testPathIgnorePatterns: ['/node_modules/', '/tools/dashboard']
|
||||||
|
};
|
110
tools/dashboard/.gitignore
vendored
Normal file
110
tools/dashboard/.gitignore
vendored
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#-----
|
||||||
|
# Node
|
||||||
|
#-----
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
#-----------------
|
||||||
|
# Create React App
|
||||||
|
#-----------------
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
build
|
||||||
|
|
||||||
|
# ------------
|
||||||
|
# Custom Files
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
work-logs
|
||||||
|
# work-logs
|
||||||
|
#
|
||||||
|
|
||||||
|
# ------------
|
||||||
|
# Probot Files
|
||||||
|
# ------------
|
||||||
|
|
||||||
|
*.pem
|
||||||
|
*.vscode
|
5
tools/dashboard/.prettierrc
Normal file
5
tools/dashboard/.prettierrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none"
|
||||||
|
}
|
1
tools/dashboard/CODE_OF_CONDUCT.md
Normal file
1
tools/dashboard/CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
> Our Code of Conduct is available here: <https://code-of-conduct.freecodecamp.org/>
|
29
tools/dashboard/LICENSE.md
Normal file
29
tools/dashboard/LICENSE.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2018, freeCodeCamp.org
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
5
tools/dashboard/README.md
Normal file
5
tools/dashboard/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|

|
||||||
|
|
||||||
|
# contribute
|
||||||
|
|
||||||
|
Tools to help maintain [freeCodeCamp.org](https://www.freecodecamp.org)'s Open Source Codebase on GitHub. Dashboard is available at https://contribute.freecodecamp.org/home
|
57
tools/dashboard/config/index.js
Normal file
57
tools/dashboard/config/index.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
const Joi = require('joi');
|
||||||
|
const path = require('path');
|
||||||
|
// require and configure dotenv, will load vars in .env in PROCESS.ENV
|
||||||
|
const envPath = path.resolve(__dirname, '../probot/.env');
|
||||||
|
require('dotenv').config({ path: envPath });
|
||||||
|
|
||||||
|
// define validation for all the env vars
|
||||||
|
const envVarsSchema = Joi.object({
|
||||||
|
NODE_ENV: Joi.string()
|
||||||
|
.allow(['development', 'production', 'test', 'provision'])
|
||||||
|
.default('development'),
|
||||||
|
PORT: Joi.number().default(3001),
|
||||||
|
MONGO_HOST: Joi.string()
|
||||||
|
.required()
|
||||||
|
.description('Mongo DB host url'),
|
||||||
|
MONGO_PORT: Joi.number().default(27017),
|
||||||
|
GITHUB_USERNAME: Joi.string().required(),
|
||||||
|
GITHUB_ACCESS_TOKEN: Joi.string().required(),
|
||||||
|
REPOSITORY_OWNER: Joi.string().required(),
|
||||||
|
REPOSITORY: Joi.string().required(),
|
||||||
|
PRODUCTION_RUN: Joi.boolean()
|
||||||
|
.default(false),
|
||||||
|
WEBHOOK_PROXY_URL: Joi.string().required(),
|
||||||
|
APP_ID: Joi.number().required(),
|
||||||
|
WEBHOOK_SECRET: Joi.string().required()
|
||||||
|
})
|
||||||
|
.unknown()
|
||||||
|
.required();
|
||||||
|
|
||||||
|
const { error, value: envVars } = Joi.validate(process.env, envVarsSchema);
|
||||||
|
if (error) {
|
||||||
|
throw new Error(`Config validation error: ${error.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
env: envVars.NODE_ENV,
|
||||||
|
mongo: {
|
||||||
|
host: envVars.MONGO_HOST,
|
||||||
|
port: envVars.MONGO_PORT
|
||||||
|
},
|
||||||
|
github: {
|
||||||
|
id: envVars.GITHUB_USERNAME,
|
||||||
|
secret: envVars.GITHUB_ACCESS_TOKEN,
|
||||||
|
owner: envVars.REPOSITORY_OWNER,
|
||||||
|
repo: envVars.REPOSITORY,
|
||||||
|
probot: {
|
||||||
|
webhookUrl: envVars.WEBHOOK_PROXY_URL,
|
||||||
|
webhookSecret: envVars.WEBHOOK_SECRET,
|
||||||
|
appID: envVars.APP_ID
|
||||||
|
}
|
||||||
|
},
|
||||||
|
oneoff: {
|
||||||
|
productionRun: envVars.PRODUCTION_RUN
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
8
tools/dashboard/docs/CONTRIBUTING.md
Normal file
8
tools/dashboard/docs/CONTRIBUTING.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
Todo
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Todo
|
||||||
|
|
81
tools/dashboard/docs/README.md
Normal file
81
tools/dashboard/docs/README.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
## Local Setup
|
||||||
|
- Follow the steps below to get this running on your local machine
|
||||||
|
|
||||||
|
### 1. Copy .env
|
||||||
|
- Copy the `sample.env` file into `.env`. The command below will do that in the terminal if your CWD(current working directory) is the `contribute` folder.
|
||||||
|
```bash
|
||||||
|
cp probot/sample.env probot/.env
|
||||||
|
```
|
||||||
|
- If you do not want to populate the database with the freeCodeCamp PR's you can [skip to step 6](#6-start-the-program)
|
||||||
|
|
||||||
|
### 2. Update .env
|
||||||
|
- Put your GitHub username in the `GITHUB_USERNAME` field of the `.env` file
|
||||||
|
|
||||||
|
### 3. Obtain GitHub Personal access token
|
||||||
|
- While on Github, click your profile icon > `Settings`
|
||||||
|
- Then click `Developer settings` > `Personal access tokens`
|
||||||
|
- Click `Generate new token`
|
||||||
|
- On the next page, give the token a name so you know what it’s for
|
||||||
|
- Scroll down to the bottom and click `Generate token`
|
||||||
|
- Copy the token string and paste it into the `GITHUB_ACCESS_TOKEN` field in the `.env` file. Note that you will not be able to see this token string on GitHub again
|
||||||
|
|
||||||
|
### 4. Create your own GitHub app
|
||||||
|
- While on GitHub, click your profile icon > `Settings`
|
||||||
|
- Then click `developer settings` > `New GitHub app`
|
||||||
|
- Fill in the `name` field with a name of your choice
|
||||||
|
- Fill in the `Homepage URL` field with the URL to the GitHub repository for your app. e.g. `https://github.com/username/contribute`
|
||||||
|
- In a new tab, go to https://smee.io/
|
||||||
|
- Click `Start a new channel` and copy the URL they give you. You can ignore the rest of the instructions on the `smee.io` page
|
||||||
|
- Paste the URL you copied into the `WEBHOOK_PROXY_URL` field of the `.env` file and the `Webhook URL` field of your new GitHub app
|
||||||
|
- Fill in the `Webhook secret` field on the GitHub app with a secret of your choice
|
||||||
|
- Put the same secret you just used in the `WEBHOOK_SECRET` field of the `.env` file
|
||||||
|
- Scroll to the bottom of your GitHub app page and click `Create GitHub App`
|
||||||
|
- On the next page, copy the `App ID` and paste it into the `APP_ID` field of the `.env` file
|
||||||
|
- Scroll to the bottom of this page and click `Generate Private Key`
|
||||||
|
- A popup menu will allow you to download the key file. Download it and put it in the `probot` folder
|
||||||
|
|
||||||
|
### 5. Run mongoDB
|
||||||
|
- Make sure a mongoDB instance is running by running the command below in the terminal.
|
||||||
|
```bash
|
||||||
|
mongod —dbpath=./database_folder
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Start the program
|
||||||
|
- In a new terminal window or tab, run these three commands to start the program. Wait for one command to finish running before starting the next.
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npm run develop
|
||||||
|
```
|
||||||
|
- If you skipped steps 2-5, you can ignore the rest
|
||||||
|
|
||||||
|
### 7. Update the Database
|
||||||
|
- Run the command below to populate your local database with PR’s from the freeCodeCamp repo. Note that you must have the program running and mongoDB running.
|
||||||
|
```bash
|
||||||
|
node probot/server/tools/update-db.js
|
||||||
|
```
|
||||||
|
- This will take a while. If it stops running partway through, it's probably a timeout error. Run the command again and it should finish
|
||||||
|
|
||||||
|
## Caveats & Notes
|
||||||
|
|
||||||
|
### Local Ports when developing locally
|
||||||
|
Using `npm run develop` will start both the api server and the Create React App(Dashboard) in development mode. The api server runs on port 3001 and the React app runs on port 3000.
|
||||||
|
|
||||||
|
### The one-off scripts will error out on actions performed by repository admins
|
||||||
|
For example, if an admin removes a label from a Pull Request, the script can not add that label back. This is usually because the script is acting on behalf of a non-admin user with write access.
|
||||||
|
This is usually the case with the use of access tokens for scripts.
|
||||||
|
|
||||||
|
### Setting up Cron jobs for Sweeper Scripts
|
||||||
|
For updating dashboard data we use PM2 like so:
|
||||||
|
```bash
|
||||||
|
pm2 start --no-autorestart probot/server/tools/update-db.js --cron "*/10 * * * *"
|
||||||
|
```
|
||||||
|
This will start the script in the "no restart" mode and re-run it every 10 minutes.
|
||||||
|
A useful link to calculate a Cron expression: <https://crontab.guru/every-10-minutes>
|
||||||
|
|
||||||
|
### Starting the express server (via probot)
|
||||||
|
```bash
|
||||||
|
pm2 start "npm start" --name "contribute-app"
|
||||||
|
```
|
||||||
|
**Note:** Start only one instance of this app, you can't have multiple probot apps running. Starting multiple instances will crash the app.
|
||||||
|
|
4
tools/dashboard/lerna.json
Normal file
4
tools/dashboard/lerna.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"packages": ["probot", "probot/client", "lib", "one-off-scripts"],
|
||||||
|
"version": "independent"
|
||||||
|
}
|
30
tools/dashboard/lib/constants.js
Normal file
30
tools/dashboard/lib/constants.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
const config = require('../config');
|
||||||
|
|
||||||
|
const owner = config.github.owner;
|
||||||
|
const repo = config.github.repo;
|
||||||
|
const fccBaseUrl = `https://github.com/${owner}/${repo}/`;
|
||||||
|
const prBaseUrl = `${fccBaseUrl}pull/`;
|
||||||
|
|
||||||
|
const octokitConfig = {
|
||||||
|
// 0 means no request timeout
|
||||||
|
timeout: 0,
|
||||||
|
headers: {
|
||||||
|
accept: 'application/vnd.github.v3+json',
|
||||||
|
// v1.2.3 will be current version
|
||||||
|
'user-agent': 'octokit/rest.js v1.2.3'
|
||||||
|
},
|
||||||
|
// custom GitHub Enterprise URL
|
||||||
|
baseUrl: 'https://api.github.com',
|
||||||
|
// Node only: advanced request options can be passed as http(s) agent
|
||||||
|
/* eslint-disable no-undefined */
|
||||||
|
agent: undefined
|
||||||
|
/* eslint-enable no-undefined */
|
||||||
|
};
|
||||||
|
|
||||||
|
const octokitAuth = {
|
||||||
|
type: 'basic',
|
||||||
|
username: config.github.id,
|
||||||
|
password: config.github.secret
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { owner, repo, prBaseUrl, octokitConfig, octokitAuth };
|
6
tools/dashboard/lib/defaults.js
Normal file
6
tools/dashboard/lib/defaults.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
labelPRConflict: {
|
||||||
|
name: 'PR: potential-conflict',
|
||||||
|
color: 'c2e0c6'
|
||||||
|
}
|
||||||
|
};
|
143
tools/dashboard/lib/get-prs/index.js
Normal file
143
tools/dashboard/lib/get-prs/index.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
const _cliProgress = require('cli-progress');
|
||||||
|
|
||||||
|
const { owner, repo, octokitConfig, octokitAuth } = require('../constants');
|
||||||
|
|
||||||
|
const octokit = require('@octokit/rest')(octokitConfig);
|
||||||
|
const { getRange, getCount } = require('./pr-stats');
|
||||||
|
|
||||||
|
octokit.authenticate(octokitAuth);
|
||||||
|
|
||||||
|
const methodProps = {
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
state: 'open',
|
||||||
|
base: 'master',
|
||||||
|
sort: 'created',
|
||||||
|
direction: 'asc',
|
||||||
|
page: 1,
|
||||||
|
per_page: 100
|
||||||
|
};
|
||||||
|
|
||||||
|
const prsPaginate = async(
|
||||||
|
method,
|
||||||
|
firstPR,
|
||||||
|
lastPR,
|
||||||
|
prPropsToGet,
|
||||||
|
progressBar
|
||||||
|
) => {
|
||||||
|
const prFilter = (prs, first, last, prPropsToGet) => {
|
||||||
|
const filtered = [];
|
||||||
|
for (let pr of prs) {
|
||||||
|
if (pr.number >= first && pr.number <= last) {
|
||||||
|
const propsObj = prPropsToGet.reduce((obj, prop) => {
|
||||||
|
obj[prop] = pr[prop];
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
filtered.push(propsObj);
|
||||||
|
}
|
||||||
|
if (pr.number >= last) {
|
||||||
|
done = true;
|
||||||
|
return filtered;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return filtered;
|
||||||
|
};
|
||||||
|
|
||||||
|
// will be true when lastPR is seen in paginated results
|
||||||
|
let done = false;
|
||||||
|
let response = await method(methodProps);
|
||||||
|
let { data } = response;
|
||||||
|
data = prFilter(data, firstPR, lastPR, prPropsToGet);
|
||||||
|
while (octokit.hasNextPage(response) && !done) {
|
||||||
|
response = await octokit.getNextPage(response);
|
||||||
|
let dataFiltered = prFilter(response.data, firstPR, lastPR, prPropsToGet);
|
||||||
|
data = data.concat(dataFiltered);
|
||||||
|
progressBar.increment(dataFiltered.length);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserInput = async(rangeType = '') => {
|
||||||
|
let data, firstPR, lastPR;
|
||||||
|
if (rangeType === 'all') {
|
||||||
|
data = await getRange().then(data => data);
|
||||||
|
firstPR = data[0];
|
||||||
|
lastPR = data[1];
|
||||||
|
} else {
|
||||||
|
let [type, start, end] = process.argv.slice(2);
|
||||||
|
data = await getRange().then(data => data);
|
||||||
|
firstPR = data[0];
|
||||||
|
lastPR = data[1];
|
||||||
|
if (type !== 'all' && type !== 'range') {
|
||||||
|
throw 'Please specify either all or range for 1st arg.';
|
||||||
|
}
|
||||||
|
if (type === 'range') {
|
||||||
|
start = parseInt(start, 10);
|
||||||
|
end = parseInt(end, 10);
|
||||||
|
if (!start || !end) {
|
||||||
|
throw 'Specify both a starting PR # (2nd arg) and ending PR # (3rd arg).';
|
||||||
|
}
|
||||||
|
if (start > end) {
|
||||||
|
throw 'Starting PR # must be less than or equal to end PR #.';
|
||||||
|
}
|
||||||
|
if (start < firstPR) {
|
||||||
|
throw `Starting PR # can not be less than first open PR # (${firstPR})`;
|
||||||
|
}
|
||||||
|
firstPR = start;
|
||||||
|
if (end > lastPR) {
|
||||||
|
throw `Ending PR # can not be greater than last open PR # (${lastPR})`;
|
||||||
|
}
|
||||||
|
lastPR = end;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const totalPRs = await getCount().then(data => data);
|
||||||
|
return { totalPRs, firstPR, lastPR };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPRs = async (totalPRs, firstPR, lastPR, prPropsToGet) => {
|
||||||
|
let progressText = `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] `;
|
||||||
|
progressText += '{percentage}% | Elapsed Time: {duration_formatted} ';
|
||||||
|
progressText += '| ETA: {eta_formatted}';
|
||||||
|
const getPRsBar = new _cliProgress.Bar(
|
||||||
|
{
|
||||||
|
format: progressText,
|
||||||
|
etaBuffer: 50
|
||||||
|
},
|
||||||
|
_cliProgress.Presets.shades_classic
|
||||||
|
);
|
||||||
|
getPRsBar.start(totalPRs, 0);
|
||||||
|
let openPRs = await prsPaginate(
|
||||||
|
octokit.pullRequests.list,
|
||||||
|
firstPR,
|
||||||
|
lastPR,
|
||||||
|
prPropsToGet,
|
||||||
|
getPRsBar
|
||||||
|
);
|
||||||
|
getPRsBar.update(totalPRs);
|
||||||
|
getPRsBar.stop();
|
||||||
|
console.log(`# of PRs retrieved: ${openPRs.length}`);
|
||||||
|
return { firstPR, lastPR, openPRs };
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const filesPaginate = async number => {
|
||||||
|
let response = await octokit.pullRequests.listFiles({
|
||||||
|
number, ...methodProps
|
||||||
|
});
|
||||||
|
|
||||||
|
let { data } = response;
|
||||||
|
while (octokit.hasNextPage(response)) {
|
||||||
|
response = await octokit.getNextPage(response);
|
||||||
|
let { data: moreData } = response;
|
||||||
|
data = data.concat(moreData);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFiles = async number => await filesPaginate(number);
|
||||||
|
|
||||||
|
const getFilenames = async number => (
|
||||||
|
await getFiles(number)
|
||||||
|
).map(({ filename }) => filename);
|
||||||
|
|
||||||
|
module.exports = { getPRs, getUserInput, getFiles, getFilenames };
|
41
tools/dashboard/lib/get-prs/pr-stats.js
Normal file
41
tools/dashboard/lib/get-prs/pr-stats.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const { owner, repo, octokitConfig, octokitAuth } = require('../constants');
|
||||||
|
|
||||||
|
const octokit = require('@octokit/rest')(octokitConfig);
|
||||||
|
|
||||||
|
octokit.authenticate(octokitAuth);
|
||||||
|
|
||||||
|
let methodProps = {
|
||||||
|
owner, repo, state: 'open',
|
||||||
|
base: 'master', sort: 'created',
|
||||||
|
page: 1, per_page: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCount = async() => {
|
||||||
|
const { data: { total_count: count } } = await octokit.search.issues({
|
||||||
|
q: `repo:${owner}/${repo}+is:open+type:pr+base:master`,
|
||||||
|
sort: 'created', order: 'asc', page: 1, per_page: 1
|
||||||
|
})
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
return count;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFirst = async() => {
|
||||||
|
const response = await octokit.pullRequests.list({
|
||||||
|
direction: 'asc', ...methodProps
|
||||||
|
});
|
||||||
|
return response.data[0].number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRange = async() => {
|
||||||
|
let response = await octokit.pullRequests.list({
|
||||||
|
direction: 'asc', ...methodProps
|
||||||
|
});
|
||||||
|
const firstPR = response.data[0].number;
|
||||||
|
response = await octokit.pullRequests.list({
|
||||||
|
direction: 'desc', ...methodProps }
|
||||||
|
);
|
||||||
|
const lastPR = response.data[0].number;
|
||||||
|
return [ firstPR, lastPR ];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { getCount, getFirst, getRange };
|
1105
tools/dashboard/lib/package-lock.json
generated
Normal file
1105
tools/dashboard/lib/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
tools/dashboard/lib/package.json
Normal file
22
tools/dashboard/lib/package.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "freecodecampcontributelib",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/rest": "^15.18.0",
|
||||||
|
"cli-progress": "^2.1.0",
|
||||||
|
"date-fns": "^1.29.0",
|
||||||
|
"dedent": "^0.7.0",
|
||||||
|
"dotenv": "^6.1.0",
|
||||||
|
"form-data": "^2.3.3",
|
||||||
|
"gray-matter": "^4.0.1",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
|
"path": "^0.12.7",
|
||||||
|
"readdirp-walk": "^1.6.0",
|
||||||
|
"travis-ci": "^2.2.0",
|
||||||
|
"util": "^0.11.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
}
|
26
tools/dashboard/lib/pr-tasks/add-comment.js
Normal file
26
tools/dashboard/lib/pr-tasks/add-comment.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const { owner, repo, octokitConfig, octokitAuth } = require('../constants');
|
||||||
|
|
||||||
|
const octokit = require('@octokit/rest')(octokitConfig);
|
||||||
|
|
||||||
|
octokit.authenticate(octokitAuth);
|
||||||
|
|
||||||
|
const addComment = async (number, comment) => {
|
||||||
|
const result = await octokit.issues
|
||||||
|
.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
number,
|
||||||
|
body: comment
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(`PR #${number} had an error when trying to add a comment\n`);
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
console.log(`PR #${number} successfully added a comment\n`);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { addComment };
|
23
tools/dashboard/lib/pr-tasks/add-labels.js
Normal file
23
tools/dashboard/lib/pr-tasks/add-labels.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const { owner, repo, octokitConfig, octokitAuth } = require('../constants');
|
||||||
|
const octokit = require('@octokit/rest')(octokitConfig);
|
||||||
|
|
||||||
|
octokit.authenticate(octokitAuth);
|
||||||
|
|
||||||
|
const addLabels = (number, labels, log) => {
|
||||||
|
octokit.issues
|
||||||
|
.addLabels({ owner, repo, number, labels })
|
||||||
|
.then(() => {
|
||||||
|
console.log(`PR #${number} added ${JSON.stringify(labels)}\n`);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(
|
||||||
|
`PR #${number} had an error when trying to labels: ${JSON.stringify(
|
||||||
|
labels
|
||||||
|
)}\n`
|
||||||
|
);
|
||||||
|
console.log(err);
|
||||||
|
log.finish();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { addLabels };
|
55
tools/dashboard/lib/pr-tasks/close-open.js
Normal file
55
tools/dashboard/lib/pr-tasks/close-open.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const { addComment } = require('./add-comment');
|
||||||
|
const { rateLimiter } = require('../utils');
|
||||||
|
const { owner, repo, octokitConfig, octokitAuth } = require('../constants');
|
||||||
|
|
||||||
|
const octokit = require('@octokit/rest')(octokitConfig);
|
||||||
|
|
||||||
|
octokit.authenticate(octokitAuth);
|
||||||
|
|
||||||
|
/* closes and reopens an open PR with applicable comment */
|
||||||
|
const closeOpen = async number => {
|
||||||
|
await octokit.pullRequests
|
||||||
|
.update({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
number,
|
||||||
|
state: 'closed',
|
||||||
|
base: 'master'
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
await rateLimiter(5000);
|
||||||
|
return octokit.pullRequests.update({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
number,
|
||||||
|
state: 'open',
|
||||||
|
base: 'master'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
await rateLimiter(1000);
|
||||||
|
const msg = 'Closed/Reopened to resolve a specific Travis build failure.';
|
||||||
|
await addComment(number, msg);
|
||||||
|
})
|
||||||
|
.catch(async err => {
|
||||||
|
// Octokit stores message as a stringified object
|
||||||
|
const { errorMg } = JSON.parse(err.message);
|
||||||
|
if (
|
||||||
|
errorMg ===
|
||||||
|
'state cannot be changed. The repository that submitted this pull request has been deleted.'
|
||||||
|
) {
|
||||||
|
await rateLimiter(1000);
|
||||||
|
await addComment(
|
||||||
|
number,
|
||||||
|
"This PR was closed because user's repo was deleted."
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
`PR #${number} was closed because user's repo was deleted.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { closeOpen };
|
6
tools/dashboard/lib/pr-tasks/index.js
Normal file
6
tools/dashboard/lib/pr-tasks/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const { addComment } = require('./add-comment');
|
||||||
|
const { addLabels } = require('./add-labels');
|
||||||
|
const { closeOpen } = require('./close-open');
|
||||||
|
const { labeler } = require('./labeler');
|
||||||
|
|
||||||
|
module.exports = { addComment, addLabels, closeOpen, labeler };
|
48
tools/dashboard/lib/pr-tasks/labeler.js
Normal file
48
tools/dashboard/lib/pr-tasks/labeler.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
const config = require('../../config');
|
||||||
|
const { validLabels } = require('../validation');
|
||||||
|
const { addLabels } = require('./add-labels');
|
||||||
|
const { rateLimiter } = require('../utils');
|
||||||
|
|
||||||
|
const labeler = async(
|
||||||
|
number,
|
||||||
|
prFiles,
|
||||||
|
currentLabels
|
||||||
|
) => {
|
||||||
|
// holds potential labels to add based on file path
|
||||||
|
const labelsToAdd = {};
|
||||||
|
const existingLabels = currentLabels.map(({ name }) => name);
|
||||||
|
prFiles.forEach(({ filename }) => {
|
||||||
|
/* remove '/challenges' from filename so
|
||||||
|
language variable hold the language */
|
||||||
|
const filenameReplacement = filename.replace(
|
||||||
|
/^curriculum\/challenges\//,
|
||||||
|
'curriculum/'
|
||||||
|
);
|
||||||
|
const regex = /^(docs|curriculum)(?:\/)(english|arabic|chinese|portuguese|russian|spanish)?\/?/;
|
||||||
|
// need an array to pass to labelsAdder
|
||||||
|
const [_, articleType, language] = filenameReplacement.match(regex) || [];
|
||||||
|
if (articleType && validLabels[articleType]) {
|
||||||
|
labelsToAdd[validLabels[articleType]] = 1;
|
||||||
|
}
|
||||||
|
if (language && validLabels[language]) {
|
||||||
|
labelsToAdd[validLabels[language]] = 1;
|
||||||
|
}
|
||||||
|
if (articleType === 'curriculum') {
|
||||||
|
labelsToAdd['status: need to test locally'] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// only adds needed labels which are NOT currently on the PR
|
||||||
|
const newLabels = Object.keys(labelsToAdd).filter(label => {
|
||||||
|
return !existingLabels.includes(label);
|
||||||
|
});
|
||||||
|
if (newLabels.length) {
|
||||||
|
if (config.oneoff.productionRun) {
|
||||||
|
addLabels(number, newLabels);
|
||||||
|
await rateLimiter(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newLabels;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { labeler };
|
13
tools/dashboard/lib/utils/index.js
Normal file
13
tools/dashboard/lib/utils/index.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
const { rateLimiter } = require('./rate-limiter');
|
||||||
|
const { savePrData } = require('./save-pr-data');
|
||||||
|
const { saveToFile } = require('./save-to-file');
|
||||||
|
const { openJSONFile } = require('./open-json-file');
|
||||||
|
const { ProcessingLog } = require('./processing-log');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
rateLimiter,
|
||||||
|
savePrData,
|
||||||
|
saveToFile,
|
||||||
|
openJSONFile,
|
||||||
|
ProcessingLog
|
||||||
|
};
|
8
tools/dashboard/lib/utils/open-json-file.js
Normal file
8
tools/dashboard/lib/utils/open-json-file.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const openJSONFile = fileName => {
|
||||||
|
const data = JSON.parse(fs.readFileSync(fileName, 'utf8'));
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { openJSONFile };
|
90
tools/dashboard/lib/utils/processing-log.js
Normal file
90
tools/dashboard/lib/utils/processing-log.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
const config = require('../../config');
|
||||||
|
const formatDate = require('date-fns/format');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const { saveToFile } = require('./save-to-file');
|
||||||
|
|
||||||
|
class ProcessingLog {
|
||||||
|
constructor(script) {
|
||||||
|
this._script = script;
|
||||||
|
this._startTime = null;
|
||||||
|
this._finishTime = null;
|
||||||
|
this._elapsedTime = null;
|
||||||
|
this._prs = [];
|
||||||
|
this._prCount = null;
|
||||||
|
this._logfile = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
`../../work-logs/data-for_${this.getRunType()}_${this._script}.json`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRunType() {
|
||||||
|
return config.oneoff.productionRun ? 'production' : 'test';
|
||||||
|
}
|
||||||
|
|
||||||
|
export() {
|
||||||
|
const log = {
|
||||||
|
startTime: this._startTime,
|
||||||
|
finishTime: this._finishTime,
|
||||||
|
elapsedTime: this._elapsedTime,
|
||||||
|
prCount: this._prs.length,
|
||||||
|
firstPR: this._firstPR,
|
||||||
|
lastPR: this._lastPR,
|
||||||
|
prs: this._prs
|
||||||
|
};
|
||||||
|
saveToFile(this._logfile, JSON.stringify(log, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
add(prNum, props) {
|
||||||
|
this._prs.push(props);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPrRange() {
|
||||||
|
if (this._prs.length) {
|
||||||
|
const first = this._prs[0].number;
|
||||||
|
const last = this._prs[this._prs.length - 1].number;
|
||||||
|
return [first, last];
|
||||||
|
}
|
||||||
|
console.log('Current log file does not contain any PRs');
|
||||||
|
return [null, null];
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this._startTime = new Date();
|
||||||
|
this.export();
|
||||||
|
}
|
||||||
|
|
||||||
|
finish(logFileName = '') {
|
||||||
|
this._finishTime = new Date();
|
||||||
|
const minutesElapsed = (this._finishTime - this._startTime) / 1000 / 60;
|
||||||
|
this._elapsedTime = minutesElapsed.toFixed(2) + ' mins';
|
||||||
|
let [first, last] = this.getPrRange();
|
||||||
|
this._firstPR = first;
|
||||||
|
this._lastPR = last;
|
||||||
|
this.export();
|
||||||
|
this.changeFilename(logFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeFilename(logFileName) {
|
||||||
|
const now = formatDate(new Date(), 'YYYY-MM-DDTHHmmss');
|
||||||
|
const prRange = `${this._firstPR}-${this._lastPR}`;
|
||||||
|
let finalFilename = `${this.getRunType()}_${
|
||||||
|
this._script
|
||||||
|
}_${prRange}_${now}.json`;
|
||||||
|
let newFilename = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
`../../work-logs/${finalFilename}`
|
||||||
|
);
|
||||||
|
if (logFileName) {
|
||||||
|
newFilename = logFileName;
|
||||||
|
}
|
||||||
|
fs.renameSync(this._logfile, newFilename);
|
||||||
|
if (!fs.existsSync(newFilename)) {
|
||||||
|
throw 'File rename unsuccessful.';
|
||||||
|
}
|
||||||
|
this._logfile = newFilename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { ProcessingLog };
|
7
tools/dashboard/lib/utils/rate-limiter.js
Normal file
7
tools/dashboard/lib/utils/rate-limiter.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const rateLimiter = (delay = 1500) => {
|
||||||
|
/* The 1500 delay will guarntee the script will not exceed Github request
|
||||||
|
limit of 1500 per hour. Only increase if you have a higher rate limit */
|
||||||
|
return new Promise(resolve => setTimeout(() => resolve(true), delay));
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { rateLimiter };
|
18
tools/dashboard/lib/utils/save-pr-data.js
Normal file
18
tools/dashboard/lib/utils/save-pr-data.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const formatDate = require('date-fns/format');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const { saveToFile } = require('./save-to-file');
|
||||||
|
|
||||||
|
const savePrData = (openPRs, firstPR, lastPR) => {
|
||||||
|
const now = formatDate(new Date(), 'YYYY-MM-DDTHHmmss');
|
||||||
|
const filename = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
`../../work-logs/data-for-openprs_${firstPR}-${lastPR}_${now}.json`
|
||||||
|
);
|
||||||
|
console.log(`# of PRs Retrieved: ${openPRs.length}`);
|
||||||
|
console.log(`PR Range: ${firstPR} - ${lastPR}`);
|
||||||
|
saveToFile(filename, JSON.stringify(openPRs));
|
||||||
|
console.log(`Data saved in file: ${filename}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { savePrData };
|
12
tools/dashboard/lib/utils/save-to-file.js
Normal file
12
tools/dashboard/lib/utils/save-to-file.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const saveToFile = (fileName, data) => {
|
||||||
|
fs.writeFileSync(fileName, data, err => {
|
||||||
|
if (err) {
|
||||||
|
return console.log(err);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { saveToFile };
|
3
tools/dashboard/lib/validation/index.js
Normal file
3
tools/dashboard/lib/validation/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
const { validLabels } = require('./valid-labels');
|
||||||
|
|
||||||
|
module.exports = { validLabels };
|
12
tools/dashboard/lib/validation/valid-labels.js
Normal file
12
tools/dashboard/lib/validation/valid-labels.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const validLabels = {
|
||||||
|
arabic: 'language: Arabic',
|
||||||
|
chinese: 'language: Chinese',
|
||||||
|
english: 'language: English',
|
||||||
|
portuguese: 'language: Portuguese',
|
||||||
|
russian: 'language: Russian',
|
||||||
|
spanish: 'language: Spanish',
|
||||||
|
curriculum: 'scope: curriculum',
|
||||||
|
docs: 'scope: docs',
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { validLabels };
|
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
This is a one-off script to run on all open PRs to add
|
||||||
|
a comment and "status: needs update" label to any PR with guide articles which
|
||||||
|
have frontmatter issues.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const config = require('../config');
|
||||||
|
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
const { getPRs, getUserInput, getFiles } = require('../lib/get-prs');
|
||||||
|
const { addLabels, addComment } = require('../lib/pr-tasks');
|
||||||
|
const { rateLimiter, ProcessingLog } = require('../lib/utils');
|
||||||
|
const {
|
||||||
|
frontmatterCheck
|
||||||
|
} = require('../lib/validation/guide-folder-checks/frontmatter-check');
|
||||||
|
const {
|
||||||
|
createErrorMsg
|
||||||
|
} = require('../lib/validation/guide-folder-checks/create-error-msg');
|
||||||
|
|
||||||
|
const allowedLangDirNames = [
|
||||||
|
'arabic',
|
||||||
|
'chinese',
|
||||||
|
'english',
|
||||||
|
'portuguese',
|
||||||
|
'russian',
|
||||||
|
'spanish'
|
||||||
|
];
|
||||||
|
|
||||||
|
const log = new ProcessingLog('all-frontmatter-checks');
|
||||||
|
|
||||||
|
const labeler = async(
|
||||||
|
number,
|
||||||
|
prFiles,
|
||||||
|
currentLabels,
|
||||||
|
guideFolderErrorsComment
|
||||||
|
) => {
|
||||||
|
// holds potential labels to add based on file path
|
||||||
|
const labelsToAdd = {};
|
||||||
|
if (guideFolderErrorsComment) {
|
||||||
|
labelsToAdd['status: needs update'] = 1;
|
||||||
|
}
|
||||||
|
const existingLabels = currentLabels.map(({ name }) => name);
|
||||||
|
|
||||||
|
/* only adds needed labels which are NOT currently on the PR. */
|
||||||
|
const newLabels = Object.keys(labelsToAdd).filter(label => {
|
||||||
|
return !existingLabels.includes(label);
|
||||||
|
});
|
||||||
|
if (newLabels.length) {
|
||||||
|
if (config.oneoff.productionRun) {
|
||||||
|
addLabels(number, newLabels);
|
||||||
|
await rateLimiter();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newLabels;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkPath = (fullPath, fileContent) => {
|
||||||
|
let errorMsgs = [];
|
||||||
|
const remaining = fullPath.split('/');
|
||||||
|
const isTranslation =
|
||||||
|
allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english';
|
||||||
|
const frontMatterErrMsgs = frontmatterCheck(
|
||||||
|
fullPath,
|
||||||
|
isTranslation,
|
||||||
|
fileContent
|
||||||
|
);
|
||||||
|
return errorMsgs.concat(frontMatterErrMsgs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const guideFolderChecks = async(number, prFiles, user) => {
|
||||||
|
let prErrors = [];
|
||||||
|
for (let { filename: fullPath, raw_url: fileUrl } of prFiles) {
|
||||||
|
let newErrors;
|
||||||
|
if ((/^guide\//).test(fullPath)) {
|
||||||
|
const response = await fetch(fileUrl);
|
||||||
|
const fileContent = await response.text();
|
||||||
|
newErrors = checkPath(fullPath, fileContent);
|
||||||
|
}
|
||||||
|
if (newErrors) {
|
||||||
|
prErrors = prErrors.concat(newErrors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prErrors.length) {
|
||||||
|
const comment = createErrorMsg(prErrors, user);
|
||||||
|
if (config.oneoff.productionRun) {
|
||||||
|
await addComment(number, comment);
|
||||||
|
await rateLimiter();
|
||||||
|
}
|
||||||
|
return comment;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(async() => {
|
||||||
|
const { totalPRs, firstPR, lastPR } = await getUserInput();
|
||||||
|
const prPropsToGet = ['number', 'labels', 'user'];
|
||||||
|
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||||
|
|
||||||
|
log.start();
|
||||||
|
console.log('Starting frontmatter checks process...');
|
||||||
|
let count = 0;
|
||||||
|
for (let i = 0; i < openPRs.length; i++) {
|
||||||
|
if (openPRs.length) {
|
||||||
|
let {
|
||||||
|
number,
|
||||||
|
labels: currentLabels,
|
||||||
|
user: { login: username }
|
||||||
|
} = openPRs[count];
|
||||||
|
|
||||||
|
const prFiles = await getFiles(number);
|
||||||
|
if (count > 4000) {
|
||||||
|
await rateLimiter(2350);
|
||||||
|
}
|
||||||
|
const guideFolderErrorsComment = await guideFolderChecks(
|
||||||
|
number,
|
||||||
|
prFiles,
|
||||||
|
username
|
||||||
|
);
|
||||||
|
const commentLogVal = guideFolderErrorsComment
|
||||||
|
? guideFolderErrorsComment
|
||||||
|
: 'none';
|
||||||
|
|
||||||
|
const labelsAdded = await labeler(
|
||||||
|
number,
|
||||||
|
prFiles,
|
||||||
|
currentLabels,
|
||||||
|
guideFolderErrorsComment
|
||||||
|
);
|
||||||
|
const labelLogVal = labelsAdded.length ? labelsAdded : 'none added';
|
||||||
|
|
||||||
|
log.add(number, { number, comment: commentLogVal, labels: labelLogVal });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.then(() => {
|
||||||
|
log.finish();
|
||||||
|
console.log('Successfully completed frontmatter checks');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.finish();
|
||||||
|
console.log(err);
|
||||||
|
});
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
This script was created to iterate over all open PRs to label.
|
||||||
|
|
||||||
|
To run the script for a specific range,
|
||||||
|
run `node sweeper.js range startingPrNumber endingPrNumber`
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { getPRs, getUserInput, getFiles } = require('../lib/get-prs');
|
||||||
|
const { ProcessingLog, rateLimiter } = require('../lib/utils');
|
||||||
|
const { labeler } = require('../lib/pr-tasks');
|
||||||
|
|
||||||
|
const log = new ProcessingLog('add-language-labels');
|
||||||
|
|
||||||
|
log.start();
|
||||||
|
console.log('Curriculum File language labeler started...');
|
||||||
|
(async() => {
|
||||||
|
const { totalPRs, firstPR, lastPR } = await getUserInput();
|
||||||
|
const prPropsToGet = ['number', 'labels', 'user'];
|
||||||
|
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||||
|
let count = 0;
|
||||||
|
if (openPRs.length) {
|
||||||
|
console.log('Processing PRs...');
|
||||||
|
for (let i = 0; i < openPRs.length; i++) {
|
||||||
|
let { number, labels: currentLabels } = openPRs[i];
|
||||||
|
|
||||||
|
const prFiles = await getFiles(number);
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const labelsAdded = await labeler(
|
||||||
|
number,
|
||||||
|
prFiles,
|
||||||
|
currentLabels
|
||||||
|
);
|
||||||
|
const labelLogVal = labelsAdded.length ? labelsAdded : 'none added';
|
||||||
|
|
||||||
|
log.add(number, { number, labels: labelLogVal });
|
||||||
|
if (count > 4000) {
|
||||||
|
await rateLimiter(2350);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.then(() => {
|
||||||
|
log.finish();
|
||||||
|
console.log('Labeler complete');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.finish();
|
||||||
|
console.log(err);
|
||||||
|
});
|
56
tools/dashboard/one-off-scripts/add-test-locally-label.js
Normal file
56
tools/dashboard/one-off-scripts/add-test-locally-label.js
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
This is a one-off script to run on all open PRs to add the
|
||||||
|
"status: need to test locally" label to any PR with an existing
|
||||||
|
"scope: curriculum" label on it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const config = require('../config');
|
||||||
|
|
||||||
|
const { getPRs, getUserInput } = require('../lib/get-prs');
|
||||||
|
const { addLabels } = require('../lib/pr-tasks');
|
||||||
|
const { rateLimiter, ProcessingLog } = require('../lib/utils');
|
||||||
|
|
||||||
|
const log = new ProcessingLog('all-locally-tested-labels');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const { totalPRs, firstPR, lastPR } = await getUserInput();
|
||||||
|
const prPropsToGet = ['number', 'labels'];
|
||||||
|
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||||
|
|
||||||
|
if (openPRs.length) {
|
||||||
|
log.start();
|
||||||
|
console.log('Starting labeling process...');
|
||||||
|
for (let count = 0; count < openPRs.length; count++) {
|
||||||
|
let { number, labels } = openPRs[count];
|
||||||
|
// holds potential labels to add based on file path
|
||||||
|
const labelsToAdd = {};
|
||||||
|
const existingLabels = labels.map(({ name }) => name);
|
||||||
|
if (existingLabels.includes('scope: curriculum')) {
|
||||||
|
labelsToAdd['status: need to test locally'] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// only adds needed labels which are NOT currently on the PR
|
||||||
|
const newLabels = Object.keys(labelsToAdd).filter(label => {
|
||||||
|
return !existingLabels.includes(label);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (newLabels.length) {
|
||||||
|
log.add(number, { number, labels: newLabels });
|
||||||
|
if (config.oneoff.productionRun) {
|
||||||
|
addLabels(number, newLabels, log);
|
||||||
|
await rateLimiter();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.add(number, { number, labels: 'none added' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.then(() => {
|
||||||
|
log.finish();
|
||||||
|
console.log('Successfully completed labeling');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.finish();
|
||||||
|
console.log(err);
|
||||||
|
});
|
@ -0,0 +1,47 @@
|
|||||||
|
const config = require('../config');
|
||||||
|
const { closeOpen } = require('../lib/pr-tasks');
|
||||||
|
const { openJSONFile, ProcessingLog, rateLimiter } = require('../lib/utils');
|
||||||
|
|
||||||
|
const log = new ProcessingLog('prs-closed-reopened');
|
||||||
|
|
||||||
|
log.start();
|
||||||
|
const getUserInput = async () => {
|
||||||
|
let filename = process.argv[2];
|
||||||
|
|
||||||
|
if (!filename) {
|
||||||
|
throw 'Specify a file with PRs which needed to be closed and reopened.';
|
||||||
|
}
|
||||||
|
|
||||||
|
let fileObj = openJSONFile(filename);
|
||||||
|
let { prs } = fileObj;
|
||||||
|
if (!prs.length) {
|
||||||
|
throw 'Either no PRs found in file or there or an error occurred.';
|
||||||
|
}
|
||||||
|
return { prs };
|
||||||
|
};
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const { prs } = await getUserInput();
|
||||||
|
return prs;
|
||||||
|
})()
|
||||||
|
.then(async prs => {
|
||||||
|
for (let { number, errorDesc } of prs) {
|
||||||
|
if (errorDesc !== 'unknown error') {
|
||||||
|
log.add(number, { number, closedOpened: true, errorDesc });
|
||||||
|
if (config.oneoff.productionRun) {
|
||||||
|
await closeOpen(number);
|
||||||
|
await rateLimiter(90000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.add(number, { number, closedOpened: false, errorDesc });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
log.finish();
|
||||||
|
console.log('closing/reopening of PRs complete');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.finish();
|
||||||
|
console.log(err);
|
||||||
|
});
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
This is a one-off script that was was used to summarize the results of a
|
||||||
|
test_sweeper json log file after sweeper.js was run on a particular set of data.
|
||||||
|
It generates a text file referencing only PRs with any comments/labels
|
||||||
|
which would have beeen added (test) based on data stored in the
|
||||||
|
specific JSON log file. You must run sweeper with environement variable
|
||||||
|
PRODUCTION_RUN set to false, to get the test version. Technically, you
|
||||||
|
could also run this on a production_sweeper json log file, if you wanted to see
|
||||||
|
if the sweeper commented or labeled any PRs during its run.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { saveToFile, openJSONFile } = require('../lib/utils');
|
||||||
|
const path = require('path');
|
||||||
|
const dedent = require('dedent');
|
||||||
|
|
||||||
|
const specificLogFile = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'../work-logs/test_add-language-labels_26001-29000_2019-01-14T215420.json'
|
||||||
|
);
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
let fileObj = openJSONFile(specificLogFile);
|
||||||
|
let { prs } = fileObj;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
let prsWithComments = prs.reduce((text, { number, comment, labels }) => {
|
||||||
|
if ((comment && comment !== 'none') || labels !== 'none added') {
|
||||||
|
text += dedent`
|
||||||
|
|
||||||
|
PR #${number}
|
||||||
|
Comment: ${comment}
|
||||||
|
|
||||||
|
Labels: ${JSON.stringify(labels)}
|
||||||
|
|
||||||
|
*************************\n
|
||||||
|
|
||||||
|
`;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}, '');
|
||||||
|
|
||||||
|
prsWithComments = dedent`
|
||||||
|
# of PRs with comments or labels added: ${count}
|
||||||
|
|
||||||
|
*************************
|
||||||
|
${prsWithComments}
|
||||||
|
`;
|
||||||
|
|
||||||
|
saveToFile(
|
||||||
|
path.resolve(__dirname, '../work-logs/guideErrorComments.txt'),
|
||||||
|
prsWithComments
|
||||||
|
);
|
||||||
|
console.log('guideErrorComments.txt created');
|
||||||
|
})();
|
90
tools/dashboard/one-off-scripts/find-failures.js
Normal file
90
tools/dashboard/one-off-scripts/find-failures.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
This is a one-off script to find all open PRs which have one of the
|
||||||
|
console.error descriptions in the failuresToFind.json file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
const { owner, repo, octokitConfig, octokitAuth } = require('../lib/constants');
|
||||||
|
|
||||||
|
const octokit = require('@octokit/rest')(octokitConfig);
|
||||||
|
const { getPRs, getUserInput } = require('../lib/get-prs');
|
||||||
|
const { savePrData, ProcessingLog } = require('../lib/utils');
|
||||||
|
|
||||||
|
octokit.authenticate(octokitAuth);
|
||||||
|
|
||||||
|
const log = new ProcessingLog('find-failures-script');
|
||||||
|
|
||||||
|
const errorsToFind = [
|
||||||
|
{
|
||||||
|
error: '',
|
||||||
|
regex: ''
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const { totalPRs, firstPR, lastPR } = await getUserInput();
|
||||||
|
const prPropsToGet = ['number', 'labels', 'head'];
|
||||||
|
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||||
|
|
||||||
|
if (openPRs.length) {
|
||||||
|
savePrData(openPRs, firstPR, lastPR);
|
||||||
|
log.start();
|
||||||
|
console.log('Starting error finding process...');
|
||||||
|
for (let count = 0; count < openPRs.length; count++) {
|
||||||
|
let {
|
||||||
|
number,
|
||||||
|
labels,
|
||||||
|
head: { sha: ref }
|
||||||
|
} = openPRs[count];
|
||||||
|
const existingLabels = labels.map(({ name }) => name);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!existingLabels.includes('status: merge conflict') &&
|
||||||
|
!existingLabels.includes('status: needs update') &&
|
||||||
|
!existingLabels.includes('status: discussing')
|
||||||
|
) {
|
||||||
|
const { data: statuses } = await octokit.repos.listStatusesForRef({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
ref
|
||||||
|
});
|
||||||
|
if (statuses.length) {
|
||||||
|
// first element contain most recent status
|
||||||
|
const { state, target_url: targetUrl } = statuses[0];
|
||||||
|
const hasProblem = state === 'failure' || state === 'error';
|
||||||
|
if (hasProblem) {
|
||||||
|
let buildNum = Number(targetUrl.match(/\/builds\/(\d+)\?/i)[1]);
|
||||||
|
/*
|
||||||
|
const logNumber = 'need to use Travis api to
|
||||||
|
access the full log for the buildNum above'
|
||||||
|
*/
|
||||||
|
const logNumber = ++buildNum;
|
||||||
|
const travisBaseUrl = 'https://api.travis-ci.org/v3/job/';
|
||||||
|
const travisLogUrl = `${travisBaseUrl + logNumber}/log.txt`;
|
||||||
|
const response = await fetch(travisLogUrl);
|
||||||
|
const logText = await response.text();
|
||||||
|
let error;
|
||||||
|
for (let { error: errorDesc, regex } of errorsToFind) {
|
||||||
|
regex = RegExp(regex);
|
||||||
|
if (regex.test(logText)) {
|
||||||
|
error = errorDesc;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const errorDesc = error ? error : 'unknown error';
|
||||||
|
log.add(number, { number, errorDesc, buildLog: travisLogUrl });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.then(() => {
|
||||||
|
log.finish();
|
||||||
|
console.log('Successfully finished finding all specified errors.');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.finish();
|
||||||
|
console.log(err);
|
||||||
|
});
|
81
tools/dashboard/one-off-scripts/get-unknown-repo-prs.js
Normal file
81
tools/dashboard/one-off-scripts/get-unknown-repo-prs.js
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
This script was created to find all open PRs with unknown repos which
|
||||||
|
potentially have merge conflicts.
|
||||||
|
|
||||||
|
To run the script for a specific language, call the script with the language
|
||||||
|
name as the first argument.
|
||||||
|
|
||||||
|
Note: If any PR displayed in the console shows "unknown", you will need to rerun
|
||||||
|
the script again.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { owner, repo, octokitConfig, octokitAuth } = require('../lib/constants');
|
||||||
|
const octokit = require('@octokit/rest')(octokitConfig);
|
||||||
|
|
||||||
|
const { getPRs, getUserInput } = require('../lib/get-prs');
|
||||||
|
const { ProcessingLog, rateLimiter } = require('../lib/utils');
|
||||||
|
const { validLabels } = require('../lib/validation/valid-labels');
|
||||||
|
|
||||||
|
octokit.authenticate(octokitAuth);
|
||||||
|
|
||||||
|
let languageLabel;
|
||||||
|
let [languageArg] = process.argv.slice(2);
|
||||||
|
if (languageArg) {
|
||||||
|
languageArg = languageArg.toLowerCase();
|
||||||
|
languageLabel = validLabels[languageArg] ? validLabels[languageArg] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languageLabel) {
|
||||||
|
console.log(`finding PRs with label = ${languageLabel}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = new ProcessingLog('unknown-repo-prs-with-merge-conflicts');
|
||||||
|
log.start();
|
||||||
|
(async () => {
|
||||||
|
const { totalPRs, firstPR, lastPR } = await getUserInput('all');
|
||||||
|
const prPropsToGet = ['number', 'labels', 'user', 'head'];
|
||||||
|
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||||
|
if (openPRs.length) {
|
||||||
|
let count = 0;
|
||||||
|
let mergeConflictCount = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < openPRs.length; i++) {
|
||||||
|
let { labels, number, head: { repo: headRepo } } = openPRs[i];
|
||||||
|
|
||||||
|
const hasLanguage = languageLabel && labels.some(
|
||||||
|
({ name }) => languageLabel === name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (headRepo === null && (!languageLabel || hasLanguage )) {
|
||||||
|
let data = await octokit.pullRequests.get({ owner, repo, number });
|
||||||
|
let mergeableState = data.data.mergeable_state;
|
||||||
|
if (mergeableState === 'unknown') {
|
||||||
|
await rateLimiter(4000); // allow time to let GitHub determine status
|
||||||
|
data = await octokit.pullRequests.get({ owner, repo, number });
|
||||||
|
mergeableState = data.data.mergeable_state;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
|
||||||
|
if (mergeableState === 'dirty' || mergeableState === 'unknown') {
|
||||||
|
log.add(number, { number, mergeableState });
|
||||||
|
console.log(`${number} (${mergeableState})`);
|
||||||
|
mergeConflictCount++;
|
||||||
|
}
|
||||||
|
if (count > 4000) {
|
||||||
|
await rateLimiter(2350);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`There were ${mergeConflictCount} PRs with potential merge conflicts out of ${count} unknown repos received from GitHub`);
|
||||||
|
} else {
|
||||||
|
throw 'There were no open PRs received from Github';
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.then(async () => {
|
||||||
|
log.finish();
|
||||||
|
console.log('Finished finding unknown repo PRs with merge conflicts');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.finish();
|
||||||
|
console.log(err);
|
||||||
|
});
|
318
tools/dashboard/one-off-scripts/package-lock.json
generated
Normal file
318
tools/dashboard/one-off-scripts/package-lock.json
generated
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
{
|
||||||
|
"name": "freecodecampcontributeoneoffscripts",
|
||||||
|
"requires": true,
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/rest": {
|
||||||
|
"version": "15.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-15.18.1.tgz",
|
||||||
|
"integrity": "sha512-g2tecjp2TEtYV8bKAFvfQtu+W29HM7ektmWmw8zrMy9/XCKDEYRErR2YvvhN9+IxkLC4O3lDqYP4b6WgsL6Utw==",
|
||||||
|
"requires": {
|
||||||
|
"before-after-hook": "^1.1.0",
|
||||||
|
"btoa-lite": "^1.0.0",
|
||||||
|
"debug": "^3.1.0",
|
||||||
|
"http-proxy-agent": "^2.1.0",
|
||||||
|
"https-proxy-agent": "^2.2.0",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"node-fetch": "^2.1.1",
|
||||||
|
"universal-user-agent": "^2.0.0",
|
||||||
|
"url-template": "^2.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"agent-base": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
|
||||||
|
"requires": {
|
||||||
|
"es6-promisify": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ansi-regex": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
|
||||||
|
},
|
||||||
|
"before-after-hook": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-BIjg60OP/sQvG7Q2L9Xkc77gyyFw1B4T73LIfZVQtXbutJinC1+t2HRl4qeR3EWAmY+tA6z9vpRi02q6ZXyluQ=="
|
||||||
|
},
|
||||||
|
"btoa-lite": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-M3dm2hWAEhD92VbCLpxokaudAzc="
|
||||||
|
},
|
||||||
|
"cli-progress": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA==",
|
||||||
|
"requires": {
|
||||||
|
"colors": "^1.1.2",
|
||||||
|
"string-width": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"colors": {
|
||||||
|
"version": "1.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
|
||||||
|
"integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg=="
|
||||||
|
},
|
||||||
|
"cross-spawn": {
|
||||||
|
"version": "6.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
|
||||||
|
"integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
|
||||||
|
"requires": {
|
||||||
|
"nice-try": "^1.0.4",
|
||||||
|
"path-key": "^2.0.1",
|
||||||
|
"semver": "^5.5.0",
|
||||||
|
"shebang-command": "^1.2.0",
|
||||||
|
"which": "^1.2.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "3.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||||
|
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dedent": {
|
||||||
|
"version": "0.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
||||||
|
"integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw="
|
||||||
|
},
|
||||||
|
"es6-promise": {
|
||||||
|
"version": "4.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.5.tgz",
|
||||||
|
"integrity": "sha512-n6wvpdE43VFtJq+lUDYDBFUwV8TZbuGXLV4D6wKafg13ldznKsyEvatubnmUe31zcvelSzOHF+XbaT+Bl9ObDg=="
|
||||||
|
},
|
||||||
|
"es6-promisify": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
|
||||||
|
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
|
||||||
|
"requires": {
|
||||||
|
"es6-promise": "^4.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"execa": {
|
||||||
|
"version": "0.10.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz",
|
||||||
|
"integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==",
|
||||||
|
"requires": {
|
||||||
|
"cross-spawn": "^6.0.0",
|
||||||
|
"get-stream": "^3.0.0",
|
||||||
|
"is-stream": "^1.1.0",
|
||||||
|
"npm-run-path": "^2.0.0",
|
||||||
|
"p-finally": "^1.0.0",
|
||||||
|
"signal-exit": "^3.0.0",
|
||||||
|
"strip-eof": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get-stream": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
|
||||||
|
},
|
||||||
|
"http-proxy-agent": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
|
||||||
|
"requires": {
|
||||||
|
"agent-base": "4",
|
||||||
|
"debug": "3.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"debug": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https-proxy-agent": {
|
||||||
|
"version": "2.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
|
||||||
|
"integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
|
||||||
|
"requires": {
|
||||||
|
"agent-base": "^4.1.0",
|
||||||
|
"debug": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||||
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||||
|
},
|
||||||
|
"is-fullwidth-code-point": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
|
||||||
|
},
|
||||||
|
"is-stream": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||||
|
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
|
||||||
|
},
|
||||||
|
"isexe": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
|
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
|
||||||
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||||
|
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
|
||||||
|
},
|
||||||
|
"macos-release": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-iCM3ZGeqIzlrH7KxYK+fphlJpCCczyHXc+HhRVbEu9uNTCrzYJjvvtefzeKTCVHd5AP/aD/fzC80JZ4ZP+dQ/A=="
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
|
||||||
|
},
|
||||||
|
"nice-try": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ=="
|
||||||
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA=="
|
||||||
|
},
|
||||||
|
"npm-run-path": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
|
||||||
|
"integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
|
||||||
|
"requires": {
|
||||||
|
"path-key": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"os-name": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/os-name/-/os-name-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-7c74tib2FsdFbQ3W+qj8Tyd1R3Z6tuVRNNxXjJcZ4NgjIEQU9N/prVMqcW29XZPXGACqaXN3jq58/6hoaoXH6g==",
|
||||||
|
"requires": {
|
||||||
|
"macos-release": "^2.0.0",
|
||||||
|
"windows-release": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"p-finally": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"version": "0.12.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
|
||||||
|
"integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=",
|
||||||
|
"requires": {
|
||||||
|
"process": "^0.11.1",
|
||||||
|
"util": "^0.10.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path-key": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
|
||||||
|
"integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
|
||||||
|
},
|
||||||
|
"process": {
|
||||||
|
"version": "0.11.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||||
|
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
|
||||||
|
},
|
||||||
|
"semver": {
|
||||||
|
"version": "5.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
|
||||||
|
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
|
||||||
|
},
|
||||||
|
"shebang-command": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
|
||||||
|
"requires": {
|
||||||
|
"shebang-regex": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shebang-regex": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
|
||||||
|
},
|
||||||
|
"signal-exit": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
|
||||||
|
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
|
||||||
|
},
|
||||||
|
"string-width": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
|
||||||
|
"requires": {
|
||||||
|
"is-fullwidth-code-point": "^2.0.0",
|
||||||
|
"strip-ansi": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
|
||||||
|
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strip-eof": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
|
||||||
|
},
|
||||||
|
"universal-user-agent": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-nOwvHWLH3dBazyuzbECPA5uVFNd7AlgviXRHgR4yf48QqitIvpdncRrxMbZNMpPPEfgz30I9ubd1XmiJiqsTrg==",
|
||||||
|
"requires": {
|
||||||
|
"os-name": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url-template": {
|
||||||
|
"version": "2.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
|
||||||
|
"integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE="
|
||||||
|
},
|
||||||
|
"util": {
|
||||||
|
"version": "0.10.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
|
||||||
|
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
|
||||||
|
"requires": {
|
||||||
|
"inherits": "2.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"which": {
|
||||||
|
"version": "1.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
|
||||||
|
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
|
||||||
|
"requires": {
|
||||||
|
"isexe": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"windows-release": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-hBb7m7acFgQPQc222uEQTmdcGLeBmQLNLFIh0rDk3CwFOBrfjefLzEfEfmpMq8Af/n/GnFf3eYf203FY1PmudA==",
|
||||||
|
"requires": {
|
||||||
|
"execa": "^0.10.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
tools/dashboard/one-off-scripts/package.json
Normal file
14
tools/dashboard/one-off-scripts/package.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "freecodecampcontributeoneoffscripts",
|
||||||
|
"dependencies": {
|
||||||
|
"@octokit/rest": "^15.18.0",
|
||||||
|
"cli-progress": "^2.1.0",
|
||||||
|
"dedent": "^0.7.0",
|
||||||
|
"path": "^0.12.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
}
|
88
tools/dashboard/one-off-scripts/prs-with-merge-conflicts.js
Normal file
88
tools/dashboard/one-off-scripts/prs-with-merge-conflicts.js
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
/*
|
||||||
|
This script was created to find all open PRs that have merge
|
||||||
|
conflicts and then add a the 'status: merge conflict' label to any PR
|
||||||
|
which does not already have the label.
|
||||||
|
|
||||||
|
To run the script for a specific language, call the script with the language
|
||||||
|
name as the first argument.
|
||||||
|
|
||||||
|
Note: It is possible that it could take more than 4 seconds for GitHub to
|
||||||
|
determine if a PR is mergeable. If that happens, the PR will not be labeled.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { owner, repo, octokitConfig, octokitAuth } = require('../lib/constants');
|
||||||
|
const octokit = require('@octokit/rest')(octokitConfig);
|
||||||
|
|
||||||
|
const { getPRs, getUserInput } = require('../lib/get-prs');
|
||||||
|
const { ProcessingLog, rateLimiter } = require('../lib/utils');
|
||||||
|
const { addLabels } = require('../lib/pr-tasks');
|
||||||
|
const { validLabels } = require('../lib/validation/valid-labels');
|
||||||
|
|
||||||
|
octokit.authenticate(octokitAuth);
|
||||||
|
|
||||||
|
let languageLabel;
|
||||||
|
let [languageArg] = process.argv.slice(2);
|
||||||
|
if (languageArg) {
|
||||||
|
languageArg = languageArg.toLowerCase();
|
||||||
|
languageLabel = validLabels[languageArg] ? validLabels[languageArg] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (languageLabel) {
|
||||||
|
console.log(`finding PRs with label = ${languageLabel}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const log = new ProcessingLog('prs-with-merge-conflicts');
|
||||||
|
log.start();
|
||||||
|
(async () => {
|
||||||
|
const { totalPRs, firstPR, lastPR } = await getUserInput('all');
|
||||||
|
const prPropsToGet = ['number', 'labels', 'user'];
|
||||||
|
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||||
|
if (openPRs.length) {
|
||||||
|
let count = 0;
|
||||||
|
let mergeConflictCount = 0;
|
||||||
|
for (let i = 0; i < openPRs.length; i++) {
|
||||||
|
let { labels, number } = openPRs[i];
|
||||||
|
|
||||||
|
const hasLanguage = languageLabel && labels.some(
|
||||||
|
({ name }) => languageLabel === name
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasMergeConflictLabel = labels.some(
|
||||||
|
({ name }) => "status: merge conflict" === name
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!languageLabel || hasLanguage) {
|
||||||
|
let data = await octokit.pullRequests.get({ owner, repo, number });
|
||||||
|
let mergeableState = data.data.mergeable_state;
|
||||||
|
count++;
|
||||||
|
if (mergeableState === 'unknown') {
|
||||||
|
await rateLimiter(4000);
|
||||||
|
data = await octokit.pullRequests.get({ owner, repo, number });
|
||||||
|
mergeableState = data.data.mergeable_state;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mergeableState === 'dirty' && !hasMergeConflictLabel) {
|
||||||
|
mergeConflictCount++;
|
||||||
|
addLabels(number, ['status: merge conflict'], log);
|
||||||
|
await rateLimiter();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > 4000) {
|
||||||
|
await rateLimiter(2350);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`There were ${mergeConflictCount} PRs with potential merge conflicts out of ${count} PRs received from GitHub`);
|
||||||
|
} else {
|
||||||
|
throw 'There were no open PRs received from Github';
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.then(async () => {
|
||||||
|
log.finish();
|
||||||
|
console.log('Finished finding PRs with merge conflicts');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.finish();
|
||||||
|
console.log(err);
|
||||||
|
});
|
57
tools/dashboard/one-off-scripts/sweeper.js
Normal file
57
tools/dashboard/one-off-scripts/sweeper.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
This script was originally created to iterate over all open PRs to label and
|
||||||
|
comment on specific PR errors (i.e. guide related filenmame syntax and
|
||||||
|
frontmatter).
|
||||||
|
|
||||||
|
Since the first run which covered over 10,000+ PRs, it is curently ran every
|
||||||
|
couple of days for just the most recent PRs.
|
||||||
|
|
||||||
|
To run the script for a specific range,
|
||||||
|
run `node sweeper.js range startingPrNumber endingPrNumber`
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { getPRs, getUserInput, getFiles } = require('../lib/get-prs');
|
||||||
|
const { ProcessingLog, rateLimiter } = require('../lib/utils');
|
||||||
|
const { labeler } = require('../lib/pr-tasks');
|
||||||
|
|
||||||
|
const log = new ProcessingLog('sweeper');
|
||||||
|
|
||||||
|
log.start();
|
||||||
|
console.log('Sweeper started...');
|
||||||
|
(async () => {
|
||||||
|
const { totalPRs, firstPR, lastPR } = await getUserInput();
|
||||||
|
const prPropsToGet = ['number', 'labels', 'user'];
|
||||||
|
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||||
|
let count = 0;
|
||||||
|
if (openPRs.length) {
|
||||||
|
console.log('Processing PRs...');
|
||||||
|
for (let i = 0; i < openPRs.length; i++) {
|
||||||
|
let {
|
||||||
|
number,
|
||||||
|
labels: currentLabels,
|
||||||
|
} = openPRs[i];
|
||||||
|
const prFiles = await getFiles(number);
|
||||||
|
count++;
|
||||||
|
|
||||||
|
const labelsAdded = await labeler(
|
||||||
|
number,
|
||||||
|
prFiles,
|
||||||
|
currentLabels,
|
||||||
|
);
|
||||||
|
const labelLogVal = labelsAdded.length ? labelsAdded : 'none added';
|
||||||
|
|
||||||
|
log.add(number, { number, labels: labelLogVal });
|
||||||
|
if (count > 4000) {
|
||||||
|
await rateLimiter(2350);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
.then(() => {
|
||||||
|
log.finish();
|
||||||
|
console.log('Sweeper complete');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
log.finish();
|
||||||
|
console.log(err);
|
||||||
|
});
|
10434
tools/dashboard/package-lock.json
generated
Normal file
10434
tools/dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
50
tools/dashboard/package.json
Normal file
50
tools/dashboard/package.json
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"name": "@freecodecamp/contribute",
|
||||||
|
"description": "Tools to help maintain freecodecamp.org's Open Source Codebase on GitHub",
|
||||||
|
"bin": {
|
||||||
|
"presolver": "probot/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"bootstrap": "lerna bootstrap --ci",
|
||||||
|
"build": "lerna run build",
|
||||||
|
"postinstall": "npm run bootstrap",
|
||||||
|
"test": "run-p test:*",
|
||||||
|
"test:probot": "cd ./probot && npm run test",
|
||||||
|
"test:client": "cd ./probot/client && npm run test",
|
||||||
|
"format": "prettier --write es5 ./**/*.{js,json} && npm run lint",
|
||||||
|
"lint": "eslint ./**/*.js --fix",
|
||||||
|
"start": "cd probot && npm start && cd ../",
|
||||||
|
"develop": "run-p develop:*",
|
||||||
|
"develop:client": "cd ./probot/client && npm run develop",
|
||||||
|
"develop:server": "cd ./probot/ && npm run develop"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"dotenv": "^6.2.0",
|
||||||
|
"eslint": "^5.9.0",
|
||||||
|
"eslint-config-freecodecamp": "^1.1.1",
|
||||||
|
"esm": "^3.0.84",
|
||||||
|
"joi": "^14.3.1",
|
||||||
|
"lerna": "^3.5.1",
|
||||||
|
"mocha": "^5.2.0",
|
||||||
|
"nodemon": "^1.18.9",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"presolver": "file:probot",
|
||||||
|
"prettier": "^1.15.2"
|
||||||
|
},
|
||||||
|
"nodemonConfig": {
|
||||||
|
"exec": "npm start",
|
||||||
|
"watch": [
|
||||||
|
".env",
|
||||||
|
"."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"probot": {
|
||||||
|
"apps": [
|
||||||
|
"presolver"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ajv": "^6.6.1",
|
||||||
|
"ajv-keywords": "^3.2.0"
|
||||||
|
}
|
||||||
|
}
|
139
tools/dashboard/probot/app.yml
Normal file
139
tools/dashboard/probot/app.yml
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
# This is a GitHub App Manifest. These settings will be used by default when
|
||||||
|
# initially configuring your GitHub App.
|
||||||
|
#
|
||||||
|
# NOTE: changing this file will not update your GitHub App settings.
|
||||||
|
# You must visit github.com/settings/apps/your-app-name to edit them.
|
||||||
|
#
|
||||||
|
# Read more about configuring your GitHub App:
|
||||||
|
# https://probot.github.io/docs/development/#configuring-a-github-app
|
||||||
|
#
|
||||||
|
# Read more about GitHub App Manifests:
|
||||||
|
# https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest/
|
||||||
|
|
||||||
|
# The list of events the GitHub App subscribes to.
|
||||||
|
# Uncomment the event names below to enable them.
|
||||||
|
default_events:
|
||||||
|
# - check_run
|
||||||
|
# - check_suite
|
||||||
|
# - commit_comment
|
||||||
|
# - create
|
||||||
|
# - delete
|
||||||
|
# - deployment
|
||||||
|
# - deployment_status
|
||||||
|
# - fork
|
||||||
|
# - gollum
|
||||||
|
# - issue_comment
|
||||||
|
- issues
|
||||||
|
# - label
|
||||||
|
# - milestone
|
||||||
|
# - member
|
||||||
|
# - membership
|
||||||
|
# - org_block
|
||||||
|
# - organization
|
||||||
|
# - page_build
|
||||||
|
# - project
|
||||||
|
# - project_card
|
||||||
|
# - project_column
|
||||||
|
# - public
|
||||||
|
- pull_request
|
||||||
|
- pull_request_review
|
||||||
|
- pull_request_review_comment
|
||||||
|
# - push
|
||||||
|
# - release
|
||||||
|
- repository
|
||||||
|
- repository_import
|
||||||
|
# - status
|
||||||
|
# - team
|
||||||
|
# - team_add
|
||||||
|
# - watch
|
||||||
|
|
||||||
|
# The set of permissions needed by the GitHub App. The format of the object uses
|
||||||
|
# the permission name for the key (for example, issues) and the access type for
|
||||||
|
# the value (for example, write).
|
||||||
|
# Valid values are `read`, `write`, and `none`
|
||||||
|
default_permissions:
|
||||||
|
# Repository creation, deletion, settings, teams, and collaborators.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-administration
|
||||||
|
administration: read
|
||||||
|
|
||||||
|
# Checks on code.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-checks
|
||||||
|
checks: read
|
||||||
|
|
||||||
|
# Repository contents, commits, branches, downloads, releases, and merges.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-contents
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
# Deployments and deployment statuses.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-deployments
|
||||||
|
# deployments: read
|
||||||
|
|
||||||
|
# Issues and related comments, assignees, labels, and milestones.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-issues
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
# Search repositories, list collaborators, and access repository metadata.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#metadata-permissions
|
||||||
|
metadata: read
|
||||||
|
|
||||||
|
# Retrieve Pages statuses, configuration, and builds, as well as create new builds.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-pages
|
||||||
|
# pages: read
|
||||||
|
|
||||||
|
# Pull requests and related comments, assignees, labels, milestones, and merges.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-pull-requests
|
||||||
|
pull_requests: write
|
||||||
|
|
||||||
|
# Manage the post-receive hooks for a repository.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-repository-hooks
|
||||||
|
repository_hooks: read
|
||||||
|
|
||||||
|
# Manage repository projects, columns, and cards.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-repository-projects
|
||||||
|
# repository_projects: read
|
||||||
|
|
||||||
|
# Retrieve security vulnerability alerts.
|
||||||
|
# https://developer.github.com/v4/object/repositoryvulnerabilityalert/
|
||||||
|
# vulnerability_alerts: read
|
||||||
|
|
||||||
|
# Commit statuses.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-statuses
|
||||||
|
statuses: read
|
||||||
|
|
||||||
|
# Organization members and teams.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-members
|
||||||
|
# members: read
|
||||||
|
|
||||||
|
# View and manage users blocked by the organization.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-organization-user-blocking
|
||||||
|
# organization_user_blocking: read
|
||||||
|
|
||||||
|
# Manage organization projects, columns, and cards.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-organization-projects
|
||||||
|
# organization_projects: read
|
||||||
|
|
||||||
|
# Manage team discussions and related comments.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-team-discussions
|
||||||
|
# team_discussions: read
|
||||||
|
|
||||||
|
# Manage the post-receive hooks for an organization.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/#permission-on-organization-hooks
|
||||||
|
organization_hooks: read
|
||||||
|
|
||||||
|
# Get notified of, and update, content references.
|
||||||
|
# https://developer.github.com/v3/apps/permissions/
|
||||||
|
# organization_administration: read
|
||||||
|
|
||||||
|
|
||||||
|
# The name of the GitHub App. Defaults to the name specified in package.json
|
||||||
|
# name: My Probot App
|
||||||
|
|
||||||
|
# The homepage of your GitHub App.
|
||||||
|
# url: https://example.com/
|
||||||
|
|
||||||
|
# A description of the GitHub App.
|
||||||
|
# description: A description of my awesome app
|
||||||
|
|
||||||
|
# Set to true when your GitHub App is available to the public or false when it is only accessible to the owner of the app.
|
||||||
|
# Default: true
|
||||||
|
# public: false
|
15146
tools/dashboard/probot/client/package-lock.json
generated
Normal file
15146
tools/dashboard/probot/client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
tools/dashboard/probot/client/package.json
Normal file
30
tools/dashboard/probot/client/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@freecodecamp/dashboard-client",
|
||||||
|
"homepage": ".",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^16.6.3",
|
||||||
|
"react-dom": "^16.6.3",
|
||||||
|
"react-scripts": "2.1.1",
|
||||||
|
"styled-components": "^4.1.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "develop",
|
||||||
|
"develop": "cross-env REACT_APP_HOST=local SKIP_PREFLIGHT_CHECK=true react-scripts start",
|
||||||
|
"build": "cross-env SKIP_PREFLIGHT_CHECK=true react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": "react-app"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not ie <= 11",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"cross-env": "^5.2.0"
|
||||||
|
}
|
||||||
|
}
|
BIN
tools/dashboard/probot/client/public/favicon.ico
Normal file
BIN
tools/dashboard/probot/client/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
37
tools/dashboard/probot/client/public/index.html
Normal file
37
tools/dashboard/probot/client/public/index.html
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<link rel="shortcut icon" href="favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
<!--
|
||||||
|
manifest.json provides metadata used when your web app is added to the
|
||||||
|
homescreen on Android. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
|
-->
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<!--
|
||||||
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
|
-->
|
||||||
|
<title>freeCodeCamp Contributor Tools</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
You need to enable JavaScript to run this app.
|
||||||
|
</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<!--
|
||||||
|
This HTML file is a template.
|
||||||
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
You can add webfonts, meta tags, or analytics to this file.
|
||||||
|
The build step will place the bundled scripts into the <body> tag.
|
||||||
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
|
-->
|
||||||
|
</body>
|
||||||
|
</html>
|
15
tools/dashboard/probot/client/public/manifest.json
Normal file
15
tools/dashboard/probot/client/public/manifest.json
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"short_name": "FCC Contribute",
|
||||||
|
"name": "Free Code Camp Contribute",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
124
tools/dashboard/probot/client/src/App.js
Normal file
124
tools/dashboard/probot/client/src/App.js
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import Tabs from './components/Tabs';
|
||||||
|
import Search from './components/Search';
|
||||||
|
import Pareto from './components/Pareto';
|
||||||
|
import Footer from './components/Footer';
|
||||||
|
|
||||||
|
import { ENDPOINT_INFO } from './constants';
|
||||||
|
|
||||||
|
const PageContainer = styled.div`
|
||||||
|
margin-top: 70px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
margin-top: 135px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
max-width: 960px;
|
||||||
|
width: 90vw;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 0 4px 0 #777;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const AppNavBar = styled.nav`
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
color: white;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display:flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: ${({ theme }) => theme.primary};
|
||||||
|
@media (max-width: 991px) {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const imgStyle = { paddingLeft: '30px' };
|
||||||
|
|
||||||
|
const titleStyle = { margin: '0', padding: '0' };
|
||||||
|
|
||||||
|
|
||||||
|
class App extends Component {
|
||||||
|
state = {
|
||||||
|
view: 'search',
|
||||||
|
footerInfo: null
|
||||||
|
};
|
||||||
|
|
||||||
|
updateInfo() {
|
||||||
|
fetch(ENDPOINT_INFO)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(({ ok, numPRs, prRange, lastUpdate }) => {
|
||||||
|
if (ok) {
|
||||||
|
const footerInfo = { numPRs, prRange, lastUpdate };
|
||||||
|
this.setState(prevState => ({ footerInfo }));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// do nothing
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleViewChange = ({ target: { id } }) => {
|
||||||
|
const view = id.replace('tabs-', '');
|
||||||
|
this.setState(prevState => ({ ...this.clearObj, view }));
|
||||||
|
this.updateInfo();
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.updateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
handleViewChange,
|
||||||
|
state: { view, footerInfo }
|
||||||
|
} = this;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<AppNavBar>
|
||||||
|
<a href="https://freecodecamp.org" target="_blank" rel="noopener noreferrer">
|
||||||
|
<img
|
||||||
|
style={imgStyle}
|
||||||
|
src="https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/e/d/ed1c70bda321aaeee9e6c20ab650ce8bc34899fa.svg"
|
||||||
|
alt="Free Code Camp Logo"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
<h1 style={titleStyle}>Contributor Tools</h1>
|
||||||
|
<ul className="app-menu">
|
||||||
|
<li>
|
||||||
|
<a href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://github.com/freeCodeCamp/freeCodeCamp" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</AppNavBar>
|
||||||
|
<PageContainer>
|
||||||
|
|
||||||
|
<Tabs view={view} onViewChange={handleViewChange} />
|
||||||
|
<Container>
|
||||||
|
{view === 'search' && <Search />}
|
||||||
|
{view === 'reports' && <Pareto />}
|
||||||
|
</Container>
|
||||||
|
{footerInfo && <Footer footerInfo={footerInfo} />}
|
||||||
|
</PageContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default App;
|
9
tools/dashboard/probot/client/src/App.test.mjs
Normal file
9
tools/dashboard/probot/client/src/App.test.mjs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
it('renders without crashing', () => {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
ReactDOM.render(<App />, div);
|
||||||
|
ReactDOM.unmountComponentAtNode(div);
|
||||||
|
});
|
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import ListItem from './ListItem';
|
||||||
|
import FullWidthDiv from './FullWidthDiv';
|
||||||
|
import Result from './Result';
|
||||||
|
|
||||||
|
const List = styled.div`
|
||||||
|
margin: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const filenameTitle = { fontWeight: '600' };
|
||||||
|
|
||||||
|
const FilenameResults = ({ searchValue, results }) => {
|
||||||
|
const elements = results.map(result => {
|
||||||
|
const { filename, prs: prObjects } = result;
|
||||||
|
const prs = prObjects.map(({ number, username, title }, index) => {
|
||||||
|
return <ListItem number={number} username={username} prTitle={title} />;
|
||||||
|
});
|
||||||
|
|
||||||
|
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
|
||||||
|
return (
|
||||||
|
<Result key={filename}>
|
||||||
|
<span style={filenameTitle}>{filename}</span>{' '}
|
||||||
|
<a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
|
||||||
|
(File on Master)
|
||||||
|
</a>
|
||||||
|
<List>{prs}</List>
|
||||||
|
</Result>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullWidthDiv>
|
||||||
|
{results.length ? <h3>Results for: {searchValue}</h3> : null}
|
||||||
|
{elements}
|
||||||
|
</FullWidthDiv>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FilenameResults;
|
24
tools/dashboard/probot/client/src/components/FilterOption.js
Normal file
24
tools/dashboard/probot/client/src/components/FilterOption.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Container = styled.label`
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FilterOption = ({ group, children, value, selectedOption, onOptionChange }) => {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<input
|
||||||
|
name={group}
|
||||||
|
type="radio"
|
||||||
|
value={value}
|
||||||
|
checked={selectedOption === value}
|
||||||
|
onChange={onOptionChange}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export default FilterOption;
|
35
tools/dashboard/probot/client/src/components/Footer.js
Normal file
35
tools/dashboard/probot/client/src/components/Footer.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
margin-top: 5px;
|
||||||
|
text-align: center;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Info = styled.div`
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 2px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Footer = props => {
|
||||||
|
const localTime = lastUpdate => {
|
||||||
|
const newTime = new Date(lastUpdate);
|
||||||
|
return newTime.toLocaleString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
footerInfo: { numPRs, prRange, lastUpdate }
|
||||||
|
} = props;
|
||||||
|
return (
|
||||||
|
lastUpdate && (
|
||||||
|
<Container>
|
||||||
|
<Info>Last Update: {localTime(lastUpdate)}</Info>
|
||||||
|
<Info>
|
||||||
|
# of open PRs: {numPRs} ({prRange})
|
||||||
|
</Info>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Footer;
|
@ -0,0 +1,7 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const FullWidthDiv = styled.div`
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default FullWidthDiv;
|
18
tools/dashboard/probot/client/src/components/Input.js
Normal file
18
tools/dashboard/probot/client/src/components/Input.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Container = styled.input`
|
||||||
|
margin-bottom: 10px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Input = React.forwardRef((props, ref) => (
|
||||||
|
<Container
|
||||||
|
type="text"
|
||||||
|
onChange={props.onInputEvent}
|
||||||
|
onKeyPress={props.onInputEvent}
|
||||||
|
value={props.value}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
export default Input;
|
37
tools/dashboard/probot/client/src/components/ListItem.js
Normal file
37
tools/dashboard/probot/client/src/components/ListItem.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-direction: row;
|
||||||
|
overflow: hidden;
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
margin-top: 1em;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const prNumStyle = { flex: 1 };
|
||||||
|
const usernameStyle = { flex: 1 };
|
||||||
|
const titleStyle = { flex: 3 };
|
||||||
|
|
||||||
|
const ListItem = ({ number, username, prTitle: title }) => {
|
||||||
|
const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}`;
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<a
|
||||||
|
style={prNumStyle}
|
||||||
|
href={prUrl}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
#{number}
|
||||||
|
</a>
|
||||||
|
<span style={usernameStyle}>{username}</span>
|
||||||
|
<span style={titleStyle}>{title}</span>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListItem;
|
207
tools/dashboard/probot/client/src/components/Pareto.js
Normal file
207
tools/dashboard/probot/client/src/components/Pareto.js
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import ListItem from './ListItem';
|
||||||
|
import FullWidthDiv from './FullWidthDiv';
|
||||||
|
import Result from './Result';
|
||||||
|
import FilterOption from './FilterOption';
|
||||||
|
import { ENDPOINT_PARETO } from '../constants';
|
||||||
|
|
||||||
|
const List = styled.div`
|
||||||
|
margin: 5px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Options = styled.div`
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const detailsStyle = { padding: '3px' };
|
||||||
|
const filenameTitle = { fontWeight: '600' };
|
||||||
|
|
||||||
|
class Pareto extends React.Component {
|
||||||
|
state = {
|
||||||
|
data: [],
|
||||||
|
all: [],
|
||||||
|
selectedFileType: 'all',
|
||||||
|
selectedLanguage: 'all',
|
||||||
|
options: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
fetch(ENDPOINT_PARETO)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(({ ok, pareto }) => {
|
||||||
|
if (ok) {
|
||||||
|
if (!pareto.length) {
|
||||||
|
pareto.push({
|
||||||
|
filename: 'Nothing to show in Pareto Report',
|
||||||
|
count: 0,
|
||||||
|
prs: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState(prevState => ({
|
||||||
|
data: pareto,
|
||||||
|
all: [...pareto],
|
||||||
|
options: this.createOptions(pareto)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
const pareto = [
|
||||||
|
{ filename: 'Nothing to show in Pareto Report', count: 0, prs: [] }
|
||||||
|
];
|
||||||
|
this.setState(prevState => ({ data: pareto }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createOptions = (data) => {
|
||||||
|
const options = data.reduce((seen, { filename }) => {
|
||||||
|
const { articleType, language } = this.getFilenameOptions(filename);
|
||||||
|
if (articleType && language) {
|
||||||
|
if (!seen.hasOwnProperty(articleType)) {
|
||||||
|
seen[articleType] = {};
|
||||||
|
}
|
||||||
|
seen[articleType][language] = true;
|
||||||
|
}
|
||||||
|
return seen;
|
||||||
|
}, {});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFileTypeOptionChange = changeEvent => {
|
||||||
|
let { all, selectedLanguage, options } = this.state;
|
||||||
|
const selectedFileType = changeEvent.target.value;
|
||||||
|
|
||||||
|
let data = [ ...all ].filter(({ filename }) => {
|
||||||
|
const { articleType, language } = this.getFilenameOptions(filename);
|
||||||
|
let condition;
|
||||||
|
if (selectedFileType === 'all') {
|
||||||
|
condition = true;
|
||||||
|
selectedLanguage = 'all';
|
||||||
|
} else {
|
||||||
|
if (selectedLanguage === 'all') {
|
||||||
|
condition = articleType === selectedFileType;
|
||||||
|
} else if (!options[selectedFileType][selectedLanguage]) {
|
||||||
|
condition = articleType === selectedFileType
|
||||||
|
selectedLanguage = 'all';
|
||||||
|
} else {
|
||||||
|
condition = articleType === selectedFileType && language === selectedLanguage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return condition;
|
||||||
|
});
|
||||||
|
this.setState(prevState => ({ data, selectedFileType, selectedLanguage }));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLanguageOptionChange = changeEvent => {
|
||||||
|
const { all, selectedFileType } = this.state;
|
||||||
|
const selectedLanguage = changeEvent.target.value;
|
||||||
|
let data = [ ...all ].filter(({ filename }) => {
|
||||||
|
const { articleType, language } = this.getFilenameOptions(filename);
|
||||||
|
let condition;
|
||||||
|
if (selectedLanguage === 'all') {
|
||||||
|
condition = articleType === selectedFileType
|
||||||
|
} else {
|
||||||
|
condition = language === selectedLanguage && articleType === selectedFileType
|
||||||
|
}
|
||||||
|
return condition;
|
||||||
|
});
|
||||||
|
this.setState(prevState => ({ data, selectedLanguage }));
|
||||||
|
}
|
||||||
|
|
||||||
|
getFilenameOptions = filename => {
|
||||||
|
const filenameReplacement = filename.replace(
|
||||||
|
/^curriculum\/challenges\//, 'curriculum/'
|
||||||
|
);
|
||||||
|
const regex = /^(docs|curriculum|guide)(?:\/)(english|arabic|chinese|portuguese|russian|spanish)?\/?/;
|
||||||
|
// need an array to pass to labelsAdder
|
||||||
|
// eslint-disable-next-line
|
||||||
|
const [_, articleType, language] = filenameReplacement.match(regex) || [];
|
||||||
|
return { articleType, language };
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { data, options, selectedFileType, selectedLanguage } = this.state;
|
||||||
|
|
||||||
|
const elements = data.map(entry => {
|
||||||
|
const { filename, count, prs } = entry;
|
||||||
|
const prsList = prs.map(({ number, username, title }) => {
|
||||||
|
return <ListItem key={number} number={number} username={username} prTitle={title} />;
|
||||||
|
});
|
||||||
|
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
|
||||||
|
return (
|
||||||
|
<Result key={filename}>
|
||||||
|
<span style={filenameTitle}>{filename}</span>{' '}
|
||||||
|
<a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
|
||||||
|
(File on Master)
|
||||||
|
</a>
|
||||||
|
<br />
|
||||||
|
<details style={detailsStyle}>
|
||||||
|
<summary># of PRs: {count}</summary>
|
||||||
|
<List>{prsList}</List>
|
||||||
|
</details>
|
||||||
|
</Result>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let fileTypeOptions = Object.keys(options).map(articleType => articleType);
|
||||||
|
const typeOptions = [ 'all', ...fileTypeOptions].map((type) => (
|
||||||
|
<FilterOption
|
||||||
|
key={type}
|
||||||
|
name="filetype"
|
||||||
|
value={type}
|
||||||
|
onOptionChange={this.handleFileTypeOptionChange}
|
||||||
|
selectedOption={selectedFileType}
|
||||||
|
>
|
||||||
|
{type.charAt().toUpperCase() + type.slice(1)}
|
||||||
|
</FilterOption>
|
||||||
|
));
|
||||||
|
|
||||||
|
let languageOptions = null;
|
||||||
|
if (selectedFileType !== 'all') {
|
||||||
|
let languages = Object.keys(options[selectedFileType]);
|
||||||
|
languages = ['all', ...languages.sort()];
|
||||||
|
languageOptions = languages.map(language => (
|
||||||
|
<FilterOption
|
||||||
|
key={language}
|
||||||
|
name="language"
|
||||||
|
value={language}
|
||||||
|
onOptionChange={this.handleLanguageOptionChange}
|
||||||
|
selectedOption={selectedLanguage}
|
||||||
|
>
|
||||||
|
{language.charAt().toUpperCase() + language.slice(1)}
|
||||||
|
</FilterOption>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<FullWidthDiv>
|
||||||
|
{fileTypeOptions.length > 0 &&
|
||||||
|
<strong>Filter Options</strong>
|
||||||
|
}
|
||||||
|
<Options>
|
||||||
|
{fileTypeOptions.length > 0 &&
|
||||||
|
<>
|
||||||
|
<fieldset>
|
||||||
|
<legend>File Type:</legend>
|
||||||
|
<div>{typeOptions}</div>
|
||||||
|
</fieldset>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
{
|
||||||
|
languageOptions &&
|
||||||
|
<fieldset>
|
||||||
|
<legend>Language:</legend>
|
||||||
|
<div>{languageOptions}</div>
|
||||||
|
</fieldset>
|
||||||
|
}
|
||||||
|
</Options>
|
||||||
|
{data.length ? elements : 'Report Loading...'}
|
||||||
|
</FullWidthDiv>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Pareto;
|
43
tools/dashboard/probot/client/src/components/PrResults.js
Normal file
43
tools/dashboard/probot/client/src/components/PrResults.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
import ListItem from './ListItem';
|
||||||
|
import FullWidthDiv from './FullWidthDiv';
|
||||||
|
import Result from './Result';
|
||||||
|
|
||||||
|
const List = styled.ul`
|
||||||
|
margin: 3px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const PrResults = ({ searchValue, results }) => {
|
||||||
|
const elements = results.map((result, idx) => {
|
||||||
|
const { number, filenames, username, title } = result;
|
||||||
|
const files = filenames.map((filename, index) => {
|
||||||
|
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
|
||||||
|
return (
|
||||||
|
<li key={`${number}-${index}`}>
|
||||||
|
{filename}{' '}
|
||||||
|
<a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
|
||||||
|
(File on Master)
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Result key={`${number}-${idx}`}>
|
||||||
|
<ListItem number={number} username={username} prTitle={title} />
|
||||||
|
<List>{files}</List>
|
||||||
|
</Result>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FullWidthDiv style={{ width: '100%' }}>
|
||||||
|
{results.length ? <h3>Results for PR# {searchValue}</h3> : null}
|
||||||
|
{elements}
|
||||||
|
</FullWidthDiv>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PrResults;
|
12
tools/dashboard/probot/client/src/components/Result.js
Normal file
12
tools/dashboard/probot/client/src/components/Result.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Result = styled.div`
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
margin: 10px 0;
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
padding: 3px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Result;
|
127
tools/dashboard/probot/client/src/components/Search.js
Normal file
127
tools/dashboard/probot/client/src/components/Search.js
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
|
||||||
|
import Input from './Input';
|
||||||
|
import PrResults from './PrResults';
|
||||||
|
import FilenameResults from './FilenameResults';
|
||||||
|
import SearchOption from './SearchOption';
|
||||||
|
|
||||||
|
import { ENDPOINT_PR, ENDPOINT_SEARCH } from '../constants';
|
||||||
|
class Search extends Component {
|
||||||
|
state = {
|
||||||
|
searchValue: '',
|
||||||
|
selectedOption: 'pr',
|
||||||
|
results: [],
|
||||||
|
message: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
clearObj = { searchValue: '', results: [] };
|
||||||
|
|
||||||
|
inputRef = React.createRef();
|
||||||
|
|
||||||
|
handleInputEvent = event => {
|
||||||
|
const {
|
||||||
|
type,
|
||||||
|
key,
|
||||||
|
target: { value: searchValue }
|
||||||
|
} = event;
|
||||||
|
|
||||||
|
if (type === 'change') {
|
||||||
|
if (this.state.selectedOption === 'pr') {
|
||||||
|
if (Number(searchValue) || searchValue === '') {
|
||||||
|
this.setState(prevState => ({ searchValue, results: [] }));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setState(prevState => ({ searchValue, results: [] }));
|
||||||
|
}
|
||||||
|
} else if (type === 'keypress' && key === 'Enter') {
|
||||||
|
this.searchPRs(searchValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleButtonClick = () => {
|
||||||
|
const { searchValue } = this.state;
|
||||||
|
if (searchValue) {
|
||||||
|
this.searchPRs(searchValue);
|
||||||
|
} else {
|
||||||
|
this.inputRef.current.focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handleOptionChange = changeEvent => {
|
||||||
|
const selectedOption = changeEvent.target.value;
|
||||||
|
|
||||||
|
this.setState(prevState => ({ selectedOption, ...this.clearObj }));
|
||||||
|
this.inputRef.current.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
searchPRs = value => {
|
||||||
|
const { selectedOption } = this.state;
|
||||||
|
|
||||||
|
const fetchUrl =
|
||||||
|
selectedOption === 'pr'
|
||||||
|
? `${ENDPOINT_PR}/${value}`
|
||||||
|
: `${ENDPOINT_SEARCH}/?value=${value}`;
|
||||||
|
|
||||||
|
fetch(fetchUrl)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(({ ok, message, results }) => {
|
||||||
|
if (ok) {
|
||||||
|
this.setState(prevState => ({ message, results }));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
this.setState(prevState => this.clearObj);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.inputRef.current.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
handleButtonClick,
|
||||||
|
handleInputEvent,
|
||||||
|
inputRef,
|
||||||
|
handleOptionChange,
|
||||||
|
state
|
||||||
|
} = this;
|
||||||
|
const { searchValue, message, results, selectedOption } = state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<SearchOption
|
||||||
|
value="pr"
|
||||||
|
onOptionChange={handleOptionChange}
|
||||||
|
selectedOption={selectedOption}
|
||||||
|
>
|
||||||
|
PR #
|
||||||
|
</SearchOption>
|
||||||
|
<SearchOption
|
||||||
|
value="filename"
|
||||||
|
onOptionChange={handleOptionChange}
|
||||||
|
selectedOption={selectedOption}
|
||||||
|
>
|
||||||
|
Filename
|
||||||
|
</SearchOption>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
ref={inputRef}
|
||||||
|
value={searchValue}
|
||||||
|
onInputEvent={handleInputEvent}
|
||||||
|
/>
|
||||||
|
<button onClick={handleButtonClick}>Search</button>
|
||||||
|
{message}
|
||||||
|
{selectedOption === 'pr' && (
|
||||||
|
<PrResults searchValue={searchValue} results={results} />
|
||||||
|
)}
|
||||||
|
{selectedOption === 'filename' && (
|
||||||
|
<FilenameResults searchValue={searchValue} results={results} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Search;
|
16
tools/dashboard/probot/client/src/components/SearchOption.js
Normal file
16
tools/dashboard/probot/client/src/components/SearchOption.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const SearchOption = ({ children, value, selectedOption, onOptionChange }) => (
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
name="searchType"
|
||||||
|
type="radio"
|
||||||
|
value={value}
|
||||||
|
checked={selectedOption === value}
|
||||||
|
onChange={onOptionChange}
|
||||||
|
/>
|
||||||
|
{children}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SearchOption;
|
49
tools/dashboard/probot/client/src/components/Tabs.js
Normal file
49
tools/dashboard/probot/client/src/components/Tabs.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
height: 40px;
|
||||||
|
margin: 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Tab = styled.div`
|
||||||
|
background: ${({ active, theme }) => (active ? theme.primary : 'white')};
|
||||||
|
color: ${({ active, theme }) => (active ? 'white' : theme.primary)};
|
||||||
|
font-size: 18px;
|
||||||
|
padding: 5px;
|
||||||
|
border: 2px solid ${({ theme }) => theme.primary};
|
||||||
|
border-left: none;
|
||||||
|
width: 200px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background: #eeeeee;
|
||||||
|
color: ${({ theme }) => theme.primary};
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-left: 2px solid ${({ theme }) => theme.primary};
|
||||||
|
}
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
width: auto;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Tabs = ({ view, onViewChange }) => {
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Tab id="tabs-search" onClick={onViewChange} active={view === 'search'}>
|
||||||
|
Search
|
||||||
|
</Tab>
|
||||||
|
<Tab id="tabs-reports" onClick={onViewChange} active={view === 'reports'}>
|
||||||
|
Pareto
|
||||||
|
</Tab>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tabs;
|
12
tools/dashboard/probot/client/src/constants/index.js
Normal file
12
tools/dashboard/probot/client/src/constants/index.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
let API_HOST = process.env.REACT_APP_HOST === 'local'
|
||||||
|
? 'http://localhost:3001'
|
||||||
|
: '';
|
||||||
|
API_HOST += '/dashboard';
|
||||||
|
|
||||||
|
const ENDPOINT_INFO = API_HOST + '/info';
|
||||||
|
const ENDPOINT_PARETO = API_HOST + '/pareto';
|
||||||
|
const ENDPOINT_PR = API_HOST + '/pr';
|
||||||
|
const ENDPOINT_SEARCH = API_HOST + '/search';
|
||||||
|
export {
|
||||||
|
API_HOST, ENDPOINT_INFO, ENDPOINT_PARETO, ENDPOINT_PR, ENDPOINT_SEARCH
|
||||||
|
};
|
BIN
tools/dashboard/probot/client/src/fonts/Lato-Bold.ttf
Normal file
BIN
tools/dashboard/probot/client/src/fonts/Lato-Bold.ttf
Normal file
Binary file not shown.
BIN
tools/dashboard/probot/client/src/fonts/Lato-Light.ttf
Normal file
BIN
tools/dashboard/probot/client/src/fonts/Lato-Light.ttf
Normal file
Binary file not shown.
BIN
tools/dashboard/probot/client/src/fonts/Lato-Regular.ttf
Normal file
BIN
tools/dashboard/probot/client/src/fonts/Lato-Regular.ttf
Normal file
Binary file not shown.
64
tools/dashboard/probot/client/src/index.css
Normal file
64
tools/dashboard/probot/client/src/index.css
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: 'Lato';
|
||||||
|
src: url(./fonts/Lato-Regular.ttf) format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato Light';
|
||||||
|
src: url(./fonts/Lato-Light.ttf) format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Lato Bold';
|
||||||
|
src: url(./fonts/Lato-Bold.ttf) format('truetype');
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: Lato, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-menu {
|
||||||
|
margin: 0;
|
||||||
|
padding: 13px 15px;
|
||||||
|
margin-right: 15px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-menu li a {
|
||||||
|
padding: 13px 15px;
|
||||||
|
color: white;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-menu li a:hover {
|
||||||
|
color: #006400;
|
||||||
|
background: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #006400;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
text-decoration: none;
|
||||||
|
color: purple;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
18
tools/dashboard/probot/client/src/index.js
Normal file
18
tools/dashboard/probot/client/src/index.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import './index.css';
|
||||||
|
import * as serviceWorker from './serviceWorker';
|
||||||
|
import theme from './theme';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
import { ThemeProvider } from 'styled-components';
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
|
<App />
|
||||||
|
</ThemeProvider>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
||||||
|
|
||||||
|
serviceWorker.unregister();
|
135
tools/dashboard/probot/client/src/serviceWorker.js
Normal file
135
tools/dashboard/probot/client/src/serviceWorker.js
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// This optional code is used to register a service worker.
|
||||||
|
// register() is not called by default.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on subsequent visits to a page, after all the
|
||||||
|
// existing tabs open on the page have been closed, since previously cached
|
||||||
|
// resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model and instructions on how to
|
||||||
|
// opt-in, read http://bit.ly/CRA-PWA
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export function register(config) {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (isLocalhost) {
|
||||||
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl, config);
|
||||||
|
|
||||||
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
|
// service worker/PWA documentation.
|
||||||
|
navigator.serviceWorker.ready.then(() => {
|
||||||
|
console.log(
|
||||||
|
'This web app is being served cache-first by a service ' +
|
||||||
|
'worker. To learn more, visit http://bit.ly/CRA-PWA'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Is not localhost. Just register service worker
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl, config) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
if (installingWorker == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the updated precached content has been fetched,
|
||||||
|
// but the previous service worker will still serve the older
|
||||||
|
// content until all client tabs are closed.
|
||||||
|
console.log(
|
||||||
|
'New content is available and will be used when all ' +
|
||||||
|
'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onUpdate) {
|
||||||
|
config.onUpdate(registration);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// "Content is cached for offline use." message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
|
||||||
|
// Execute callback
|
||||||
|
if (config && config.onSuccess) {
|
||||||
|
config.onSuccess(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl, config) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl)
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
const contentType = response.headers.get('content-type');
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl, config);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
5
tools/dashboard/probot/client/src/theme/index.js
Normal file
5
tools/dashboard/probot/client/src/theme/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const theme = {
|
||||||
|
primary: '#006400'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default theme;
|
92
tools/dashboard/probot/index.js
Normal file
92
tools/dashboard/probot/index.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
const debug = require('debug')('probot:presolver');
|
||||||
|
const Presolver = require('./server/presolver');
|
||||||
|
const config = require('../config');
|
||||||
|
// config should be imported before importing any other file
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
async function probotPlugin(robot) {
|
||||||
|
const events = [
|
||||||
|
'pull_request.opened',
|
||||||
|
'pull_request.edited',
|
||||||
|
'pull_request.synchronize',
|
||||||
|
'pull_request.reopened',
|
||||||
|
'pull_request.labeled',
|
||||||
|
'pull_request.closed'
|
||||||
|
];
|
||||||
|
|
||||||
|
robot.on(events, presolve.bind(null, robot));
|
||||||
|
|
||||||
|
// const prOpened = require('./test/payloads/events/pullRequests.opened');
|
||||||
|
// robot.receive({
|
||||||
|
// name: 'pull_request.opened',
|
||||||
|
// payload: prOpened
|
||||||
|
// });
|
||||||
|
const redirect = robot.route('/');
|
||||||
|
redirect.get('/', (req, res) => res.redirect('/home'));
|
||||||
|
const landingPage = robot.route('/home');
|
||||||
|
landingPage.use(require('express').static('public'));
|
||||||
|
const app = robot.route('/dashboard');
|
||||||
|
const { pareto, pr, search, info } = require('./server/routes');
|
||||||
|
|
||||||
|
const staticPath = path.join(__dirname, '.', 'client', 'build');
|
||||||
|
app.use(require('express').static(staticPath));
|
||||||
|
|
||||||
|
app.use((request, response, next) => {
|
||||||
|
response.header('Access-Control-Allow-Origin', '*');
|
||||||
|
response.header(
|
||||||
|
'Access-Control-Allow-Headers',
|
||||||
|
'Origin, X-Requested-With, Content-Type, Accept'
|
||||||
|
);
|
||||||
|
response.header('Access-Control-Allow-Methods', 'GET');
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
const landingHtml = path.join(__dirname, './public/index.html');
|
||||||
|
landingPage.get('/', (req, res) => res.sendFile(landingHtml));
|
||||||
|
|
||||||
|
const htmlpath = path.join(__dirname, './client/build/index.html');
|
||||||
|
app.get('/', (request, response) => response.sendFile(htmlpath));
|
||||||
|
|
||||||
|
app.use('/pr', pr);
|
||||||
|
app.use('/search', search);
|
||||||
|
app.use('/pareto', pareto);
|
||||||
|
app.use('/info', info);
|
||||||
|
|
||||||
|
app.use(function(err, req, res) {
|
||||||
|
res.status(err.status || 500).send(err.message);
|
||||||
|
});
|
||||||
|
if (mongoose.connection.readyState === 0) {
|
||||||
|
// connect to mongo db
|
||||||
|
const mongoUri = config.mongo.host;
|
||||||
|
|
||||||
|
const promise = mongoose.connect(
|
||||||
|
mongoUri, { useNewUrlParser: true }
|
||||||
|
);
|
||||||
|
promise
|
||||||
|
.then(() => {
|
||||||
|
console.log('MongoDB is connected');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
console.log('MongoDB connection unsuccessful');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function presolve(app, context) {
|
||||||
|
const presolver = forRepository(context);
|
||||||
|
const pullRequest = getPullRequest(context);
|
||||||
|
return presolver.presolve(pullRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
function forRepository(context) {
|
||||||
|
const config = { ...context.repo({ logger: debug }) };
|
||||||
|
return new Presolver(context, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPullRequest(context) {
|
||||||
|
return context.payload.pull_request || context.payload.review.pull_request;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = probotPlugin;
|
9265
tools/dashboard/probot/package-lock.json
generated
Normal file
9265
tools/dashboard/probot/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
60
tools/dashboard/probot/package.json
Normal file
60
tools/dashboard/probot/package.json
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "@freecodecamp/contribute",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"repository": "https://github.com/freecodecamp/contribute.git",
|
||||||
|
"bugs": "https://github.com/freeCodeCamp/contribute/issues",
|
||||||
|
"keywords": [
|
||||||
|
"probot",
|
||||||
|
"github",
|
||||||
|
"probot-app"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"dev": "develop",
|
||||||
|
"develop": "nodemon",
|
||||||
|
"start": "probot run esm ./index.js",
|
||||||
|
"start-probot": "cross-env TEST_ENV=false probot run ./index.js",
|
||||||
|
"lint": "standard --fix",
|
||||||
|
"test": "cross-env TEST_ENV=true jest --forceExit && standard",
|
||||||
|
"test:watch": "jest --watch --notify --notifyMode=change --coverage"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bluebird": "^3.5.3",
|
||||||
|
"body-parser": "^1.18.3",
|
||||||
|
"cli-progress": "^2.1.1",
|
||||||
|
"cross-env": "^5.2.0",
|
||||||
|
"dotenv": "^6.2.0",
|
||||||
|
"express": "^4.16.3",
|
||||||
|
"http-status": "^1.3.1",
|
||||||
|
"joi": "^14.3.1",
|
||||||
|
"mongoose": "^5.4.1",
|
||||||
|
"node-fetch": "^2.3.0",
|
||||||
|
"probot": "^7.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"expect": "^23.6.0",
|
||||||
|
"jest": "^22.4.3",
|
||||||
|
"nock": "^10.0.0",
|
||||||
|
"nodemon": "^1.17.2",
|
||||||
|
"smee-client": "^1.0.2",
|
||||||
|
"standard": "^10.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 8.3.0"
|
||||||
|
},
|
||||||
|
"standard": {
|
||||||
|
"env": [
|
||||||
|
"jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nodemonConfig": {
|
||||||
|
"exec": "npm start",
|
||||||
|
"watch": [
|
||||||
|
".env",
|
||||||
|
"."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"testEnvironment": "node"
|
||||||
|
}
|
||||||
|
}
|
BIN
tools/dashboard/probot/public/fonts/Lato-Bold.ttf
Normal file
BIN
tools/dashboard/probot/public/fonts/Lato-Bold.ttf
Normal file
Binary file not shown.
BIN
tools/dashboard/probot/public/fonts/Lato-Light.ttf
Normal file
BIN
tools/dashboard/probot/public/fonts/Lato-Light.ttf
Normal file
Binary file not shown.
BIN
tools/dashboard/probot/public/fonts/Lato-Regular.ttf
Normal file
BIN
tools/dashboard/probot/public/fonts/Lato-Regular.ttf
Normal file
Binary file not shown.
145
tools/dashboard/probot/public/index.html
Normal file
145
tools/dashboard/probot/public/index.html
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<meta content="X5tHeKjV-jMLyp4VMoUhW9PAYaOjtPslV250" name="csrf-token">
|
||||||
|
<title>Contribute to freeCodeCamp | Learn to code with free online courses, programming projects, and interview preparation for developer jobs.</title>
|
||||||
|
<link href="https://www.freecodecamp.org" rel="canonical">
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta content="IE=edge" http-equiv="X-UA-Compatible">
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport">
|
||||||
|
<meta content="X5tHeKjV-jMLyp4VMoUhW9PAYaOjtPslV250" name="csrf-token">
|
||||||
|
<meta content="Contribute to freeCodeCamp | Learn to code with free online courses, programming projects, and interview preparation for developer jobs." property="og:title">
|
||||||
|
<meta content="freeCodeCamp" property="og:site_name">
|
||||||
|
<meta content="on" name="twitter:widgets:csp">
|
||||||
|
<meta content="d0bc047a482c03c24f1168004c2a216a" name="p:domain_verify">
|
||||||
|
<meta content="https://www.freecodecamp.org" property="og:url">
|
||||||
|
<meta content="Learn to code with free online courses, programming projects, and interview preparation for developer jobs." property="og:description">
|
||||||
|
<meta content="https://s3.amazonaws.com/freecodecamp/curriculum-diagram-full.jpg" property="og:image">
|
||||||
|
<meta content="article" property="og:type">
|
||||||
|
<meta content="https://www.facebook.com/freecodecamp" property="article:publisher">
|
||||||
|
<meta content="Responsive" property="article:section">
|
||||||
|
<link href="https://plus.google.com/+Freecodecamp" rel="publisher">
|
||||||
|
<link href="https://plus.google.com/+Freecodecamp" rel="author">
|
||||||
|
<meta content="Learn to code with free online courses, programming projects, and interview preparation for developer jobs." name="description">
|
||||||
|
<meta content="@freecodecamp" name="twitter:creator">
|
||||||
|
<meta content="https://www.freecodecamp.org" name="twitter:url">
|
||||||
|
<meta content="@freecodecamp" name="twitter:site">
|
||||||
|
<meta content="summary_large_image" name="twitter:card">
|
||||||
|
<meta content="https://s3.amazonaws.com/freecodecamp/curriculum-diagram-full.jpg" name="twitter:image:src">
|
||||||
|
<meta content="Learn to code and help nonprofits" name="twitter:title">
|
||||||
|
<meta content="Learn to code with free online courses, programming projects, and interview preparation for developer jobs." name="twitter:description">
|
||||||
|
<meta content="a40ee5d5dba3bb091ad783ebd2b1383f" name="p:domain_verify">
|
||||||
|
<meta content="#FFFFFF" name="msapplication-TileColor">
|
||||||
|
<meta content="/" name="msapplication-TileImage">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/android-chrome-144x144.png" rel="android-chrome" sizes="144x144">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/android-chrome-192x192.png" rel="android-chrome" sizes="192x192">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/android-chrome-36x36.png" rel="android-chrome" sizes="36x36">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/android-chrome-48x48.png" rel="android-chrome" sizes="48x48">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/android-chrome-72x72.png" rel="android-chrome" sizes="72x72">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/android-chrome-96x96.png" rel="android-chrome" sizes="96x96">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/android-chrome-manifest.json" rel="android-chrome-manifest">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-114x114.png" rel="apple-touch-icon" sizes="114x114">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-120x120.png" rel="apple-touch-icon" sizes="120x120">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-144x144.png" rel="apple-touch-icon" sizes="144x144">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-152x152.png" rel="apple-touch-icon" sizes="152x152">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-180x180.png" rel="apple-touch-icon" sizes="180x180">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-57x57.png" rel="apple-touch-icon" sizes="57x57">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-60x60.png" rel="apple-touch-icon" sizes="60x60">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-72x72.png" rel="apple-touch-icon" sizes="72x72">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-76x76.png" rel="apple-touch-icon" sizes="76x76">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon-precomposed.png" rel="apple-touch-icon-precomposed">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/apple-touch-icon.png" rel="apple-touch-icon">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/favicon-16x16.png" rel="favicon" sizes="16x16">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/favicon-32x32.png" rel="favicon" sizes="32x32">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/favicon-96x96.png" rel="favicon" sizes="96x96">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-144x144.png" rel="mstile" sizes="144x144">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-150x150.png" rel="mstile" sizes="150x150">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-310x310.png" rel="mstile" sizes="310x310">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-310x150.png" rel="mstile" sizes="310x150">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/mstile-70x70.png" rel="mstile" sizes="70x70">
|
||||||
|
<link href="https://s3.amazonaws.com/freecodecamp/favicons/favicon.ico" rel="favicon">
|
||||||
|
<link href="//s3.amazonaws.com/freecodecamp/favicons/favicon.ico" rel="shortcut icon">
|
||||||
|
<link rel='stylesheet' href='https://d33wubrfki0l68.cloudfront.net/css/1d4e5eca38a921b4701240a97afaf40185de57c2/style.css'>
|
||||||
|
<link href="./style.css" rel="stylesheet">
|
||||||
|
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||||
|
</head>
|
||||||
|
<body class="top-and-bottom-margins">
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js">
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
(function(i,s,o,g,r,a,m){ i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
||||||
|
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||||
|
ga('create', 'UA-55446531-1', 'auto');
|
||||||
|
ga('require', 'displayfeatures');
|
||||||
|
ga('send', 'pageview');
|
||||||
|
</script><!-- Leave the below lines alone!-->
|
||||||
|
<nav class="navbar navbar-default navbar-fixed-top nav-height">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand" href="https://www.freecodecamp.org"><img alt="learn to code javascript at freeCodeCamp logo" class="img-responsive nav-logo" src="https://s3.amazonaws.com/freecodecamp/freecodecamp_logo.svg"></a>
|
||||||
|
</div>
|
||||||
|
<div class="return-to-free-code-camp">
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li class="return-to-free-code-camp">
|
||||||
|
<a href="/dashboard">Dashboard</a>
|
||||||
|
</li>
|
||||||
|
<li class="return-to-free-code-camp">
|
||||||
|
<a href="https://github.com/freeCodeCamp/freeCodeCamp/" target="_blank" rel="noopener noreferrer">GitHub</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row flashMessage negative-30">
|
||||||
|
<div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row main-content">
|
||||||
|
<div class="col-xs-12 col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2">
|
||||||
|
<h1 class="text-center">Contribute to the Community</h1>
|
||||||
|
<hr>
|
||||||
|
<h4>freeCodeCamp is possible thanks to thousands of kind volunteers like you.</p>
|
||||||
|
<h4>Here are 5 fun quick ways you can help the community right now:</p>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<table class="table link-table">
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><i class="fa fa-question"></i></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.freecodecamp.org/forum/" target="_blank" rel="noopener noreferrer">Help campers by answering their questions</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><i class="fa fa-comments"></i></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.freecodecamp.org/forum/c/project-feedback/" target="_blank" rel="noopener noreferrer">Give feedback on camper projects</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><i class="fa fa-youtube"></i></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://www.youtube.com/timedtext_cs_panel?c=UC8butISFwT-Wl7EV0hUK0BQ&tab=2" rel="noopener noreferrer"target="_blank">Add subtitles to YouTube videos</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><i class="fa fa-book"></i></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://github.com/freeCodeCamp/freeCodeCamp/blob/master/CONTRIBUTING.md#research-write-and-update-our-guide-articles" rel="noopener noreferrer" target="_blank">Research and write guide articles</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><i class="fa fa-github"></i></td>
|
||||||
|
<td>
|
||||||
|
<a href="https://github.com/freeCodeCamp/freeCodeCamp/blob/master/CONTRIBUTING.md" rel="noopener noreferrer" target="_blank">Contribute to the codebase</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
11
tools/dashboard/probot/public/style.css
Normal file
11
tools/dashboard/probot/public/style.css
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
@media (max-width: 991px) {
|
||||||
|
.main-content {
|
||||||
|
margin-top: 65px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.return-to-free-code-camp {
|
||||||
|
padding: 0!important;
|
||||||
|
margin: -9px 0!important;
|
||||||
|
height: 50px!important;
|
||||||
|
}
|
17
tools/dashboard/probot/sample.env
Normal file
17
tools/dashboard/probot/sample.env
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
GITHUB_USERNAME='camperbot'
|
||||||
|
GITHUB_ACCESS_TOKEN=
|
||||||
|
REPOSITORY_OWNER='freeCodeCamp'
|
||||||
|
REPOSITORY='freeCodeCamp'
|
||||||
|
PORT=3001
|
||||||
|
MONGO_HOST=mongodb://localhost/contributor-tools
|
||||||
|
MONGO_PORT=27017
|
||||||
|
PRODUCTION_RUN=false
|
||||||
|
|
||||||
|
# PROBOT
|
||||||
|
# The ID of your GitHub App
|
||||||
|
APP_ID=
|
||||||
|
# Go to https://smee.io/new set this to the URL that you are redirected to.
|
||||||
|
WEBHOOK_PROXY_URL=
|
||||||
|
WEBHOOK_SECRET=
|
||||||
|
# Use `trace` to get verbose logging or `info` to show less
|
||||||
|
LOG_LEVEL=info
|
24
tools/dashboard/probot/server/models/index.js
Normal file
24
tools/dashboard/probot/server/models/index.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const pr = new mongoose.Schema({
|
||||||
|
_id: Number,
|
||||||
|
updatedAt: String,
|
||||||
|
username: String,
|
||||||
|
title: String,
|
||||||
|
filenames: [String]
|
||||||
|
});
|
||||||
|
|
||||||
|
const info = new mongoose.Schema({
|
||||||
|
lastUpdate: Date,
|
||||||
|
numPRs: Number,
|
||||||
|
prRange: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbCollections = {
|
||||||
|
pr: 'openprs',
|
||||||
|
info: 'info'
|
||||||
|
};
|
||||||
|
|
||||||
|
const PR = mongoose.model('PR', pr, dbCollections['pr']);
|
||||||
|
const INFO = mongoose.model('INFO', info, dbCollections['info']);
|
||||||
|
module.exports = { PR, INFO, dbCollections };
|
113
tools/dashboard/probot/server/presolver.js
Normal file
113
tools/dashboard/probot/server/presolver.js
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// const { updateDb } = require('./tools/update-db');
|
||||||
|
|
||||||
|
class Presolver {
|
||||||
|
constructor(context, { owner, repo, logger = console, ...config }) {
|
||||||
|
this.context = context;
|
||||||
|
this.github = context.github;
|
||||||
|
this.logger = logger;
|
||||||
|
this.config = {
|
||||||
|
...require('../../lib/defaults'),
|
||||||
|
...(config || {}),
|
||||||
|
...{
|
||||||
|
owner,
|
||||||
|
repo
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.pullRequest = {};
|
||||||
|
this.conflictingFiles = [];
|
||||||
|
// this._updateDb = updateDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
async presolve(pullRequest) {
|
||||||
|
Object.assign(this.pullRequest, pullRequest);
|
||||||
|
// await this._updateDb(this.context);
|
||||||
|
await this._ensurePresolverLabelExists();
|
||||||
|
await this._getState();
|
||||||
|
const labelObj = this.config.labelPRConflict;
|
||||||
|
if (this.conflictingFiles.length) {
|
||||||
|
await this._addLabel(labelObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getState() {
|
||||||
|
// console.log(this.context.issue())
|
||||||
|
const files = await this.github.pullRequests.listFiles(this.context.issue());
|
||||||
|
// console.log(files)
|
||||||
|
const { owner, repo } = this.config;
|
||||||
|
const prs =
|
||||||
|
(await this.github.pullRequests.list({ owner, repo }).data) || [];
|
||||||
|
// console.log(prs)
|
||||||
|
await this._getConflictingFiles(prs, files);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _getConflictingFiles(prs, files) {
|
||||||
|
const { owner, repo } = this.config;
|
||||||
|
const github = this.github;
|
||||||
|
const conflictingFiles = this.conflictingFiles;
|
||||||
|
// console.log(prs, files)
|
||||||
|
prs.forEach(pr => {
|
||||||
|
const prIssue = {
|
||||||
|
number: pr.number,
|
||||||
|
owner: owner,
|
||||||
|
repo: repo
|
||||||
|
};
|
||||||
|
const prFiles = github.pullRequests.listFiles(prIssue);
|
||||||
|
prFiles.data.forEach(file => {
|
||||||
|
files.data.forEach(f => {
|
||||||
|
if (f.filename === file.filename) {
|
||||||
|
conflictingFiles.push(file.filename);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _ensurePresolverLabelExists() {
|
||||||
|
const label = this.config.labelPRConflict;
|
||||||
|
await this._createLabel(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _createLabel(labelObj) {
|
||||||
|
const { owner, repo } = this.config;
|
||||||
|
const github = this.github;
|
||||||
|
return this.github.issues
|
||||||
|
.getLabel({ owner, repo, name: labelObj.name })
|
||||||
|
.catch(() => {
|
||||||
|
return github.issues.createLabel({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
name: labelObj.name,
|
||||||
|
color: labelObj.color
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_getLabel(labelObj) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
for (const label of this.pullRequest.labels) {
|
||||||
|
if (labelObj && labelObj.name && label.name === labelObj.name) {
|
||||||
|
resolve(labelObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reject(new Error('Not found'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async _addLabel(labelObj) {
|
||||||
|
const { owner, repo } = this.config;
|
||||||
|
const number = this.pullRequest.number;
|
||||||
|
const label = this.config.labelPRConflict;
|
||||||
|
const github = this.github;
|
||||||
|
// Check if a label does not exist. If it does, it addes the label.
|
||||||
|
return this._getLabel(label).catch(() => {
|
||||||
|
// console.log(labelObj)
|
||||||
|
return github.issues.addLabels({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
number,
|
||||||
|
labels: [labelObj.name]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Presolver;
|
6
tools/dashboard/probot/server/routes/index.js
Normal file
6
tools/dashboard/probot/server/routes/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
const pareto = require('./pareto');
|
||||||
|
const pr = require('./pr');
|
||||||
|
const search = require('./search');
|
||||||
|
const info = require('./info');
|
||||||
|
|
||||||
|
module.exports = { pareto, pr, search, info };
|
9
tools/dashboard/probot/server/routes/info.js
Normal file
9
tools/dashboard/probot/server/routes/info.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const router = require('express').Router();
|
||||||
|
const { INFO } = require('../models');
|
||||||
|
|
||||||
|
router.get('/', async (request, response) => {
|
||||||
|
const [{ lastUpdate, numPRs, prRange }] = await INFO.find({});
|
||||||
|
response.json({ ok: true, lastUpdate, numPRs, prRange });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
37
tools/dashboard/probot/server/routes/pareto.js
Normal file
37
tools/dashboard/probot/server/routes/pareto.js
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
const router = require('express').Router();
|
||||||
|
const { PR } = require('../models');
|
||||||
|
|
||||||
|
const createPareto = reportObj =>
|
||||||
|
Object.keys(reportObj)
|
||||||
|
.reduce((arr, filename) => {
|
||||||
|
const { count, prs } = reportObj[filename];
|
||||||
|
if (count > 1) {
|
||||||
|
arr.push({ filename, count, prs });
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}, [])
|
||||||
|
.sort((a, b) => b.count - a.count);
|
||||||
|
|
||||||
|
router.get('/', async (reqeust, response) => {
|
||||||
|
const prs = await PR.find({}).then(data => data);
|
||||||
|
prs.sort((a, b) => a._id - b._id);
|
||||||
|
const reportObj = prs.reduce((obj, pr) => {
|
||||||
|
const { _id: number, filenames, username, title } = pr;
|
||||||
|
filenames.forEach(filename => {
|
||||||
|
if (obj[filename]) {
|
||||||
|
const { count, prs } = obj[filename];
|
||||||
|
obj[filename] = {
|
||||||
|
count: count + 1,
|
||||||
|
prs: prs.concat({ number, username, title })
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
obj[filename] = { count: 1, prs: [{ number, username, title }] };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
response.json({ ok: true, pareto: createPareto(reportObj) });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
46
tools/dashboard/probot/server/routes/pr.js
Normal file
46
tools/dashboard/probot/server/routes/pr.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
const router = require('express').Router();
|
||||||
|
const { PR } = require('../models');
|
||||||
|
|
||||||
|
router.get('/:number', async (request, response) => {
|
||||||
|
const prs = await PR.find({}).then(data => data);
|
||||||
|
prs.sort((a, b) => a._id - b._id);
|
||||||
|
const indices = prs.reduce((obj, { _id }, index) => {
|
||||||
|
obj[_id] = index;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
const { number: refNumber } = request.params;
|
||||||
|
const index = indices[refNumber];
|
||||||
|
|
||||||
|
if (!index && index !== 0) {
|
||||||
|
response.json({
|
||||||
|
ok: true,
|
||||||
|
message: `Unable to find an open PR with #${refNumber}.`,
|
||||||
|
results: []
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pr = prs[index];
|
||||||
|
const results = [];
|
||||||
|
const { filenames: refFilenames } = pr;
|
||||||
|
|
||||||
|
prs.forEach(({ _id: number, filenames, username, title }) => {
|
||||||
|
if (number !== +refNumber) {
|
||||||
|
const matchedFilenames = filenames.filter(filename => {
|
||||||
|
return refFilenames.includes(filename);
|
||||||
|
});
|
||||||
|
if (matchedFilenames.length) {
|
||||||
|
results.push({ number, filenames: matchedFilenames, username, title });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!results.length) {
|
||||||
|
let msg = `No other open PRs with matching filenames of PR #${refNumber}`;
|
||||||
|
response.json({ ok: true, message: msg, results: [] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.json({ ok: true, results });
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
16
tools/dashboard/probot/server/routes/probot.js
Normal file
16
tools/dashboard/probot/server/routes/probot.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const router = require('express').Router();
|
||||||
|
const shell = require('shelljs');
|
||||||
|
|
||||||
|
router.get('/', (request, response) => {
|
||||||
|
// not working yet
|
||||||
|
shell
|
||||||
|
.exec('npm run presolver')
|
||||||
|
.then(function() {
|
||||||
|
return response.status(200).send('ok');
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
47
tools/dashboard/probot/server/routes/search.js
Normal file
47
tools/dashboard/probot/server/routes/search.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const router = require('express').Router();
|
||||||
|
const { PR } = require('../models');
|
||||||
|
|
||||||
|
router.get('/', async (request, response) => {
|
||||||
|
const prs = await PR.find({}).then(data => data);
|
||||||
|
prs.sort((a, b) => a._id - b._id);
|
||||||
|
const indices = prs.reduce((obj, { _id }, index) => {
|
||||||
|
obj[_id] = index;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
const value = request.query.value;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
const filesFound = {};
|
||||||
|
prs.forEach(({ _id: number, filenames, username, title }) => {
|
||||||
|
filenames.forEach(filename => {
|
||||||
|
if (filename.toLowerCase().includes(value.toLowerCase())) {
|
||||||
|
const fileCount = prs[indices[number]].filenames.length;
|
||||||
|
const prObj = { number, fileCount, username, title };
|
||||||
|
|
||||||
|
if (filesFound.hasOwnProperty(filename)) {
|
||||||
|
filesFound[filename].push(prObj);
|
||||||
|
} else {
|
||||||
|
filesFound[filename] = [prObj];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let results = Object.keys(filesFound)
|
||||||
|
.map(filename => ({ filename, prs: filesFound[filename] }))
|
||||||
|
.sort((a, b) => {
|
||||||
|
if (a.filename === b.filename) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return a.filename < b.filename ? -1 : 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!results.length) {
|
||||||
|
response.json({ ok: true, message: 'No matching results.', results: [] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.json({ ok: true, results });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
99
tools/dashboard/probot/server/tools/update-db.js
Normal file
99
tools/dashboard/probot/server/tools/update-db.js
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
const config = require('../../../config');
|
||||||
|
// config should be imported before importing any other file
|
||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
// added to prevent deprecation warning when findOneAndUpdate is used
|
||||||
|
mongoose.set('useFindAndModify', false);
|
||||||
|
|
||||||
|
// connect to mongo db
|
||||||
|
const mongoUri = config.mongo.host;
|
||||||
|
const db = mongoose.connect(
|
||||||
|
mongoUri,
|
||||||
|
{ useNewUrlParser: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
const { PR, INFO } = require('../models');
|
||||||
|
const { getPRs, getUserInput, getFilenames } = require('../../../lib/get-prs');
|
||||||
|
const { rateLimiter } = require('../../../lib/utils');
|
||||||
|
|
||||||
|
const lastUpdate = new Date();
|
||||||
|
|
||||||
|
db.then(async () => {
|
||||||
|
const oldPRs = await PR.find({}).then(data => data);
|
||||||
|
const oldIndices = oldPRs.reduce((obj, { _id }, index) => {
|
||||||
|
obj[_id] = index;
|
||||||
|
return obj;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const { totalPRs, firstPR, lastPR } = await getUserInput('all');
|
||||||
|
const prPropsToGet = ['number', 'user', 'title', 'updated_at'];
|
||||||
|
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||||
|
let count = 0;
|
||||||
|
const newIndices = {};
|
||||||
|
for (let i = 0; i < openPRs.length; i++) {
|
||||||
|
const {
|
||||||
|
number,
|
||||||
|
updated_at: updatedAt,
|
||||||
|
title,
|
||||||
|
user: { login: username }
|
||||||
|
} = openPRs[i];
|
||||||
|
|
||||||
|
newIndices[number] = i;
|
||||||
|
let oldPrData = oldPRs[oldIndices[number]];
|
||||||
|
const oldUpdatedAt = oldPrData ? oldPrData.updatedAt : null;
|
||||||
|
if (!oldIndices.hasOwnProperty(number)) {
|
||||||
|
// insert a new pr
|
||||||
|
const filenames = await getFilenames(number);
|
||||||
|
count++;
|
||||||
|
await PR.create({ _id: number, updatedAt, title, username, filenames });
|
||||||
|
console.log('added PR# ' + number);
|
||||||
|
} else if (updatedAt > oldUpdatedAt) {
|
||||||
|
// update an existing pr
|
||||||
|
const filenames = await getFilenames(number);
|
||||||
|
count++;
|
||||||
|
await PR.findOneAndUpdate(
|
||||||
|
{ _id: number },
|
||||||
|
{ updatedAt, title, username, filenames }
|
||||||
|
);
|
||||||
|
console.log('updated PR #' + number);
|
||||||
|
}
|
||||||
|
if (count > 4500) {
|
||||||
|
await rateLimiter(4500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let j = 0; j < oldPRs.length; j++) {
|
||||||
|
const { _id: number } = oldPRs[j];
|
||||||
|
if (!newIndices.hasOwnProperty(number)) {
|
||||||
|
// delete pr because it is no longer open
|
||||||
|
await PR.deleteOne({ _id: number });
|
||||||
|
console.log('deleted PR #' + number);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(async () => {
|
||||||
|
// update info collection
|
||||||
|
const [ { firstPR, lastPR }] = await PR.aggregate(
|
||||||
|
[{
|
||||||
|
$group: {
|
||||||
|
_id: null,
|
||||||
|
firstPR: { $min: '$_id' },
|
||||||
|
lastPR: { $max: '$_id' }
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
const numPRs = await PR.countDocuments();
|
||||||
|
const info = {
|
||||||
|
lastUpdate,
|
||||||
|
numPRs,
|
||||||
|
prRange: `${firstPR}-${lastPR}`
|
||||||
|
};
|
||||||
|
await INFO.updateOne({}, info, { upsert: true })
|
||||||
|
.catch(err => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
mongoose.connection.close();
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
mongoose.connection.close();
|
||||||
|
throw err;
|
||||||
|
});
|
149
tools/dashboard/probot/test/index.test.js
Normal file
149
tools/dashboard/probot/test/index.test.js
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
const expect = require('expect');
|
||||||
|
const { Probot } = require('probot');
|
||||||
|
const prOpened = require('./payloads/events/pullRequests.opened');
|
||||||
|
const prExisting = require('./payloads/events/pullRequests.existing');
|
||||||
|
const prUnrelated = require('./payloads/events/pullRequests.unrelated');
|
||||||
|
const prClosed = require('./payloads/events/pullRequests.closed');
|
||||||
|
const prOpenedFiles = require('./payloads/files/files.opened');
|
||||||
|
const prExistingFiles = require('./payloads/files/files.existing');
|
||||||
|
const prUnrelatedFiles = require('./payloads/files/files.unrelated');
|
||||||
|
const probotPlugin = require('..');
|
||||||
|
const { PRtest } = require('./utils/testmodels');
|
||||||
|
// const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
describe('Presolver', () => {
|
||||||
|
let probot, github;
|
||||||
|
|
||||||
|
afterEach(async (done) => {
|
||||||
|
await PRtest.deleteMany({}).catch(err => console.log(err));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach( async() => {
|
||||||
|
|
||||||
|
probot = new Probot({});
|
||||||
|
// Load our app into probot
|
||||||
|
let app = await probot.load(probotPlugin);
|
||||||
|
await PRtest.deleteMany({}).catch(err => console.log(err));
|
||||||
|
// This is an easy way to mock out the GitHub API
|
||||||
|
// https://probot.github.io/docs/testing/
|
||||||
|
github = {
|
||||||
|
issues: {
|
||||||
|
/* eslint-disable no-undef */
|
||||||
|
createComment: jest.fn().mockReturnValue(Promise.resolve({})),
|
||||||
|
addLabels: jest.fn(),
|
||||||
|
getLabel: jest.fn().mockImplementation(() => Promise.resolve([])),
|
||||||
|
createLabel: jest.fn()
|
||||||
|
/* eslint-enable no-undef */
|
||||||
|
},
|
||||||
|
repos: {
|
||||||
|
getContent: () =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: Buffer.from(
|
||||||
|
`
|
||||||
|
issueOpened: Message
|
||||||
|
pullRequestOpened: Message
|
||||||
|
`
|
||||||
|
).toString('base64')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
pullRequests: {
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
listFiles: jest.fn().mockImplementation((issue) => {
|
||||||
|
const { number } = issue;
|
||||||
|
let data;
|
||||||
|
switch (number) {
|
||||||
|
case 31525:
|
||||||
|
data = prOpenedFiles;
|
||||||
|
break;
|
||||||
|
case 33363:
|
||||||
|
data = prExistingFiles;
|
||||||
|
break;
|
||||||
|
case 34559:
|
||||||
|
data = prUnrelatedFiles;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
data = prExistingFiles;
|
||||||
|
}
|
||||||
|
return { data };
|
||||||
|
}),
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
list: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => ({ data: [
|
||||||
|
prExisting.pull_request
|
||||||
|
] }))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
app.auth = () => Promise.resolve(github);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(`adds a label if a PR has changes to files targeted by an
|
||||||
|
existing PR`, async () => {
|
||||||
|
// Receive a webhook event
|
||||||
|
await probot.receive({
|
||||||
|
name: 'pull_request.opened',
|
||||||
|
payload: prOpened
|
||||||
|
});
|
||||||
|
expect(github.issues.addLabels).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not add a label when files do not coincide', async () => {
|
||||||
|
await probot.receive({
|
||||||
|
name: 'pull_request.opened',
|
||||||
|
payload: prUnrelated
|
||||||
|
});
|
||||||
|
expect(github.issues.addLabels).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('db should update if the action is opened', async () => {
|
||||||
|
await probot.receive({
|
||||||
|
name: 'pull_request.opened',
|
||||||
|
payload: prOpened
|
||||||
|
});
|
||||||
|
const results = await PRtest.find({}).then(data => data);
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('db should update if the action is reopened', async () => {
|
||||||
|
await probot.receive({
|
||||||
|
name: 'pull_request.reopened',
|
||||||
|
payload: prOpened
|
||||||
|
});
|
||||||
|
const results = await PRtest.find({}).then(data => data);
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('db should update if the action is synchronize', async () => {
|
||||||
|
await probot.receive({
|
||||||
|
name: 'pull_request.synchronize',
|
||||||
|
payload: prOpened
|
||||||
|
});
|
||||||
|
const results = await PRtest.find({}).then(data => data);
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('db should update if the action is edited', async () => {
|
||||||
|
await probot.receive({
|
||||||
|
name: 'pull_request.edited',
|
||||||
|
payload: prOpened
|
||||||
|
});
|
||||||
|
const results = await PRtest.find({}).then(data => data);
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('db should have removed document if action is closed', async () => {
|
||||||
|
await probot.receive({
|
||||||
|
name: 'pull_request.closed',
|
||||||
|
payload: prClosed
|
||||||
|
});
|
||||||
|
const result = await PRtest.findOne(
|
||||||
|
{ _id: prClosed.number }).then(doc => doc)
|
||||||
|
.catch(err => console.log(err));
|
||||||
|
expect(result).toBe(null);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// For more information about testing with Jest see:
|
||||||
|
// https://facebook.github.io/jest/
|
@ -0,0 +1,473 @@
|
|||||||
|
{
|
||||||
|
"action": "closed",
|
||||||
|
"number": 33363,
|
||||||
|
"pull_request": {
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363",
|
||||||
|
"id": 227227572,
|
||||||
|
"node_id": "MDExOlB1bGxSZXF1ZXN0MjI3MjI3NTcy",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/33363",
|
||||||
|
"diff_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/33363.diff",
|
||||||
|
"patch_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/33363.patch",
|
||||||
|
"issue_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/33363",
|
||||||
|
"number": 33363,
|
||||||
|
"state": "closed",
|
||||||
|
"locked": false,
|
||||||
|
"title": "Add the text \"16\"",
|
||||||
|
"user": {
|
||||||
|
"login": "fredydev",
|
||||||
|
"id": 9504948,
|
||||||
|
"node_id": "MDQ6VXNlcjk1MDQ5NDg=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9504948?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/fredydev",
|
||||||
|
"html_url": "https://github.com/fredydev",
|
||||||
|
"followers_url": "https://api.github.com/users/fredydev/followers",
|
||||||
|
"following_url": "https://api.github.com/users/fredydev/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/fredydev/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/fredydev/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/fredydev/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/fredydev/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/fredydev/repos",
|
||||||
|
"events_url": "https://api.github.com/users/fredydev/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/fredydev/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"body": "<!-- Please follow this checklist and put an x in each of the boxes, like this: [x]. It will ensure that our team takes your pull request seriously. -->\r\n\r\n- [x] I have read [freeCodeCamp's contribution guidelines](https://github.com/freeCodeCamp/freeCodeCamp/blob/master/CONTRIBUTING.md).\r\n- [x] My pull request has a descriptive title (not a vague title like `Update index.md`)\r\n- [x] My pull request targets the `master` branch of freeCodeCamp.\r\n- [x] None of my changes are plagiarized from another source without proper attribution.\r\n- [x] My article does not contain shortened URLs or affiliate links.\r\n\r\nIf your pull request closes a GitHub issue, replace the XXXXX below with the issue number.\r\n\r\nCloses #XXXXX\r\n",
|
||||||
|
"created_at": "2018-10-31T10:15:23Z",
|
||||||
|
"updated_at": "2018-11-13T03:49:07Z",
|
||||||
|
"closed_at": null,
|
||||||
|
"merged_at": null,
|
||||||
|
"merge_commit_sha": "84a7a48d2a90f79fa05eb32d0bff9b0df1d7fe3b",
|
||||||
|
"assignee": null,
|
||||||
|
"assignees": [],
|
||||||
|
"requested_reviewers": [],
|
||||||
|
"requested_teams": [],
|
||||||
|
"labels": [
|
||||||
|
{
|
||||||
|
"id": 1088400742,
|
||||||
|
"node_id": "MDU6TGFiZWwxMDg4NDAwNzQy",
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels/scope:%20guide",
|
||||||
|
"name": "scope: guide",
|
||||||
|
"color": "9631e2",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"milestone": null,
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363/commits",
|
||||||
|
"review_comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363/comments",
|
||||||
|
"review_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/comments{/number}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/33363/comments",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b",
|
||||||
|
"head": {
|
||||||
|
"label": "fredydev:patch-1",
|
||||||
|
"ref": "patch-1",
|
||||||
|
"sha": "ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b",
|
||||||
|
"user": {
|
||||||
|
"login": "fredydev",
|
||||||
|
"id": 9504948,
|
||||||
|
"node_id": "MDQ6VXNlcjk1MDQ5NDg=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9504948?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/fredydev",
|
||||||
|
"html_url": "https://github.com/fredydev",
|
||||||
|
"followers_url": "https://api.github.com/users/fredydev/followers",
|
||||||
|
"following_url": "https://api.github.com/users/fredydev/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/fredydev/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/fredydev/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/fredydev/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/fredydev/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/fredydev/repos",
|
||||||
|
"events_url": "https://api.github.com/users/fredydev/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/fredydev/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"id": 155534904,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkxNTU1MzQ5MDQ=",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "fredydev/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "fredydev",
|
||||||
|
"id": 9504948,
|
||||||
|
"node_id": "MDQ6VXNlcjk1MDQ5NDg=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9504948?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/fredydev",
|
||||||
|
"html_url": "https://github.com/fredydev",
|
||||||
|
"followers_url": "https://api.github.com/users/fredydev/followers",
|
||||||
|
"following_url": "https://api.github.com/users/fredydev/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/fredydev/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/fredydev/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/fredydev/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/fredydev/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/fredydev/repos",
|
||||||
|
"events_url": "https://api.github.com/users/fredydev/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/fredydev/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/fredydev/freeCodeCamp",
|
||||||
|
"description": "The https://freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": true,
|
||||||
|
"url": "https://api.github.com/repos/fredydev/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/fredydev/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/fredydev/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/fredydev/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/fredydev/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/fredydev/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/fredydev/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/fredydev/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/fredydev/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/fredydev/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/fredydev/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/fredydev/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/fredydev/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/fredydev/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/fredydev/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/fredydev/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/fredydev/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/fredydev/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/fredydev/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/fredydev/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/fredydev/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/fredydev/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/fredydev/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/fredydev/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/fredydev/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/fredydev/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/fredydev/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/fredydev/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/fredydev/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/fredydev/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/fredydev/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/fredydev/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2018-10-31T09:55:52Z",
|
||||||
|
"updated_at": "2018-10-31T09:56:56Z",
|
||||||
|
"pushed_at": "2018-10-31T12:28:21Z",
|
||||||
|
"git_url": "git://github.com/fredydev/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:fredydev/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/fredydev/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/fredydev/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 95184,
|
||||||
|
"stargazers_count": 0,
|
||||||
|
"watchers_count": 0,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": false,
|
||||||
|
"has_projects": true,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 0,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 0,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 0,
|
||||||
|
"open_issues": 0,
|
||||||
|
"watchers": 0,
|
||||||
|
"default_branch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
"label": "freeCodeCamp:master",
|
||||||
|
"ref": "master",
|
||||||
|
"sha": "4a48031353c0b0dfbafe46bfa41a17acc4e2a5ae",
|
||||||
|
"user": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"id": 28457823,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "freeCodeCamp/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"description": "The https://www.freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": false,
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2014-12-24T17:49:19Z",
|
||||||
|
"updated_at": "2019-01-08T17:05:29Z",
|
||||||
|
"pushed_at": "2019-01-08T17:09:22Z",
|
||||||
|
"git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 103712,
|
||||||
|
"stargazers_count": 296829,
|
||||||
|
"watchers_count": 296829,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": true,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 20716,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 5762,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 20716,
|
||||||
|
"open_issues": 5762,
|
||||||
|
"watchers": 296829,
|
||||||
|
"default_branch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_links": {
|
||||||
|
"self": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363"
|
||||||
|
},
|
||||||
|
"html": {
|
||||||
|
"href": "https://github.com/freeCodeCamp/freeCodeCamp/pull/33363"
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/33363"
|
||||||
|
},
|
||||||
|
"comments": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/33363/comments"
|
||||||
|
},
|
||||||
|
"review_comments": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363/comments"
|
||||||
|
},
|
||||||
|
"review_comment": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/comments{/number}"
|
||||||
|
},
|
||||||
|
"commits": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363/commits"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"author_association": "NONE",
|
||||||
|
"merged": false,
|
||||||
|
"mergeable": false,
|
||||||
|
"rebaseable": false,
|
||||||
|
"mergeable_state": "dirty",
|
||||||
|
"merged_by": null,
|
||||||
|
"comments": 0,
|
||||||
|
"review_comments": 0,
|
||||||
|
"maintainer_can_modify": true,
|
||||||
|
"commits": 1,
|
||||||
|
"additions": 1,
|
||||||
|
"deletions": 1,
|
||||||
|
"changed_files": 1
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 28457823,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "freeCodeCamp/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"description": "The https://www.freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": false,
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2014-12-24T17:49:19Z",
|
||||||
|
"updated_at": "2019-01-08T17:05:29Z",
|
||||||
|
"pushed_at": "2019-01-08T17:09:22Z",
|
||||||
|
"git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 103712,
|
||||||
|
"stargazers_count": 296829,
|
||||||
|
"watchers_count": 296829,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": true,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 20716,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 5762,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 20716,
|
||||||
|
"open_issues": 5762,
|
||||||
|
"watchers": 296829,
|
||||||
|
"default_branch": "master"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"login": "tbushman",
|
||||||
|
"id": 7049294,
|
||||||
|
"node_id": "MDQ6VXNlcjcwNDkyOTQ=",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/7049294?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/tbushman",
|
||||||
|
"html_url": "https://github.com/tbushman",
|
||||||
|
"followers_url": "https://api.github.com/users/tbushman/followers",
|
||||||
|
"following_url": "https://api.github.com/users/tbushman/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/tbushman/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/tbushman/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/tbushman/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/tbushman/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/tbushman/repos",
|
||||||
|
"events_url": "https://api.github.com/users/tbushman/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/tbushman/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"installation": {
|
||||||
|
"id": 421598,
|
||||||
|
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,481 @@
|
|||||||
|
{
|
||||||
|
"action": "opened",
|
||||||
|
"number": 31525,
|
||||||
|
"pull_request": {
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/31525",
|
||||||
|
"id": 226420344,
|
||||||
|
"node_id": "MDExOlB1bGxSZXF1ZXN0MjI2NDIwMzQ0",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/31525",
|
||||||
|
"diff_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/31525.diff",
|
||||||
|
"patch_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/31525.patch",
|
||||||
|
"issue_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/31525",
|
||||||
|
"number": 31525,
|
||||||
|
"state": "open",
|
||||||
|
"locked": false,
|
||||||
|
"title": "Fix the duplicate ‘15’ into '16'",
|
||||||
|
"user": {
|
||||||
|
"login": "imdonnie",
|
||||||
|
"id": 42952467,
|
||||||
|
"node_id": "MDQ6VXNlcjQyOTUyNDY3",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/42952467?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/imdonnie",
|
||||||
|
"html_url": "https://github.com/imdonnie",
|
||||||
|
"followers_url": "https://api.github.com/users/imdonnie/followers",
|
||||||
|
"following_url": "https://api.github.com/users/imdonnie/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/imdonnie/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/imdonnie/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/imdonnie/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/imdonnie/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/imdonnie/repos",
|
||||||
|
"events_url": "https://api.github.com/users/imdonnie/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/imdonnie/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"body": "- [x] I have read [freeCodeCamp's contribution guidelines](https://github.com/freeCodeCamp/freeCodeCamp/blob/master/CONTRIBUTING.md).\r\n- [x] My pull request has a descriptive title (not a vague title like `Update index.md`)\r\n- [x] My pull request targets the `master` branch of freeCodeCamp.\r\n- [x] None of my changes are plagiarized from another source without proper attribution.\r\n- [x] My article does not contain shortened URLs or affiliate links.\r\n\r\n",
|
||||||
|
"created_at": "2018-10-29T02:23:02Z",
|
||||||
|
"updated_at": "2018-11-27T17:47:52Z",
|
||||||
|
"closed_at": null,
|
||||||
|
"merged_at": null,
|
||||||
|
"merge_commit_sha": "8d8e905729bbbc2d7561df7e252a1537f60aaff5",
|
||||||
|
"assignee": null,
|
||||||
|
"assignees": [],
|
||||||
|
"requested_reviewers": [],
|
||||||
|
"requested_teams": [],
|
||||||
|
"labels": [
|
||||||
|
{
|
||||||
|
"id": 1088400742,
|
||||||
|
"node_id": "MDU6TGFiZWwxMDg4NDAwNzQy",
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels/scope:%20guide",
|
||||||
|
"name": "scope: guide",
|
||||||
|
"color": "9631e2",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1096429566,
|
||||||
|
"node_id": "MDU6TGFiZWwxMDk2NDI5NTY2",
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels/status:%20merge%20conflict",
|
||||||
|
"name": "status: merge conflict",
|
||||||
|
"color": "4386bc",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"milestone": null,
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/31525/commits",
|
||||||
|
"review_comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/31525/comments",
|
||||||
|
"review_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/comments{/number}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/31525/comments",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/e3b300f24ef8d9aee1eb2677af0b897e8736fcea",
|
||||||
|
"head": {
|
||||||
|
"label": "imdonnie:patch-5",
|
||||||
|
"ref": "patch-5",
|
||||||
|
"sha": "e3b300f24ef8d9aee1eb2677af0b897e8736fcea",
|
||||||
|
"user": {
|
||||||
|
"login": "imdonnie",
|
||||||
|
"id": 42952467,
|
||||||
|
"node_id": "MDQ6VXNlcjQyOTUyNDY3",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/42952467?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/imdonnie",
|
||||||
|
"html_url": "https://github.com/imdonnie",
|
||||||
|
"followers_url": "https://api.github.com/users/imdonnie/followers",
|
||||||
|
"following_url": "https://api.github.com/users/imdonnie/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/imdonnie/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/imdonnie/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/imdonnie/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/imdonnie/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/imdonnie/repos",
|
||||||
|
"events_url": "https://api.github.com/users/imdonnie/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/imdonnie/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"id": 153862987,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkxNTM4NjI5ODc=",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "imdonnie/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "imdonnie",
|
||||||
|
"id": 42952467,
|
||||||
|
"node_id": "MDQ6VXNlcjQyOTUyNDY3",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/42952467?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/imdonnie",
|
||||||
|
"html_url": "https://github.com/imdonnie",
|
||||||
|
"followers_url": "https://api.github.com/users/imdonnie/followers",
|
||||||
|
"following_url": "https://api.github.com/users/imdonnie/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/imdonnie/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/imdonnie/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/imdonnie/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/imdonnie/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/imdonnie/repos",
|
||||||
|
"events_url": "https://api.github.com/users/imdonnie/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/imdonnie/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/imdonnie/freeCodeCamp",
|
||||||
|
"description": "The https://freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": true,
|
||||||
|
"url": "https://api.github.com/repos/imdonnie/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/imdonnie/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2018-10-20T02:41:07Z",
|
||||||
|
"updated_at": "2018-10-20T02:42:07Z",
|
||||||
|
"pushed_at": "2019-01-04T20:05:18Z",
|
||||||
|
"git_url": "git://github.com/imdonnie/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:imdonnie/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/imdonnie/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/imdonnie/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 98408,
|
||||||
|
"stargazers_count": 0,
|
||||||
|
"watchers_count": 0,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": false,
|
||||||
|
"has_projects": true,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 0,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 0,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 0,
|
||||||
|
"open_issues": 0,
|
||||||
|
"watchers": 0,
|
||||||
|
"default_branch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
"label": "freeCodeCamp:master",
|
||||||
|
"ref": "master",
|
||||||
|
"sha": "f3c601ee16838fdd94fd828c1dbd88d387fc603d",
|
||||||
|
"user": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"id": 28457823,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "freeCodeCamp/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"description": "The https://www.freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": false,
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2014-12-24T17:49:19Z",
|
||||||
|
"updated_at": "2019-01-08T17:05:29Z",
|
||||||
|
"pushed_at": "2019-01-08T17:09:22Z",
|
||||||
|
"git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 103712,
|
||||||
|
"stargazers_count": 296829,
|
||||||
|
"watchers_count": 296829,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": true,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 20716,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 5762,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 20716,
|
||||||
|
"open_issues": 5762,
|
||||||
|
"watchers": 296829,
|
||||||
|
"default_branch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_links": {
|
||||||
|
"self": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/31525"
|
||||||
|
},
|
||||||
|
"html": {
|
||||||
|
"href": "https://github.com/freeCodeCamp/freeCodeCamp/pull/31525"
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/31525"
|
||||||
|
},
|
||||||
|
"comments": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/31525/comments"
|
||||||
|
},
|
||||||
|
"review_comments": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/31525/comments"
|
||||||
|
},
|
||||||
|
"review_comment": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/comments{/number}"
|
||||||
|
},
|
||||||
|
"commits": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/31525/commits"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/e3b300f24ef8d9aee1eb2677af0b897e8736fcea"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"author_association": "CONTRIBUTOR",
|
||||||
|
"merged": false,
|
||||||
|
"mergeable": null,
|
||||||
|
"rebaseable": null,
|
||||||
|
"mergeable_state": "unknown",
|
||||||
|
"merged_by": null,
|
||||||
|
"comments": 0,
|
||||||
|
"review_comments": 0,
|
||||||
|
"maintainer_can_modify": true,
|
||||||
|
"commits": 1,
|
||||||
|
"additions": 1,
|
||||||
|
"deletions": 1,
|
||||||
|
"changed_files": 1
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 28457823,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "freeCodeCamp/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"description": "The https://www.freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": false,
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2014-12-24T17:49:19Z",
|
||||||
|
"updated_at": "2019-01-08T17:05:29Z",
|
||||||
|
"pushed_at": "2019-01-08T17:09:22Z",
|
||||||
|
"git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 103712,
|
||||||
|
"stargazers_count": 296829,
|
||||||
|
"watchers_count": 296829,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": true,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 20716,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 5762,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 20716,
|
||||||
|
"open_issues": 5762,
|
||||||
|
"watchers": 296829,
|
||||||
|
"default_branch": "master"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"login": "tbushman",
|
||||||
|
"id": 7049294,
|
||||||
|
"node_id": "MDQ6VXNlcjcwNDkyOTQ=",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/7049294?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/tbushman",
|
||||||
|
"html_url": "https://github.com/tbushman",
|
||||||
|
"followers_url": "https://api.github.com/users/tbushman/followers",
|
||||||
|
"following_url": "https://api.github.com/users/tbushman/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/tbushman/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/tbushman/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/tbushman/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/tbushman/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/tbushman/repos",
|
||||||
|
"events_url": "https://api.github.com/users/tbushman/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/tbushman/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"installation": {
|
||||||
|
"id": 421598,
|
||||||
|
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,473 @@
|
|||||||
|
{
|
||||||
|
"action": "opened",
|
||||||
|
"number": 33363,
|
||||||
|
"pull_request": {
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363",
|
||||||
|
"id": 227227572,
|
||||||
|
"node_id": "MDExOlB1bGxSZXF1ZXN0MjI3MjI3NTcy",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/33363",
|
||||||
|
"diff_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/33363.diff",
|
||||||
|
"patch_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/33363.patch",
|
||||||
|
"issue_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/33363",
|
||||||
|
"number": 33363,
|
||||||
|
"state": "open",
|
||||||
|
"locked": false,
|
||||||
|
"title": "Add the text \"16\"",
|
||||||
|
"user": {
|
||||||
|
"login": "fredydev",
|
||||||
|
"id": 9504948,
|
||||||
|
"node_id": "MDQ6VXNlcjk1MDQ5NDg=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9504948?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/fredydev",
|
||||||
|
"html_url": "https://github.com/fredydev",
|
||||||
|
"followers_url": "https://api.github.com/users/fredydev/followers",
|
||||||
|
"following_url": "https://api.github.com/users/fredydev/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/fredydev/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/fredydev/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/fredydev/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/fredydev/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/fredydev/repos",
|
||||||
|
"events_url": "https://api.github.com/users/fredydev/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/fredydev/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"body": "<!-- Please follow this checklist and put an x in each of the boxes, like this: [x]. It will ensure that our team takes your pull request seriously. -->\r\n\r\n- [x] I have read [freeCodeCamp's contribution guidelines](https://github.com/freeCodeCamp/freeCodeCamp/blob/master/CONTRIBUTING.md).\r\n- [x] My pull request has a descriptive title (not a vague title like `Update index.md`)\r\n- [x] My pull request targets the `master` branch of freeCodeCamp.\r\n- [x] None of my changes are plagiarized from another source without proper attribution.\r\n- [x] My article does not contain shortened URLs or affiliate links.\r\n\r\nIf your pull request closes a GitHub issue, replace the XXXXX below with the issue number.\r\n\r\nCloses #XXXXX\r\n",
|
||||||
|
"created_at": "2018-10-31T10:15:23Z",
|
||||||
|
"updated_at": "2018-11-13T03:49:07Z",
|
||||||
|
"closed_at": null,
|
||||||
|
"merged_at": null,
|
||||||
|
"merge_commit_sha": "84a7a48d2a90f79fa05eb32d0bff9b0df1d7fe3b",
|
||||||
|
"assignee": null,
|
||||||
|
"assignees": [],
|
||||||
|
"requested_reviewers": [],
|
||||||
|
"requested_teams": [],
|
||||||
|
"labels": [
|
||||||
|
{
|
||||||
|
"id": 1088400742,
|
||||||
|
"node_id": "MDU6TGFiZWwxMDg4NDAwNzQy",
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels/scope:%20guide",
|
||||||
|
"name": "scope: guide",
|
||||||
|
"color": "9631e2",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"milestone": null,
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363/commits",
|
||||||
|
"review_comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363/comments",
|
||||||
|
"review_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/comments{/number}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/33363/comments",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b",
|
||||||
|
"head": {
|
||||||
|
"label": "fredydev:patch-1",
|
||||||
|
"ref": "patch-1",
|
||||||
|
"sha": "ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b",
|
||||||
|
"user": {
|
||||||
|
"login": "fredydev",
|
||||||
|
"id": 9504948,
|
||||||
|
"node_id": "MDQ6VXNlcjk1MDQ5NDg=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9504948?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/fredydev",
|
||||||
|
"html_url": "https://github.com/fredydev",
|
||||||
|
"followers_url": "https://api.github.com/users/fredydev/followers",
|
||||||
|
"following_url": "https://api.github.com/users/fredydev/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/fredydev/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/fredydev/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/fredydev/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/fredydev/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/fredydev/repos",
|
||||||
|
"events_url": "https://api.github.com/users/fredydev/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/fredydev/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"id": 155534904,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkxNTU1MzQ5MDQ=",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "fredydev/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "fredydev",
|
||||||
|
"id": 9504948,
|
||||||
|
"node_id": "MDQ6VXNlcjk1MDQ5NDg=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9504948?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/fredydev",
|
||||||
|
"html_url": "https://github.com/fredydev",
|
||||||
|
"followers_url": "https://api.github.com/users/fredydev/followers",
|
||||||
|
"following_url": "https://api.github.com/users/fredydev/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/fredydev/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/fredydev/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/fredydev/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/fredydev/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/fredydev/repos",
|
||||||
|
"events_url": "https://api.github.com/users/fredydev/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/fredydev/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/fredydev/freeCodeCamp",
|
||||||
|
"description": "The https://freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": true,
|
||||||
|
"url": "https://api.github.com/repos/fredydev/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/fredydev/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/fredydev/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/fredydev/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/fredydev/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/fredydev/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/fredydev/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/fredydev/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/fredydev/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/fredydev/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/fredydev/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/fredydev/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/fredydev/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/fredydev/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/fredydev/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/fredydev/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/fredydev/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/fredydev/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/fredydev/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/fredydev/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/fredydev/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/fredydev/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/fredydev/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/fredydev/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/fredydev/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/fredydev/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/fredydev/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/fredydev/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/fredydev/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/fredydev/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/fredydev/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/fredydev/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/fredydev/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2018-10-31T09:55:52Z",
|
||||||
|
"updated_at": "2018-10-31T09:56:56Z",
|
||||||
|
"pushed_at": "2018-10-31T12:28:21Z",
|
||||||
|
"git_url": "git://github.com/fredydev/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:fredydev/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/fredydev/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/fredydev/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 95184,
|
||||||
|
"stargazers_count": 0,
|
||||||
|
"watchers_count": 0,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": false,
|
||||||
|
"has_projects": true,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 0,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 0,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 0,
|
||||||
|
"open_issues": 0,
|
||||||
|
"watchers": 0,
|
||||||
|
"default_branch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
"label": "freeCodeCamp:master",
|
||||||
|
"ref": "master",
|
||||||
|
"sha": "4a48031353c0b0dfbafe46bfa41a17acc4e2a5ae",
|
||||||
|
"user": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"id": 28457823,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "freeCodeCamp/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"description": "The https://www.freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": false,
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2014-12-24T17:49:19Z",
|
||||||
|
"updated_at": "2019-01-08T17:05:29Z",
|
||||||
|
"pushed_at": "2019-01-08T17:09:22Z",
|
||||||
|
"git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 103712,
|
||||||
|
"stargazers_count": 296829,
|
||||||
|
"watchers_count": 296829,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": true,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 20716,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 5762,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 20716,
|
||||||
|
"open_issues": 5762,
|
||||||
|
"watchers": 296829,
|
||||||
|
"default_branch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_links": {
|
||||||
|
"self": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363"
|
||||||
|
},
|
||||||
|
"html": {
|
||||||
|
"href": "https://github.com/freeCodeCamp/freeCodeCamp/pull/33363"
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/33363"
|
||||||
|
},
|
||||||
|
"comments": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/33363/comments"
|
||||||
|
},
|
||||||
|
"review_comments": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363/comments"
|
||||||
|
},
|
||||||
|
"review_comment": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/comments{/number}"
|
||||||
|
},
|
||||||
|
"commits": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/33363/commits"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"author_association": "NONE",
|
||||||
|
"merged": false,
|
||||||
|
"mergeable": false,
|
||||||
|
"rebaseable": false,
|
||||||
|
"mergeable_state": "dirty",
|
||||||
|
"merged_by": null,
|
||||||
|
"comments": 0,
|
||||||
|
"review_comments": 0,
|
||||||
|
"maintainer_can_modify": true,
|
||||||
|
"commits": 1,
|
||||||
|
"additions": 1,
|
||||||
|
"deletions": 1,
|
||||||
|
"changed_files": 1
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 28457823,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "freeCodeCamp/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"description": "The https://www.freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": false,
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2014-12-24T17:49:19Z",
|
||||||
|
"updated_at": "2019-01-08T17:05:29Z",
|
||||||
|
"pushed_at": "2019-01-08T17:09:22Z",
|
||||||
|
"git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 103712,
|
||||||
|
"stargazers_count": 296829,
|
||||||
|
"watchers_count": 296829,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": true,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 20716,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 5762,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 20716,
|
||||||
|
"open_issues": 5762,
|
||||||
|
"watchers": 296829,
|
||||||
|
"default_branch": "master"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"login": "tbushman",
|
||||||
|
"id": 7049294,
|
||||||
|
"node_id": "MDQ6VXNlcjcwNDkyOTQ=",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/7049294?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/tbushman",
|
||||||
|
"html_url": "https://github.com/tbushman",
|
||||||
|
"followers_url": "https://api.github.com/users/tbushman/followers",
|
||||||
|
"following_url": "https://api.github.com/users/tbushman/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/tbushman/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/tbushman/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/tbushman/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/tbushman/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/tbushman/repos",
|
||||||
|
"events_url": "https://api.github.com/users/tbushman/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/tbushman/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"installation": {
|
||||||
|
"id": 421598,
|
||||||
|
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,489 @@
|
|||||||
|
{
|
||||||
|
"action": "opened",
|
||||||
|
"number": 34559,
|
||||||
|
"pull_request": {
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/34559",
|
||||||
|
"id": 236302173,
|
||||||
|
"node_id": "MDExOlB1bGxSZXF1ZXN0MjM2MzAyMTcz",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/34559",
|
||||||
|
"diff_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/34559.diff",
|
||||||
|
"patch_url": "https://github.com/freeCodeCamp/freeCodeCamp/pull/34559.patch",
|
||||||
|
"issue_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/34559",
|
||||||
|
"number": 34559,
|
||||||
|
"state": "open",
|
||||||
|
"locked": false,
|
||||||
|
"title": "Feat/test challenges for all lang",
|
||||||
|
"user": {
|
||||||
|
"login": "ValeraS",
|
||||||
|
"id": 10867286,
|
||||||
|
"node_id": "MDQ6VXNlcjEwODY3Mjg2",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/10867286?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/ValeraS",
|
||||||
|
"html_url": "https://github.com/ValeraS",
|
||||||
|
"followers_url": "https://api.github.com/users/ValeraS/followers",
|
||||||
|
"following_url": "https://api.github.com/users/ValeraS/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/ValeraS/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/ValeraS/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/ValeraS/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/ValeraS/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/ValeraS/repos",
|
||||||
|
"events_url": "https://api.github.com/users/ValeraS/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/ValeraS/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"body": "This PR shows challenge errors for other langs.",
|
||||||
|
"created_at": "2018-12-05T19:54:11Z",
|
||||||
|
"updated_at": "2018-12-07T11:46:29Z",
|
||||||
|
"closed_at": null,
|
||||||
|
"merged_at": null,
|
||||||
|
"merge_commit_sha": "470bb6e1825ed500ab7016897c2796a989affe11",
|
||||||
|
"assignee": null,
|
||||||
|
"assignees": [],
|
||||||
|
"requested_reviewers": [],
|
||||||
|
"requested_teams": [],
|
||||||
|
"labels": [
|
||||||
|
{
|
||||||
|
"id": 252661769,
|
||||||
|
"node_id": "MDU6TGFiZWwyNTI2NjE3Njk=",
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels/scope:%20curriculum",
|
||||||
|
"name": "scope: curriculum",
|
||||||
|
"color": "9631e2",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 252689581,
|
||||||
|
"node_id": "MDU6TGFiZWwyNTI2ODk1ODE=",
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels/scope:%20tests",
|
||||||
|
"name": "scope: tests",
|
||||||
|
"color": "9631e2",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 1086669320,
|
||||||
|
"node_id": "MDU6TGFiZWwxMDg2NjY5MzIw",
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels/status:%20need%20to%20test%20locally",
|
||||||
|
"name": "status: need to test locally",
|
||||||
|
"color": "df9ae2",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"milestone": null,
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/34559/commits",
|
||||||
|
"review_comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/34559/comments",
|
||||||
|
"review_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/comments{/number}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/34559/comments",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/3e3abdc74b526972dedeb4d704b55a503b388944",
|
||||||
|
"head": {
|
||||||
|
"label": "ValeraS:feat/test-challenges-for-all-lang",
|
||||||
|
"ref": "feat/test-challenges-for-all-lang",
|
||||||
|
"sha": "3e3abdc74b526972dedeb4d704b55a503b388944",
|
||||||
|
"user": {
|
||||||
|
"login": "ValeraS",
|
||||||
|
"id": 10867286,
|
||||||
|
"node_id": "MDQ6VXNlcjEwODY3Mjg2",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/10867286?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/ValeraS",
|
||||||
|
"html_url": "https://github.com/ValeraS",
|
||||||
|
"followers_url": "https://api.github.com/users/ValeraS/followers",
|
||||||
|
"following_url": "https://api.github.com/users/ValeraS/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/ValeraS/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/ValeraS/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/ValeraS/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/ValeraS/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/ValeraS/repos",
|
||||||
|
"events_url": "https://api.github.com/users/ValeraS/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/ValeraS/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"id": 141251006,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkxNDEyNTEwMDY=",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "ValeraS/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "ValeraS",
|
||||||
|
"id": 10867286,
|
||||||
|
"node_id": "MDQ6VXNlcjEwODY3Mjg2",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/10867286?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/ValeraS",
|
||||||
|
"html_url": "https://github.com/ValeraS",
|
||||||
|
"followers_url": "https://api.github.com/users/ValeraS/followers",
|
||||||
|
"following_url": "https://api.github.com/users/ValeraS/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/ValeraS/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/ValeraS/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/ValeraS/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/ValeraS/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/ValeraS/repos",
|
||||||
|
"events_url": "https://api.github.com/users/ValeraS/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/ValeraS/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/ValeraS/freeCodeCamp",
|
||||||
|
"description": "The https://freeCodeCamp.org open source codebase and curriculum. Learn to code and help nonprofits.",
|
||||||
|
"fork": true,
|
||||||
|
"url": "https://api.github.com/repos/ValeraS/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/ValeraS/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2018-07-17T07:43:04Z",
|
||||||
|
"updated_at": "2018-08-06T11:42:53Z",
|
||||||
|
"pushed_at": "2018-12-29T13:46:06Z",
|
||||||
|
"git_url": "git://github.com/ValeraS/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:ValeraS/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/ValeraS/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/ValeraS/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 104232,
|
||||||
|
"stargazers_count": 0,
|
||||||
|
"watchers_count": 0,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": false,
|
||||||
|
"has_projects": true,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 0,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 0,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 0,
|
||||||
|
"open_issues": 0,
|
||||||
|
"watchers": 0,
|
||||||
|
"default_branch": "staging"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"base": {
|
||||||
|
"label": "freeCodeCamp:master",
|
||||||
|
"ref": "master",
|
||||||
|
"sha": "4862246eab9511656294c9be0e4854f253c0ca38",
|
||||||
|
"user": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"repo": {
|
||||||
|
"id": 28457823,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "freeCodeCamp/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"description": "The https://www.freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": false,
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2014-12-24T17:49:19Z",
|
||||||
|
"updated_at": "2019-01-08T17:32:49Z",
|
||||||
|
"pushed_at": "2019-01-08T17:09:22Z",
|
||||||
|
"git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 103712,
|
||||||
|
"stargazers_count": 296827,
|
||||||
|
"watchers_count": 296827,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": true,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 20716,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 5762,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 20716,
|
||||||
|
"open_issues": 5762,
|
||||||
|
"watchers": 296827,
|
||||||
|
"default_branch": "master"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_links": {
|
||||||
|
"self": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/34559"
|
||||||
|
},
|
||||||
|
"html": {
|
||||||
|
"href": "https://github.com/freeCodeCamp/freeCodeCamp/pull/34559"
|
||||||
|
},
|
||||||
|
"issue": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/34559"
|
||||||
|
},
|
||||||
|
"comments": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/34559/comments"
|
||||||
|
},
|
||||||
|
"review_comments": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/34559/comments"
|
||||||
|
},
|
||||||
|
"review_comment": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/comments{/number}"
|
||||||
|
},
|
||||||
|
"commits": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls/34559/commits"
|
||||||
|
},
|
||||||
|
"statuses": {
|
||||||
|
"href": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/3e3abdc74b526972dedeb4d704b55a503b388944"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"author_association": "COLLABORATOR",
|
||||||
|
"merged": false,
|
||||||
|
"mergeable": null,
|
||||||
|
"rebaseable": null,
|
||||||
|
"mergeable_state": "unknown",
|
||||||
|
"merged_by": null,
|
||||||
|
"comments": 1,
|
||||||
|
"review_comments": 0,
|
||||||
|
"maintainer_can_modify": true,
|
||||||
|
"commits": 3,
|
||||||
|
"additions": 127,
|
||||||
|
"deletions": 95,
|
||||||
|
"changed_files": 4
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"id": 28457823,
|
||||||
|
"node_id": "MDEwOlJlcG9zaXRvcnkyODQ1NzgyMw==",
|
||||||
|
"name": "freeCodeCamp",
|
||||||
|
"full_name": "freeCodeCamp/freeCodeCamp",
|
||||||
|
"private": false,
|
||||||
|
"owner": {
|
||||||
|
"login": "freeCodeCamp",
|
||||||
|
"id": 9892522,
|
||||||
|
"node_id": "MDEyOk9yZ2FuaXphdGlvbjk4OTI1MjI=",
|
||||||
|
"avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/freeCodeCamp",
|
||||||
|
"html_url": "https://github.com/freeCodeCamp",
|
||||||
|
"followers_url": "https://api.github.com/users/freeCodeCamp/followers",
|
||||||
|
"following_url": "https://api.github.com/users/freeCodeCamp/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/freeCodeCamp/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/freeCodeCamp/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/freeCodeCamp/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/freeCodeCamp/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/freeCodeCamp/repos",
|
||||||
|
"events_url": "https://api.github.com/users/freeCodeCamp/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/freeCodeCamp/received_events",
|
||||||
|
"type": "Organization",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"html_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"description": "The https://www.freeCodeCamp.org open source codebase and curriculum. Learn to code for free together with millions of people.",
|
||||||
|
"fork": false,
|
||||||
|
"url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp",
|
||||||
|
"forks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/forks",
|
||||||
|
"keys_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/keys{/key_id}",
|
||||||
|
"collaborators_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/collaborators{/collaborator}",
|
||||||
|
"teams_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/teams",
|
||||||
|
"hooks_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/hooks",
|
||||||
|
"issue_events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/events{/number}",
|
||||||
|
"events_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/events",
|
||||||
|
"assignees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/assignees{/user}",
|
||||||
|
"branches_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/branches{/branch}",
|
||||||
|
"tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/tags",
|
||||||
|
"blobs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/blobs{/sha}",
|
||||||
|
"git_tags_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/tags{/sha}",
|
||||||
|
"git_refs_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/refs{/sha}",
|
||||||
|
"trees_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/trees{/sha}",
|
||||||
|
"statuses_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/statuses/{sha}",
|
||||||
|
"languages_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/languages",
|
||||||
|
"stargazers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/stargazers",
|
||||||
|
"contributors_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contributors",
|
||||||
|
"subscribers_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscribers",
|
||||||
|
"subscription_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/subscription",
|
||||||
|
"commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/commits{/sha}",
|
||||||
|
"git_commits_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/git/commits{/sha}",
|
||||||
|
"comments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/comments{/number}",
|
||||||
|
"issue_comment_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues/comments{/number}",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/{+path}",
|
||||||
|
"compare_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/compare/{base}...{head}",
|
||||||
|
"merges_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/merges",
|
||||||
|
"archive_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/{archive_format}{/ref}",
|
||||||
|
"downloads_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/downloads",
|
||||||
|
"issues_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/issues{/number}",
|
||||||
|
"pulls_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/pulls{/number}",
|
||||||
|
"milestones_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/milestones{/number}",
|
||||||
|
"notifications_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/notifications{?since,all,participating}",
|
||||||
|
"labels_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/labels{/name}",
|
||||||
|
"releases_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/releases{/id}",
|
||||||
|
"deployments_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/deployments",
|
||||||
|
"created_at": "2014-12-24T17:49:19Z",
|
||||||
|
"updated_at": "2019-01-08T17:05:29Z",
|
||||||
|
"pushed_at": "2019-01-08T17:09:22Z",
|
||||||
|
"git_url": "git://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"ssh_url": "git@github.com:freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"clone_url": "https://github.com/freeCodeCamp/freeCodeCamp.git",
|
||||||
|
"svn_url": "https://github.com/freeCodeCamp/freeCodeCamp",
|
||||||
|
"homepage": "",
|
||||||
|
"size": 103712,
|
||||||
|
"stargazers_count": 296829,
|
||||||
|
"watchers_count": 296829,
|
||||||
|
"language": "JavaScript",
|
||||||
|
"has_issues": true,
|
||||||
|
"has_projects": false,
|
||||||
|
"has_downloads": true,
|
||||||
|
"has_wiki": false,
|
||||||
|
"has_pages": false,
|
||||||
|
"forks_count": 20716,
|
||||||
|
"mirror_url": null,
|
||||||
|
"archived": false,
|
||||||
|
"open_issues_count": 5762,
|
||||||
|
"license": {
|
||||||
|
"key": "bsd-3-clause",
|
||||||
|
"name": "BSD 3-Clause \"New\" or \"Revised\" License",
|
||||||
|
"spdx_id": "BSD-3-Clause",
|
||||||
|
"url": "https://api.github.com/licenses/bsd-3-clause",
|
||||||
|
"node_id": "MDc6TGljZW5zZTU="
|
||||||
|
},
|
||||||
|
"forks": 20716,
|
||||||
|
"open_issues": 5762,
|
||||||
|
"watchers": 296829,
|
||||||
|
"default_branch": "master"
|
||||||
|
},
|
||||||
|
"sender": {
|
||||||
|
"login": "tbushman",
|
||||||
|
"id": 7049294,
|
||||||
|
"node_id": "MDQ6VXNlcjcwNDkyOTQ=",
|
||||||
|
"avatar_url": "https://avatars2.githubusercontent.com/u/7049294?v=4",
|
||||||
|
"gravatar_id": "",
|
||||||
|
"url": "https://api.github.com/users/tbushman",
|
||||||
|
"html_url": "https://github.com/tbushman",
|
||||||
|
"followers_url": "https://api.github.com/users/tbushman/followers",
|
||||||
|
"following_url": "https://api.github.com/users/tbushman/following{/other_user}",
|
||||||
|
"gists_url": "https://api.github.com/users/tbushman/gists{/gist_id}",
|
||||||
|
"starred_url": "https://api.github.com/users/tbushman/starred{/owner}{/repo}",
|
||||||
|
"subscriptions_url": "https://api.github.com/users/tbushman/subscriptions",
|
||||||
|
"organizations_url": "https://api.github.com/users/tbushman/orgs",
|
||||||
|
"repos_url": "https://api.github.com/users/tbushman/repos",
|
||||||
|
"events_url": "https://api.github.com/users/tbushman/events{/privacy}",
|
||||||
|
"received_events_url": "https://api.github.com/users/tbushman/received_events",
|
||||||
|
"type": "User",
|
||||||
|
"site_admin": false
|
||||||
|
},
|
||||||
|
"installation": {
|
||||||
|
"id": 421598,
|
||||||
|
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"sha": "49131c31d624d9434171ec700b992fe1e3698513",
|
||||||
|
"filename": "guide/english/linux/basic-linux-commands/index.md",
|
||||||
|
"status": "modified",
|
||||||
|
"additions": 1,
|
||||||
|
"deletions": 1,
|
||||||
|
"changes": 2,
|
||||||
|
"blob_url": "https://github.com/freeCodeCamp/freeCodeCamp/blob/e3b300f24ef8d9aee1eb2677af0b897e8736fcea/guide/english/linux/basic-linux-commands/index.md",
|
||||||
|
"raw_url": "https://github.com/freeCodeCamp/freeCodeCamp/raw/e3b300f24ef8d9aee1eb2677af0b897e8736fcea/guide/english/linux/basic-linux-commands/index.md",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/guide/english/linux/basic-linux-commands/index.md?ref=e3b300f24ef8d9aee1eb2677af0b897e8736fcea",
|
||||||
|
"patch": "@@ -61,7 +61,7 @@ When starting out with linux, there are some basic commands everyone should know\n 25409 s004 Ss 0:00.04 login -pf <user>\n ```\n \n- 15. **df -h** Checks disk space in human readable form\n+ 16. **df -h** Checks disk space in human readable form\n - Shows the size, amount used, amount available and capacity percentage of mounted drives/partitions.\n \n "
|
||||||
|
}
|
||||||
|
]
|
14
tools/dashboard/probot/test/payloads/files/files.opened.json
Normal file
14
tools/dashboard/probot/test/payloads/files/files.opened.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"sha": "49131c31d624d9434171ec700b992fe1e3698513",
|
||||||
|
"filename": "guide/english/linux/basic-linux-commands/index.md",
|
||||||
|
"status": "modified",
|
||||||
|
"additions": 1,
|
||||||
|
"deletions": 1,
|
||||||
|
"changes": 2,
|
||||||
|
"blob_url": "https://github.com/freeCodeCamp/freeCodeCamp/blob/ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b/guide/english/linux/basic-linux-commands/index.md",
|
||||||
|
"raw_url": "https://github.com/freeCodeCamp/freeCodeCamp/raw/ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b/guide/english/linux/basic-linux-commands/index.md",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/guide/english/linux/basic-linux-commands/index.md?ref=ccadaeb4ed4ae17274fb7c32abe2071ea1e3898b",
|
||||||
|
"patch": "@@ -61,7 +61,7 @@ When starting out with linux, there are some basic commands everyone should know\n 25409 s004 Ss 0:00.04 login -pf <user>\n ```\n \n- 15. **df -h** Checks disk space in human readable form\n+ 16. **df -h** Checks disk space in human readable form\n - Shows the size, amount used, amount available and capacity percentage of mounted drives/partitions.\n \n "
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,50 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"sha": "53d25622b620a1fdb5b58c68b54e35a42233b1f8",
|
||||||
|
"filename": "curriculum/schema/challengeSchema.js",
|
||||||
|
"status": "modified",
|
||||||
|
"additions": 83,
|
||||||
|
"deletions": 78,
|
||||||
|
"changes": 161,
|
||||||
|
"blob_url": "https://github.com/freeCodeCamp/freeCodeCamp/blob/3e3abdc74b526972dedeb4d704b55a503b388944/curriculum/schema/challengeSchema.js",
|
||||||
|
"raw_url": "https://github.com/freeCodeCamp/freeCodeCamp/raw/3e3abdc74b526972dedeb4d704b55a503b388944/curriculum/schema/challengeSchema.js",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/curriculum/schema/challengeSchema.js?ref=3e3abdc74b526972dedeb4d704b55a503b388944",
|
||||||
|
"patch": "@@ -1,84 +1,89 @@\n const Joi = require('joi');\n Joi.objectId = require('joi-objectid')(Joi);\n-const path = require('path');\n-require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });\n \n-const { LOCALE: lang = 'english' } = process.env;\n-\n-let schema = Joi.object().keys({\n- block: Joi.string(),\n- blockId: Joi.objectId(),\n- challengeOrder: Joi.number(),\n- challengeType: Joi.number()\n- .min(0)\n- .max(9)\n- .required(),\n- checksum: Joi.number(),\n- dashedName: Joi.string(),\n- description: Joi.string().required(),\n- fileName: Joi.string(),\n- files: Joi.array().items(\n- Joi.object().keys({\n- key: Joi.string(),\n- ext: Joi.string(),\n- name: Joi.string(),\n- head: [Joi.array().items(Joi.string().allow('')), Joi.string().allow('')],\n- tail: [Joi.array().items(Joi.string().allow('')), Joi.string().allow('')],\n- contents: [\n- Joi.array().items(Joi.string().allow('')),\n- Joi.string().allow('')\n- ]\n- })\n- ),\n- guideUrl: Joi.string().uri({ scheme: 'https' }),\n- videoUrl: Joi.string().allow(''),\n- helpRoom: Joi.string(),\n- id: Joi.objectId().required(),\n- instructions: Joi.string().required(),\n- isBeta: Joi.bool(),\n- isComingSoon: Joi.bool(),\n- isLocked: Joi.bool(),\n- isPrivate: Joi.bool(),\n- isRequired: Joi.bool(),\n- name: Joi.string(),\n- order: Joi.number(),\n- required: Joi.array().items(\n- Joi.object().keys({\n- link: Joi.string(),\n- raw: Joi.bool(),\n- src: Joi.string(),\n- crossDomain: Joi.bool()\n- })\n- ),\n- solutions: Joi.array().items(Joi.string().optional()),\n- superBlock: Joi.string(),\n- superOrder: Joi.number(),\n- suborder: Joi.number(),\n- tests: Joi.array().items(\n- // public challenges\n- Joi.object().keys({\n- text: Joi.string().required(),\n- testString: Joi.string()\n- .allow('')\n- .required()\n- }),\n- // our tests used in certification verification\n- Joi.object().keys({\n- id: Joi.string().required(),\n- title: Joi.string().required()\n- })\n- ),\n- template: Joi.string().allow(''),\n- time: Joi.string().allow(''),\n- title: Joi.string().required()\n-});\n-\n-if (lang !== 'english') {\n- schema = schema.append({\n- localeTitle: Joi.string().required()\n+function getSchemaForLang(lang) {\n+ let schema = Joi.object().keys({\n+ block: Joi.string(),\n+ blockId: Joi.objectId(),\n+ challengeOrder: Joi.number(),\n+ challengeType: Joi.number()\n+ .min(0)\n+ .max(9)\n+ .required(),\n+ checksum: Joi.number(),\n+ dashedName: Joi.string(),\n+ description: Joi.string().required(),\n+ fileName: Joi.string(),\n+ files: Joi.array().items(\n+ Joi.object().keys({\n+ key: Joi.string(),\n+ ext: Joi.string(),\n+ name: Joi.string(),\n+ head: [\n+ Joi.array().items(Joi.string().allow('')),\n+ Joi.string().allow('')\n+ ],\n+ tail: [\n+ Joi.array().items(Joi.string().allow('')),\n+ Joi.string().allow('')\n+ ],\n+ contents: [\n+ Joi.array().items(Joi.string().allow('')),\n+ Joi.string().allow('')\n+ ]\n+ })\n+ ),\n+ guideUrl: Joi.string().uri({ scheme: 'https' }),\n+ videoUrl: Joi.string().allow(''),\n+ helpRoom: Joi.string(),\n+ id: Joi.objectId().required(),\n+ instructions: Joi.string().required(),\n+ isBeta: Joi.bool(),\n+ isComingSoon: Joi.bool(),\n+ isLocked: Joi.bool(),\n+ isPrivate: Joi.bool(),\n+ isRequired: Joi.bool(),\n+ name: Joi.string(),\n+ order: Joi.number(),\n+ required: Joi.array().items(\n+ Joi.object().keys({\n+ link: Joi.string(),\n+ raw: Joi.bool(),\n+ src: Joi.string(),\n+ crossDomain: Joi.bool()\n+ })\n+ ),\n+ solutions: Joi.array().items(Joi.string().optional()),\n+ superBlock: Joi.string(),\n+ superOrder: Joi.number(),\n+ suborder: Joi.number(),\n+ tests: Joi.array().items(\n+ // public challenges\n+ Joi.object().keys({\n+ text: Joi.string().required(),\n+ testString: Joi.string()\n+ .allow('')\n+ .required()\n+ }),\n+ // our tests used in certification verification\n+ Joi.object().keys({\n+ id: Joi.string().required(),\n+ title: Joi.string().required()\n+ })\n+ ),\n+ template: Joi.string().allow(''),\n+ time: Joi.string().allow(''),\n+ title: Joi.string().required()\n });\n-}\n \n-exports.validateChallenge = function validateChallenge(challenge) {\n- return Joi.validate(challenge, schema);\n+ if (lang !== 'english') {\n+ schema = schema.append({\n+ localeTitle: Joi.string().required()\n+ });\n+ }\n+ return schema;\n+}\n+exports.challengeSchemaValidator = lang => {\n+ const schema = getSchemaForLang(lang);\n+ return challenge => Joi.validate(challenge, schema);\n };"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sha": "648329aaa0ac1e052f5a9174d0ca6027b42c3a92",
|
||||||
|
"filename": "curriculum/test/test-challenges.js",
|
||||||
|
"status": "modified",
|
||||||
|
"additions": 33,
|
||||||
|
"deletions": 16,
|
||||||
|
"changes": 49,
|
||||||
|
"blob_url": "https://github.com/freeCodeCamp/freeCodeCamp/blob/3e3abdc74b526972dedeb4d704b55a503b388944/curriculum/test/test-challenges.js",
|
||||||
|
"raw_url": "https://github.com/freeCodeCamp/freeCodeCamp/raw/3e3abdc74b526972dedeb4d704b55a503b388944/curriculum/test/test-challenges.js",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/curriculum/test/test-challenges.js?ref=3e3abdc74b526972dedeb4d704b55a503b388944",
|
||||||
|
"patch": "@@ -22,27 +22,27 @@ const { getChallengesForLang } = require('../getChallenges');\n \n const MongoIds = require('./utils/mongoIds');\n const ChallengeTitles = require('./utils/challengeTitles');\n-const { validateChallenge } = require('../schema/challengeSchema');\n+const { challengeSchemaValidator } = require('../schema/challengeSchema');\n const { challengeTypes } = require('../../client/utils/challengeTypes');\n \n-const { LOCALE: lang = 'english' } = process.env;\n+const { supportedLangs } = require('../utils');\n \n const oldRunnerFail = Mocha.Runner.prototype.fail;\n Mocha.Runner.prototype.fail = function(test, err) {\n- if (err.stack && err instanceof AssertionError) {\n- const assertIndex = err.message.indexOf(': expected');\n+ if (err instanceof AssertionError) {\n+ const errMessage = String(err.message || '');\n+ const assertIndex = errMessage.indexOf(': expected');\n if (assertIndex !== -1) {\n- err.message = err.message.slice(0, assertIndex);\n+ err.message = errMessage.slice(0, assertIndex);\n }\n // Don't show stacktrace for assertion errors.\n- delete err.stack;\n+ if (err.stack) {\n+ delete err.stack;\n+ }\n }\n return oldRunnerFail.call(this, test, err);\n };\n \n-let mongoIds = new MongoIds();\n-let challengeTitles = new ChallengeTitles();\n-\n const { JSDOM } = jsdom;\n \n const babelOptions = {\n@@ -55,7 +55,23 @@ const jQueryScript = fs.readFileSync(\n 'utf8'\n );\n \n-(async function() {\n+runTests();\n+\n+async function runTests() {\n+ let testLangs = [...supportedLangs];\n+ if (process.env.TEST_CHALLENGES_FOR_LANGS) {\n+ const filterLangs = process.env.TEST_CHALLENGES_FOR_LANGS.split(',').map(\n+ lang => lang.trim().toLowerCase()\n+ );\n+ testLangs = testLangs.filter(lang => filterLangs.includes(lang));\n+ }\n+\n+ await Promise.all(testLangs.map(lang => populateTestsForLang(lang)));\n+\n+ run();\n+}\n+\n+async function populateTestsForLang(lang) {\n const allChallenges = await getChallengesForLang(lang).then(curriculum =>\n Object.keys(curriculum)\n .map(key => curriculum[key].blocks)\n@@ -67,7 +83,11 @@ const jQueryScript = fs.readFileSync(\n }, [])\n );\n \n- describe('Check challenges tests', async function() {\n+ const mongoIds = new MongoIds();\n+ const challengeTitles = new ChallengeTitles();\n+ const validateChallenge = challengeSchemaValidator(lang);\n+\n+ describe(`Check challenges (${lang})`, async function() {\n before(async function() {\n this.timeout(30000);\n global.browser = await puppeteer.launch({ args: ['--no-sandbox'] });\n@@ -87,8 +107,7 @@ const jQueryScript = fs.readFileSync(\n it('Common checks', function() {\n const result = validateChallenge(challenge);\n if (result.error) {\n- console.log(result.value);\n- throw new Error(result.error);\n+ throw new AssertionError(result.error);\n }\n const { id, title } = challenge;\n mongoIds.check(id, title);\n@@ -208,9 +227,7 @@ const jQueryScript = fs.readFileSync(\n });\n });\n });\n-\n- run();\n-})();\n+}\n \n // Fake Deep Equal dependency\n const DeepEqual = (a, b) => JSON.stringify(a) === JSON.stringify(b);"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sha": "d8b52ff8ae6587f3701ca5f8bc5339981f1adde4",
|
||||||
|
"filename": "curriculum/utils.js",
|
||||||
|
"status": "modified",
|
||||||
|
"additions": 9,
|
||||||
|
"deletions": 1,
|
||||||
|
"changes": 10,
|
||||||
|
"blob_url": "https://github.com/freeCodeCamp/freeCodeCamp/blob/3e3abdc74b526972dedeb4d704b55a503b388944/curriculum/utils.js",
|
||||||
|
"raw_url": "https://github.com/freeCodeCamp/freeCodeCamp/raw/3e3abdc74b526972dedeb4d704b55a503b388944/curriculum/utils.js",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/curriculum/utils.js?ref=3e3abdc74b526972dedeb4d704b55a503b388944",
|
||||||
|
"patch": "@@ -6,5 +6,13 @@ exports.dasherize = function dasherize(name) {\n .replace(/\\:/g, '');\n };\n \n-const supportedLangs = ['english', 'spanish'];\n+const supportedLangs = [\n+ 'arabic',\n+ 'chinese',\n+ 'english',\n+ 'portuguese',\n+ 'russian',\n+ 'spanish'\n+];\n+\n exports.supportedLangs = supportedLangs;"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"sha": "5e847f5188b3962ed7811ebae55c7099ee18940d",
|
||||||
|
"filename": "sample.env",
|
||||||
|
"status": "modified",
|
||||||
|
"additions": 2,
|
||||||
|
"deletions": 0,
|
||||||
|
"changes": 2,
|
||||||
|
"blob_url": "https://github.com/freeCodeCamp/freeCodeCamp/blob/3e3abdc74b526972dedeb4d704b55a503b388944/sample.env",
|
||||||
|
"raw_url": "https://github.com/freeCodeCamp/freeCodeCamp/raw/3e3abdc74b526972dedeb4d704b55a503b388944/sample.env",
|
||||||
|
"contents_url": "https://api.github.com/repos/freeCodeCamp/freeCodeCamp/contents/sample.env?ref=3e3abdc74b526972dedeb4d704b55a503b388944",
|
||||||
|
"patch": "@@ -28,3 +28,5 @@ API_LOCATION='http://localhost:3000'\n FORUM_LOCATION='https://forum.localhost'\n FORUM_PROXY_LOCATION='https://proxy.localhost'\n LOCALE=english\n+\n+TEST_CHALLENGES_FOR_LANGS=english"
|
||||||
|
}
|
||||||
|
]
|
24
tools/dashboard/probot/test/utils/testmodels.js
Normal file
24
tools/dashboard/probot/test/utils/testmodels.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const mongoose = require('mongoose');
|
||||||
|
|
||||||
|
const prTest = new mongoose.Schema({
|
||||||
|
_id: Number,
|
||||||
|
updatedAt: String,
|
||||||
|
username: String,
|
||||||
|
title: String,
|
||||||
|
filenames: [String]
|
||||||
|
});
|
||||||
|
|
||||||
|
const infoTest = new mongoose.Schema({
|
||||||
|
lastUpdate: Date,
|
||||||
|
numPRs: Number,
|
||||||
|
prRange: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const dbCollections = {
|
||||||
|
prtest: 'test_openprs',
|
||||||
|
infotest: 'test_info'
|
||||||
|
};
|
||||||
|
|
||||||
|
const PRtest = mongoose.model('PRtest', prTest, dbCollections['pr']);
|
||||||
|
const INFOtest = mongoose.model('INFOtest', infoTest, dbCollections['info']);
|
||||||
|
module.exports = { PRtest, INFOtest, dbCollections };
|
23
tools/dashboard/probot/travis.yml
Normal file
23
tools/dashboard/probot/travis.yml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
sudo: false
|
||||||
|
language: node_js
|
||||||
|
node_js:
|
||||||
|
- "8.3"
|
||||||
|
notifications:
|
||||||
|
disabled: true
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- "node_modules"
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
install:
|
||||||
|
- npm install
|
||||||
|
script:
|
||||||
|
- echo "Skipping tests"
|
||||||
|
deploy:
|
||||||
|
provider: pages
|
||||||
|
skip-cleanup: true
|
||||||
|
github-token: $GITHUB_ACCESS_TOKEN
|
||||||
|
keep-history: true
|
||||||
|
on:
|
||||||
|
branch: master
|
4
tools/dashboard/work-logs/.gitignore
vendored
Normal file
4
tools/dashboard/work-logs/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Ignore everything in this directory
|
||||||
|
*
|
||||||
|
# Except this file
|
||||||
|
!.gitignore
|
Reference in New Issue
Block a user