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:
Mrugesh Mohapatra
2020-04-07 18:28:56 +05:30
committed by GitHub
parent 7473bcc40e
commit 21b27cb7c1
100 changed files with 41993 additions and 1 deletions

View File

@ -3,4 +3,5 @@ client/.cache/**
client/static/**
client/public/**
api-server/public/**
api-server/lib/**
api-server/lib/**
tools/dashboard/**

5
jest.config.js Normal file
View 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
View 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

View File

@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "none"
}

View File

@ -0,0 +1 @@
> Our Code of Conduct is available here: <https://code-of-conduct.freecodecamp.org/>

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

View File

@ -0,0 +1,5 @@
![freeCodeCamp Social Banner](https://s3.amazonaws.com/freecodecamp/wide-social-banner.png)
# 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

View 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;

View File

@ -0,0 +1,8 @@
# Contributing
Todo
## Usage
Todo

View 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 its 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 PRs 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.

View File

@ -0,0 +1,4 @@
{
"packages": ["probot", "probot/client", "lib", "one-off-scripts"],
"version": "independent"
}

View 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 };

View File

@ -0,0 +1,6 @@
module.exports = {
labelPRConflict: {
name: 'PR: potential-conflict',
color: 'c2e0c6'
}
};

View 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 };

View 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

File diff suppressed because it is too large Load Diff

View 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"
}

View 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 };

View 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 };

View 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 };

View 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 };

View 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 };

View 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
};

View 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 };

View 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 };

View 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 };

View 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 };

View 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 };

View File

@ -0,0 +1,3 @@
const { validLabels } = require('./valid-labels');
module.exports = { validLabels };

View 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 };

View File

@ -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);
});

View File

@ -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);
});

View 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);
});

View File

@ -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);
});

View File

@ -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');
})();

View 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);
});

View 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);
});

View 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"
}
}
}
}

View 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"
}

View 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);
});

View 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

File diff suppressed because it is too large Load Diff

View 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"
}
}

View 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

File diff suppressed because it is too large Load Diff

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

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

View 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"
}

View 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;

View 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);
});

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

View 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;

View 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;

View File

@ -0,0 +1,7 @@
import styled from 'styled-components';
const FullWidthDiv = styled.div`
width: 100%;
`;
export default FullWidthDiv;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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
};

Binary file not shown.

Binary file not shown.

View 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;
}

View 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();

View 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();
});
}
}

View File

@ -0,0 +1,5 @@
const theme = {
primary: '#006400'
};
export default theme;

View 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

File diff suppressed because it is too large Load Diff

View 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"
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

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

View 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;
}

View 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

View 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 };

View 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;

View 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 };

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;
});

View 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/

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View 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/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 "
}
]

View 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 "
}
]

View File

@ -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"
}
]

View 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 };

View 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
View File

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore