78 |
79 | ### Starting the express server (via probot)
80 |
81 | ```bash
82 | pm2 start "npm start" --name "contribute-app"
83 | ```
84 |
85 | **Note:** Start only one instance of this app, you can't have multiple probot apps running. Starting multiple instances will crash the app.
86 |
--------------------------------------------------------------------------------
/one-off-scripts/find-failures.js:
--------------------------------------------------------------------------------
1 | /*
2 | This is a one-off script to find all open PRs which have one of the
3 | console.error descriptions in the failuresToFind.json file.
4 | */
5 |
6 | const { Octokit } = require('@octokit/rest');
7 | const fetch = require('node-fetch');
8 |
9 | const {
10 | github: { owner, secret, freeCodeCampRepo, defaultBase }
11 | } = require('../lib/config');
12 |
13 | const octokit = new Octokit({ auth: secret });
14 |
15 | const { getPRs, getUserInput } = require('../lib/get-prs');
16 | const { savePrData, ProcessingLog } = require('../lib/utils');
17 |
18 | const log = new ProcessingLog('find-failures-script');
19 |
20 | const errorsToFind = [
21 | {
22 | error: '',
23 | regex: ''
24 | }
25 | ];
26 |
27 | (async () => {
28 | const { totalPRs, firstPR, lastPR } = await getUserInput(
29 | freeCodeCampRepo,
30 | defaultBase
31 | );
32 | const prPropsToGet = ['number', 'labels', 'head'];
33 | const { openPRs } = await getPRs(
34 | freeCodeCampRepo,
35 | defaultBase,
36 | totalPRs,
37 | firstPR,
38 | lastPR,
39 | prPropsToGet
40 | );
41 |
42 | if (openPRs.length) {
43 | savePrData(openPRs, firstPR, lastPR);
44 | log.start();
45 | console.log('Starting error finding process...');
46 | for (let count = 0; count < openPRs.length; count++) {
47 | let {
48 | number,
49 | labels,
50 | head: { sha: ref }
51 | } = openPRs[count];
52 | const existingLabels = labels.map(({ name }) => name);
53 |
54 | if (
55 | !existingLabels.includes('status: merge conflict') &&
56 | !existingLabels.includes('status: needs update') &&
57 | !existingLabels.includes('status: discussing')
58 | ) {
59 | const { data: statuses } = await octokit.repos.listStatusesForRef({
60 | owner,
61 | repo: freeCodeCampRepo,
62 | ref
63 | });
64 | if (statuses.length) {
65 | // first element contain most recent status
66 | const { state, target_url: targetUrl } = statuses[0];
67 | const hasProblem = state === 'failure' || state === 'error';
68 | if (hasProblem) {
69 | let buildNum = Number(targetUrl.match(/\/builds\/(\d+)\?/i)[1]);
70 | /*
71 | const logNumber = 'need to use Travis api to
72 | access the full log for the buildNum above'
73 | */
74 | const logNumber = ++buildNum;
75 | const travisBaseUrl = 'https://api.travis-ci.org/v3/job/';
76 | const travisLogUrl = `${travisBaseUrl + logNumber}/log.txt`;
77 | const response = await fetch(travisLogUrl);
78 | const logText = await response.text();
79 | let error;
80 | for (let { error: errorDesc, regex } of errorsToFind) {
81 | regex = RegExp(regex);
82 | if (regex.test(logText)) {
83 | error = errorDesc;
84 | break;
85 | }
86 | }
87 | const errorDesc = error ? error : 'unknown error';
88 | log.add(number, { number, errorDesc, buildLog: travisLogUrl });
89 | }
90 | }
91 | }
92 | }
93 | }
94 | })()
95 | .then(() => {
96 | log.finish();
97 | console.log('Successfully finished finding all specified errors.');
98 | })
99 | .catch((err) => {
100 | log.finish();
101 | console.log(err);
102 | });
103 |
--------------------------------------------------------------------------------
/one-off-scripts/get-unknown-repo-prs.js:
--------------------------------------------------------------------------------
1 | /*
2 | This script was created to find all open PRs with unknown repos which
3 | potentially have merge conflicts.
4 |
5 | To run the script for a specific language, call the script with the language
6 | name as the first argument.
7 |
8 | Note: If any PR displayed in the console shows "unknown", you will need to rerun
9 | the script again.
10 | */
11 |
12 | const {
13 | github: { owner, secret, freeCodeCampRepo, defaultBase }
14 | } = require('../lib/config');
15 | const { Octokit } = require('@octokit/rest');
16 |
17 | const octokit = new Octokit({ auth: secret });
18 |
19 | const { getPRs, getUserInput } = require('../lib/get-prs');
20 | const { ProcessingLog, rateLimiter } = require('../lib/utils');
21 | const { validLabels } = require('../lib/validation/valid-labels');
22 |
23 | let languageLabel;
24 | let [languageArg] = process.argv.slice(2);
25 | if (languageArg) {
26 | languageArg = languageArg.toLowerCase();
27 | languageLabel = validLabels[languageArg] ? validLabels[languageArg] : null;
28 | }
29 |
30 | if (languageLabel) {
31 | console.log(`finding PRs with label = ${languageLabel}`);
32 | }
33 |
34 | const log = new ProcessingLog('unknown-repo-prs-with-merge-conflicts');
35 | log.start();
36 | (async () => {
37 | const { totalPRs, firstPR, lastPR } = await getUserInput(
38 | freeCodeCampRepo,
39 | defaultBase,
40 | 'all'
41 | );
42 | const prPropsToGet = ['number', 'labels', 'user', 'head'];
43 | const { openPRs } = await getPRs(
44 | freeCodeCampRepo,
45 | defaultBase,
46 | totalPRs,
47 | firstPR,
48 | lastPR,
49 | prPropsToGet
50 | );
51 | if (openPRs.length) {
52 | let count = 0;
53 | let mergeConflictCount = 0;
54 |
55 | for (let i = 0; i < openPRs.length; i++) {
56 | let {
57 | labels,
58 | number,
59 | head: { repo: headRepo }
60 | } = openPRs[i];
61 |
62 | const hasLanguage =
63 | languageLabel && labels.some(({ name }) => languageLabel === name);
64 |
65 | if (headRepo === null && (!languageLabel || hasLanguage)) {
66 | let data = await octokit.pulls.get({
67 | owner,
68 | repo: freeCodeCampRepo,
69 | number
70 | });
71 | let mergeableState = data.data.mergeable_state;
72 | if (mergeableState === 'unknown') {
73 | // allow time to let GitHub determine status
74 | await rateLimiter(4000);
75 | data = await octokit.pulls.get({
76 | owner,
77 | repo: freeCodeCampRepo,
78 | number
79 | });
80 | mergeableState = data.data.mergeable_state;
81 | }
82 | count++;
83 |
84 | if (mergeableState === 'dirty' || mergeableState === 'unknown') {
85 | log.add(number, { number, mergeableState });
86 | console.log(`${number} (${mergeableState})`);
87 | mergeConflictCount++;
88 | }
89 | if (count > 4000) {
90 | await rateLimiter(2350);
91 | }
92 | }
93 | }
94 | console.log(
95 | `There were ${mergeConflictCount} PRs with potential merge conflicts out of ${count} unknown repos received from GitHub`
96 | );
97 | } else {
98 | throw 'There were no open PRs received from Github';
99 | }
100 | })()
101 | .then(async () => {
102 | log.finish();
103 | console.log('Finished finding unknown repo PRs with merge conflicts');
104 | })
105 | .catch((err) => {
106 | log.finish();
107 | console.log(err);
108 | });
109 |
--------------------------------------------------------------------------------
/one-off-scripts/prs-with-merge-conflicts.js:
--------------------------------------------------------------------------------
1 | /*
2 | This script was created to find all open PRs that have merge
3 | conflicts and then add a the 'status: merge conflict' label to any PR
4 | which does not already have the label.
5 |
6 | To run the script for a specific language, call the script with the language
7 | name as the first argument.
8 |
9 | Note: It is possible that it could take more than 4 seconds for GitHub to
10 | determine if a PR is mergeable. If that happens, the PR will not be labeled.
11 | */
12 |
13 | const { Octokit } = require('@octokit/rest');
14 | const {
15 | github: { owner, secret, freeCodeCampRepo, defaultBase }
16 | } = require('../lib/config');
17 |
18 | const octokit = new Octokit({ auth: secret });
19 |
20 | const { getPRs, getUserInput } = require('../lib/get-prs');
21 | const { ProcessingLog, rateLimiter } = require('../lib/utils');
22 | const { addLabels } = require('../lib/pr-tasks');
23 | const { validLabels } = require('../lib/validation/valid-labels');
24 |
25 | let languageLabel;
26 | let [languageArg] = process.argv.slice(2);
27 | if (languageArg) {
28 | languageArg = languageArg.toLowerCase();
29 | languageLabel = validLabels[languageArg] ? validLabels[languageArg] : null;
30 | }
31 |
32 | if (languageLabel) {
33 | console.log(`finding PRs with label = ${languageLabel}`);
34 | }
35 |
36 | const log = new ProcessingLog('prs-with-merge-conflicts');
37 | log.start();
38 | (async () => {
39 | const { totalPRs, firstPR, lastPR } = await getUserInput(
40 | freeCodeCampRepo,
41 | defaultBase,
42 | 'all'
43 | );
44 | const prPropsToGet = ['number', 'labels', 'user'];
45 | const { openPRs } = await getPRs(
46 | freeCodeCampRepo,
47 | defaultBase,
48 | totalPRs,
49 | firstPR,
50 | lastPR,
51 | prPropsToGet
52 | );
53 | if (openPRs.length) {
54 | let count = 0;
55 | let mergeConflictCount = 0;
56 | for (let i = 0; i < openPRs.length; i++) {
57 | let { labels, number } = openPRs[i];
58 |
59 | const hasLanguage =
60 | languageLabel && labels.some(({ name }) => languageLabel === name);
61 |
62 | const hasMergeConflictLabel = labels.some(
63 | ({ name }) => 'status: merge conflict' === name
64 | );
65 |
66 | if (!languageLabel || hasLanguage) {
67 | let data = await octokit.pulls.get({
68 | owner,
69 | repo: freeCodeCampRepo,
70 | number
71 | });
72 | let mergeableState = data.data.mergeable_state;
73 | count++;
74 | if (mergeableState === 'unknown') {
75 | await rateLimiter(4000);
76 | data = await octokit.pulls.get({
77 | owner,
78 | repo: freeCodeCampRepo,
79 | number
80 | });
81 | mergeableState = data.data.mergeable_state;
82 | count++;
83 | }
84 |
85 | if (mergeableState === 'dirty' && !hasMergeConflictLabel) {
86 | mergeConflictCount++;
87 | addLabels(number, ['status: merge conflict'], log);
88 | await rateLimiter();
89 | }
90 |
91 | if (count > 4000) {
92 | await rateLimiter(2350);
93 | }
94 | }
95 | }
96 | console.log(
97 | `There were ${mergeConflictCount} PRs with potential merge conflicts out of ${count} PRs received from GitHub`
98 | );
99 | } else {
100 | throw 'There were no open PRs received from Github';
101 | }
102 | })()
103 | .then(async () => {
104 | log.finish();
105 | console.log('Finished finding PRs with merge conflicts');
106 | })
107 | .catch((err) => {
108 | log.finish();
109 | console.log(err);
110 | });
111 |
--------------------------------------------------------------------------------
/dashboard-app/client/src/components/Search.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Input from './Input';
4 | import PrResults from './PrResults';
5 | import FilenameResults from './FilenameResults';
6 | import SearchOption from './SearchOption';
7 |
8 | import { ENDPOINT_PR, ENDPOINT_SEARCH } from '../constants';
9 | class Search extends Component {
10 | state = {
11 | searchValue: '',
12 | selectedOption: 'pr',
13 | results: [],
14 | message: ''
15 | };
16 |
17 | clearObj = { searchValue: '', results: [] };
18 |
19 | inputRef = React.createRef();
20 |
21 | handleInputEvent = (event) => {
22 | const {
23 | type,
24 | key,
25 | target: { value: searchValue }
26 | } = event;
27 |
28 | if (type === 'change') {
29 | if (this.state.selectedOption === 'pr') {
30 | if (Number(searchValue) || searchValue === '') {
31 | this.setState((prevState) => ({ searchValue, results: [] }));
32 | }
33 | } else {
34 | this.setState((prevState) => ({ searchValue, results: [] }));
35 | }
36 | } else if (type === 'keypress' && key === 'Enter') {
37 | this.searchPRs(searchValue);
38 | }
39 | };
40 |
41 | handleButtonClick = () => {
42 | const { searchValue } = this.state;
43 | if (searchValue) {
44 | this.searchPRs(searchValue);
45 | } else {
46 | this.inputRef.current.focus();
47 | }
48 | };
49 |
50 | handleOptionChange = (changeEvent) => {
51 | const selectedOption = changeEvent.target.value;
52 |
53 | this.setState((prevState) => ({ selectedOption, ...this.clearObj }));
54 | this.inputRef.current.focus();
55 | };
56 |
57 | searchPRs = (value) => {
58 | const { selectedOption } = this.state;
59 |
60 | const fetchUrl =
61 | selectedOption === 'pr'
62 | ? `${ENDPOINT_PR}/${value}`
63 | : `${ENDPOINT_SEARCH}/?value=${value}`;
64 |
65 | fetch(fetchUrl)
66 | .then((response) => response.json())
67 | .then(({ ok, message, results, rateLimitMessage }) => {
68 | if (ok) {
69 | this.setState((prevState) => ({ message, results }));
70 | } else if (rateLimitMessage) {
71 | this.setState((prevState) => ({
72 | rateLimitMessage
73 | }));
74 | }
75 | })
76 | .catch(() => {
77 | this.setState((prevState) => this.clearObj);
78 | });
79 | };
80 |
81 | componentDidMount() {
82 | this.inputRef.current.focus();
83 | }
84 |
85 | render() {
86 | const {
87 | handleButtonClick,
88 | handleInputEvent,
89 | inputRef,
90 | handleOptionChange,
91 | state
92 | } = this;
93 | const { searchValue, message, results, selectedOption, rateLimitMessage } =
94 | state;
95 |
96 | return (
97 | <>
98 |
99 |
104 | PR #
105 |
106 |
111 | Filename
112 |
113 |
114 |
119 |
120 | {message}
121 | {selectedOption === 'pr' && (
122 |
127 | )}
128 | {selectedOption === 'filename' && (
129 |
134 | )}
135 | >
136 | );
137 | }
138 | }
139 |
140 | export default Search;
141 |
--------------------------------------------------------------------------------
/dashboard-app/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import styled from 'styled-components';
3 |
4 | import FreeCodeCampLogo from './assets/freeCodeCampLogo';
5 | import Tabs from './components/Tabs';
6 | import Search from './components/Search';
7 | import Pareto from './components/Pareto';
8 | import Repos from './components/Repos';
9 | import Footer from './components/Footer';
10 |
11 | import { ENDPOINT_INFO } from './constants';
12 |
13 | const PageContainer = styled.div`
14 | margin-top: 70px;
15 | display: flex;
16 | flex-direction: column;
17 | justify-content: center;
18 | align-items: center;
19 | @media (max-width: 991px) {
20 | margin-top: 135px;
21 | }
22 | `;
23 |
24 | const Container = styled.div`
25 | display: flex;
26 | flex-direction: column;
27 | justify-content: center;
28 | align-items: center;
29 | max-width: 960px;
30 | width: 90vw;
31 | padding: 15px;
32 | border-radius: 4px;
33 | box-shadow: 0 0 4px 0 #777;
34 | `;
35 |
36 | const AppNavBar = styled.nav`
37 | margin: 0;
38 | padding: 0;
39 | color: white;
40 | position: fixed;
41 | top: 0;
42 | left: 0;
43 | right: 0;
44 | display: flex;
45 | justify-content: space-between;
46 | align-items: center;
47 | background: ${({ theme }) => theme.primary};
48 | @media (max-width: 991px) {
49 | flex-direction: column;
50 | }
51 | `;
52 |
53 | const logoStyle = { paddingLeft: '30px' };
54 |
55 | const titleStyle = { margin: '0', padding: '0' };
56 |
57 | class App extends Component {
58 | state = {
59 | view: 'search',
60 | footerInfo: null
61 | };
62 |
63 | updateInfo() {
64 | fetch(ENDPOINT_INFO)
65 | .then((response) => response.json())
66 | .then(({ ok, numPRs, prRange, lastUpdate }) => {
67 | if (ok) {
68 | const footerInfo = { numPRs, prRange, lastUpdate };
69 | this.setState((prevState) => ({ footerInfo }));
70 | }
71 | })
72 | .catch(() => {
73 | // do nothing
74 | });
75 | }
76 |
77 | handleViewChange = ({ target: { id } }) => {
78 | const view = id.replace('tabs-', '');
79 | this.setState((prevState) => ({ ...this.clearObj, view }));
80 | if (view === 'reports' || view === 'search') {
81 | this.updateInfo();
82 | }
83 | };
84 |
85 | componentDidMount() {
86 | this.updateInfo();
87 | }
88 |
89 | render() {
90 | const {
91 | handleViewChange,
92 | state: { view, footerInfo }
93 | } = this;
94 | return (
95 | <>
96 |
97 |
103 |
104 |
105 | Contributor Tools
106 |
117 |
118 |
119 |
120 |
121 | {view === 'search' && }
122 | {view === 'reports' && }
123 | {view === 'boilerplates' && (
124 | repo._id.includes('boilerplate')}
127 | />
128 | )}
129 | {view === 'other' && (
130 |
133 | !repo._id.includes('boilerplate') &&
134 | repo._id !== 'freeCodeCamp'
135 | }
136 | />
137 | )}
138 |
139 | {footerInfo && }
140 |
141 | >
142 | );
143 | }
144 | }
145 | export default App;
146 |
--------------------------------------------------------------------------------
/one-off-scripts/add-comment-on-frontmatter-issues.js:
--------------------------------------------------------------------------------
1 | /*
2 | This is a one-off script to run on all open PRs to add
3 | a comment and "status: needs update" label to any PR with guide articles which
4 | have frontmatter issues.
5 | */
6 |
7 | const fetch = require('node-fetch');
8 | const { getPRs, getUserInput, getFiles } = require('../lib/get-prs');
9 | const { addLabels, addComment } = require('../lib/pr-tasks');
10 | const { rateLimiter, ProcessingLog } = require('../lib/utils');
11 | const {
12 | frontmatterCheck
13 | } = require('../lib/validation/guide-folder-checks/frontmatter-check');
14 | const {
15 | createErrorMsg
16 | } = require('../lib/validation/guide-folder-checks/create-error-msg');
17 |
18 | const {
19 | github: { freeCodeCampRepo, defaultBase },
20 | oneoff: { productionRun }
21 | } = require('../config');
22 |
23 | const allowedLangDirNames = [
24 | 'arabic',
25 | 'chinese',
26 | 'english',
27 | 'portuguese',
28 | 'russian',
29 | 'spanish'
30 | ];
31 |
32 | const log = new ProcessingLog('all-frontmatter-checks');
33 |
34 | const labeler = async (
35 | number,
36 | prFiles,
37 | currentLabels,
38 | guideFolderErrorsComment
39 | ) => {
40 | // holds potential labels to add based on file path
41 | const labelsToAdd = {};
42 | if (guideFolderErrorsComment) {
43 | labelsToAdd['status: needs update'] = 1;
44 | }
45 | const existingLabels = currentLabels.map(({ name }) => name);
46 |
47 | /* only adds needed labels which are NOT currently on the PR. */
48 | const newLabels = Object.keys(labelsToAdd).filter((label) => {
49 | return !existingLabels.includes(label);
50 | });
51 | if (newLabels.length) {
52 | if (productionRun) {
53 | addLabels(number, newLabels);
54 | await rateLimiter();
55 | }
56 | }
57 | return newLabels;
58 | };
59 |
60 | const checkPath = (fullPath, fileContent) => {
61 | let errorMsgs = [];
62 | const remaining = fullPath.split('/');
63 | const isTranslation =
64 | allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english';
65 | const frontMatterErrMsgs = frontmatterCheck(
66 | fullPath,
67 | isTranslation,
68 | fileContent
69 | );
70 | return errorMsgs.concat(frontMatterErrMsgs);
71 | };
72 |
73 | const guideFolderChecks = async (number, prFiles, user) => {
74 | let prErrors = [];
75 | for (let { filename: fullPath, raw_url: fileUrl } of prFiles) {
76 | let newErrors;
77 | if (/^guide\//.test(fullPath)) {
78 | const response = await fetch(fileUrl);
79 | const fileContent = await response.text();
80 | newErrors = checkPath(fullPath, fileContent);
81 | }
82 | if (newErrors) {
83 | prErrors = prErrors.concat(newErrors);
84 | }
85 | }
86 |
87 | if (prErrors.length) {
88 | const comment = createErrorMsg(prErrors, user);
89 | if (productionRun) {
90 | await addComment(number, comment);
91 | await rateLimiter();
92 | }
93 | return comment;
94 | } else {
95 | return null;
96 | }
97 | };
98 |
99 | (async () => {
100 | const { totalPRs, firstPR, lastPR } = await getUserInput(
101 | freeCodeCampRepo,
102 | defaultBase
103 | );
104 | const prPropsToGet = ['number', 'labels', 'user'];
105 | const { openPRs } = await getPRs(
106 | freeCodeCampRepo,
107 | defaultBase,
108 | totalPRs,
109 | firstPR,
110 | lastPR,
111 | prPropsToGet
112 | );
113 |
114 | log.start();
115 | console.log('Starting frontmatter checks process...');
116 | let count = 0;
117 | for (let i = 0; i < openPRs.length; i++) {
118 | if (openPRs.length) {
119 | let {
120 | number,
121 | labels: currentLabels,
122 | user: { login: username }
123 | } = openPRs[count];
124 |
125 | const prFiles = await getFiles(freeCodeCampRepo, number);
126 | if (count > 4000) {
127 | await rateLimiter(2350);
128 | }
129 | const guideFolderErrorsComment = await guideFolderChecks(
130 | number,
131 | prFiles,
132 | username
133 | );
134 | const commentLogVal = guideFolderErrorsComment
135 | ? guideFolderErrorsComment
136 | : 'none';
137 |
138 | const labelsAdded = await labeler(
139 | number,
140 | prFiles,
141 | currentLabels,
142 | guideFolderErrorsComment
143 | );
144 | const labelLogVal = labelsAdded.length ? labelsAdded : 'none added';
145 |
146 | log.add(number, { number, comment: commentLogVal, labels: labelLogVal });
147 | }
148 | }
149 | })()
150 | .then(() => {
151 | log.finish();
152 | console.log('Successfully completed frontmatter checks');
153 | })
154 | .catch((err) => {
155 | log.finish();
156 | console.log(err);
157 | });
158 |
--------------------------------------------------------------------------------
/lib/get-prs/index.js:
--------------------------------------------------------------------------------
1 | const { Octokit } = require('@octokit/rest');
2 | const _cliProgress = require('cli-progress');
3 |
4 | const {
5 | github: { owner, secret }
6 | } = require('../config');
7 |
8 | const octokit = new Octokit({ auth: secret });
9 |
10 | const { getRange, getCount } = require('./pr-stats');
11 |
12 | /* eslint-disable camelcase */
13 | const prsPaginate = async (
14 | repo,
15 | base,
16 | method,
17 | firstPR,
18 | lastPR,
19 | prPropsToGet
20 | ) => {
21 | let methodProps = {
22 | owner,
23 | repo,
24 | state: 'open',
25 | sort: 'created',
26 | direction: 'asc',
27 | page: 1,
28 | per_page: 100
29 | };
30 |
31 | if (base) {
32 | methodProps = { ...methodProps, base };
33 | }
34 |
35 | const prFilter = (prs, first, last, prPropsToGet) => {
36 | const filtered = [];
37 | for (let pr of prs) {
38 | if (pr.number >= first && pr.number <= last) {
39 | const propsObj = prPropsToGet.reduce((obj, prop) => {
40 | obj[prop] = pr[prop];
41 | return obj;
42 | }, {});
43 | filtered.push(propsObj);
44 | }
45 | if (pr.number >= last) {
46 | done = true;
47 | return filtered;
48 | }
49 | }
50 | return filtered;
51 | };
52 |
53 | // will be true when lastPR is seen in paginated results
54 | let done = false;
55 | let response = await method(methodProps);
56 | console.log('x-ratelimit-remaining:', response.meta['x-ratelimit-remaining']);
57 | let { data } = response;
58 | data = prFilter(data, firstPR, lastPR, prPropsToGet);
59 | while (octokit.hasNextPage(response) && !done) {
60 | response = await octokit.getNextPage(response);
61 | console.log(
62 | 'x-ratelimit-remaining:',
63 | response.meta['x-ratelimit-remaining']
64 | );
65 | let dataFiltered = prFilter(response.data, firstPR, lastPR, prPropsToGet);
66 | data = data.concat(dataFiltered);
67 | // progressBar.increment(dataFiltered.length);
68 | }
69 | return data;
70 | };
71 |
72 | const getUserInput = async (repo, base, rangeType = '') => {
73 | let data, firstPR, lastPR;
74 | if (rangeType === 'all') {
75 | data = await getRange(repo, base).then((data) => data);
76 | firstPR = data[0];
77 | lastPR = data[1];
78 | } else {
79 | let [type, start, end] = process.argv.slice(2);
80 | data = await getRange(repo, base).then((data) => data);
81 | firstPR = data[0];
82 | lastPR = data[1];
83 | if (type !== 'all' && type !== 'range') {
84 | throw 'Please specify either all or range for 1st arg.';
85 | }
86 | if (type === 'range') {
87 | start = parseInt(start, 10);
88 | end = parseInt(end, 10);
89 | if (!start || !end) {
90 | throw 'Specify both a starting PR # (2nd arg) and ending PR # (3rd arg).';
91 | }
92 | if (start > end) {
93 | throw 'Starting PR # must be less than or equal to end PR #.';
94 | }
95 | if (start < firstPR) {
96 | throw `Starting PR # can not be less than first open PR # (${firstPR})`;
97 | }
98 | firstPR = start;
99 | if (end > lastPR) {
100 | throw `Ending PR # can not be greater than last open PR # (${lastPR})`;
101 | }
102 | lastPR = end;
103 | }
104 | }
105 | // A null value for firstPR or lastPR indicates the repo had no open PRs
106 | if (firstPR === null || lastPR === null) {
107 | return { totalPRs: 0, firstPR, lastPR };
108 | }
109 | const totalPRs = await getCount(repo, base).then((data) => data);
110 | return { totalPRs, firstPR, lastPR };
111 | };
112 |
113 | const getPRs = async (repo, base, totalPRs, firstPR, lastPR, prPropsToGet) => {
114 | let progressText = `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] `;
115 | progressText += '{percentage}% | Elapsed Time: {duration_formatted} ';
116 | progressText += '| ETA: {eta_formatted}';
117 | const getPRsBar = new _cliProgress.Bar(
118 | {
119 | format: progressText,
120 | etaBuffer: 50
121 | },
122 | _cliProgress.Presets.shades_classic
123 | );
124 | // getPRsBar.start(totalPRs, 0);
125 | let openPRs = await prsPaginate(
126 | repo,
127 | base,
128 | octokit.pulls.list,
129 | firstPR,
130 | lastPR,
131 | prPropsToGet,
132 | getPRsBar
133 | );
134 | // getPRsBar.update(totalPRs);
135 | // getPRsBar.stop();
136 | console.log(`# of PRs retrieved: ${openPRs.length}`);
137 | return { firstPR, lastPR, openPRs };
138 | };
139 |
140 | const filesPaginate = async (repo, number) => {
141 | let methodProps = {
142 | owner,
143 | state: 'open',
144 | sort: 'created',
145 | direction: 'asc',
146 | page: 1,
147 | per_page: 100
148 | };
149 |
150 | if (repo) {
151 | methodProps = { ...methodProps, repo };
152 | }
153 |
154 | let response = await octokit.pulls.listFiles({
155 | number,
156 | ...methodProps
157 | });
158 |
159 | let { data } = response;
160 | while (octokit.hasNextPage(response)) {
161 | response = await octokit.getNextPage(response);
162 | let { data: moreData } = response;
163 | data = data.concat(moreData);
164 | }
165 | return data;
166 | };
167 |
168 | const getFiles = async (repo, number) => await filesPaginate(repo, number);
169 |
170 | const getFilenames = async (repo, number) =>
171 | (await getFiles(repo, number)).map(({ filename }) => filename);
172 |
173 | module.exports = { getPRs, getUserInput, getFiles, getFilenames };
174 |
--------------------------------------------------------------------------------
/dashboard-app/client/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then((registration) => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch((error) => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then((response) => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then((registration) => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then((registration) => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/dashboard-app/client/src/components/Pareto.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import ListItem from './ListItem';
5 | import FullWidthDiv from './FullWidthDiv';
6 | import Result from './Result';
7 | import FilterOption from './FilterOption';
8 | import { ENDPOINT_PARETO } from '../constants';
9 |
10 | const List = styled.div`
11 | margin: 5px;
12 | display: flex;
13 | flex-direction: column;
14 | `;
15 |
16 | const Options = styled.div`
17 | display: flex;
18 | `;
19 |
20 | const detailsStyle = { padding: '3px' };
21 | const filenameTitle = { fontWeight: '600' };
22 |
23 | class Pareto extends React.Component {
24 | state = {
25 | data: [],
26 | all: [],
27 | selectedFileType: 'all',
28 | selectedLanguage: 'all',
29 | options: {},
30 | rateLimitMessage: ''
31 | };
32 |
33 | componentDidMount() {
34 | fetch(ENDPOINT_PARETO)
35 | .then((response) => response.json())
36 | .then(({ ok, rateLimitMessage, pareto }) => {
37 | if (ok) {
38 | if (!pareto.length) {
39 | pareto.push({
40 | filename: 'Nothing to show in Pareto Report',
41 | count: 0,
42 | prs: []
43 | });
44 | }
45 |
46 | this.setState((prevState) => ({
47 | data: pareto,
48 | all: [...pareto],
49 | options: this.createOptions(pareto)
50 | }));
51 | } else if (rateLimitMessage) {
52 | this.setState((prevState) => ({
53 | rateLimitMessage
54 | }));
55 | }
56 | })
57 | .catch(() => {
58 | const pareto = [
59 | { filename: 'Nothing to show in Pareto Report', count: 0, prs: [] }
60 | ];
61 | this.setState((prevState) => ({ data: pareto }));
62 | });
63 | }
64 |
65 | createOptions = (data) => {
66 | const options = data.reduce((seen, { filename }) => {
67 | const { articleType, language } = this.getFilenameOptions(filename);
68 | if (articleType && language) {
69 | if (!seen.hasOwnProperty(articleType)) {
70 | seen[articleType] = {};
71 | }
72 | seen[articleType][language] = true;
73 | }
74 | return seen;
75 | }, {});
76 | return options;
77 | };
78 |
79 | handleFileTypeOptionChange = (changeEvent) => {
80 | let { all, selectedLanguage, options } = this.state;
81 | const selectedFileType = changeEvent.target.value;
82 |
83 | let data = [...all].filter(({ filename }) => {
84 | const { articleType, language } = this.getFilenameOptions(filename);
85 | let condition;
86 | if (selectedFileType === 'all') {
87 | condition = true;
88 | selectedLanguage = 'all';
89 | } else {
90 | if (selectedLanguage === 'all') {
91 | condition = articleType === selectedFileType;
92 | } else if (!options[selectedFileType][selectedLanguage]) {
93 | condition = articleType === selectedFileType;
94 | selectedLanguage = 'all';
95 | } else {
96 | condition =
97 | articleType === selectedFileType && language === selectedLanguage;
98 | }
99 | }
100 | return condition;
101 | });
102 | this.setState((prevState) => ({
103 | data,
104 | selectedFileType,
105 | selectedLanguage
106 | }));
107 | };
108 |
109 | handleLanguageOptionChange = (changeEvent) => {
110 | const { all, selectedFileType } = this.state;
111 | const selectedLanguage = changeEvent.target.value;
112 | let data = [...all].filter(({ filename }) => {
113 | const { articleType, language } = this.getFilenameOptions(filename);
114 | let condition;
115 | if (selectedLanguage === 'all') {
116 | condition = articleType === selectedFileType;
117 | } else {
118 | condition =
119 | language === selectedLanguage && articleType === selectedFileType;
120 | }
121 | return condition;
122 | });
123 | this.setState((prevState) => ({ data, selectedLanguage }));
124 | };
125 |
126 | getFilenameOptions = (filename) => {
127 | const filenameReplacement = filename.replace(
128 | /^curriculum\/challenges\//,
129 | 'curriculum/'
130 | );
131 | const regex =
132 | /^(docs|curriculum|guide)(?:\/)(english|arabic|chinese|portuguese|russian|spanish)?\/?/;
133 | // need an array to pass to labelsAdder
134 | // eslint-disable-next-line
135 | const [_, articleType, language] = filenameReplacement.match(regex) || [];
136 | return { articleType, language };
137 | };
138 |
139 | render() {
140 | const {
141 | data,
142 | options,
143 | selectedFileType,
144 | selectedLanguage,
145 | rateLimitMessage
146 | } = this.state;
147 | const elements = rateLimitMessage
148 | ? rateLimitMessage
149 | : data.map((entry) => {
150 | const { filename, count, prs } = entry;
151 | const prsList = prs.map(({ number, username, title }) => {
152 | return (
153 |
159 | );
160 | });
161 | const fileOnMain = `https://github.com/freeCodeCamp/freeCodeCamp/blob/main/${filename}`;
162 | return (
163 |
164 | {filename}{' '}
165 |
166 | (File on Main)
167 |
168 |
169 |
170 | # of PRs: {count}
171 | {prsList}
172 |
173 |
174 | );
175 | });
176 |
177 | let fileTypeOptions = Object.keys(options).map(
178 | (articleType) => articleType
179 | );
180 | const typeOptions = ['all', ...fileTypeOptions].map((type) => (
181 |
188 | {type.charAt().toUpperCase() + type.slice(1)}
189 |
190 | ));
191 |
192 | let languageOptions = null;
193 | if (selectedFileType !== 'all') {
194 | let languages = Object.keys(options[selectedFileType]);
195 | languages = ['all', ...languages.sort()];
196 | languageOptions = languages.map((language) => (
197 |
204 | {language.charAt().toUpperCase() + language.slice(1)}
205 |
206 | ));
207 | }
208 | return (
209 |
210 | {fileTypeOptions.length > 0 && Filter Options}
211 |
212 | {fileTypeOptions.length > 0 && (
213 | <>
214 |
218 | >
219 | )}
220 | {languageOptions && (
221 |
225 | )}
226 |
227 | {rateLimitMessage
228 | ? rateLimitMessage
229 | : data.length
230 | ? elements
231 | : 'Report Loading...'}
232 |
233 | );
234 | }
235 | }
236 |
237 | export default Pareto;
238 |
--------------------------------------------------------------------------------
/one-off-scripts/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@freecodecamp/contributor-tools-one-off-scripts",
3 | "version": "0.0.1",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "@freecodecamp/contributor-tools-one-off-scripts",
9 | "version": "0.0.1",
10 | "license": "BSD-3-Clause",
11 | "dependencies": {
12 | "@octokit/rest": "18.5.2",
13 | "cli-progress": "3.6.1",
14 | "dedent": "0.7.0",
15 | "path": "0.12.7"
16 | },
17 | "devDependencies": {},
18 | "engines": {
19 | "node": ">=16",
20 | "npm": ">=8"
21 | }
22 | },
23 | "node_modules/@octokit/auth-token": {
24 | "version": "2.4.5",
25 | "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
26 | "integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
27 | "dependencies": {
28 | "@octokit/types": "^6.0.3"
29 | }
30 | },
31 | "node_modules/@octokit/core": {
32 | "version": "3.3.1",
33 | "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.3.1.tgz",
34 | "integrity": "sha512-Dc5NNQOYjgZU5S1goN6A/E500yXOfDUFRGQB8/2Tl16AcfvS3H9PudyOe3ZNE/MaVyHPIfC0htReHMJb1tMrvw==",
35 | "dependencies": {
36 | "@octokit/auth-token": "^2.4.4",
37 | "@octokit/graphql": "^4.5.8",
38 | "@octokit/request": "^5.4.12",
39 | "@octokit/request-error": "^2.0.5",
40 | "@octokit/types": "^6.0.3",
41 | "before-after-hook": "^2.2.0",
42 | "universal-user-agent": "^6.0.0"
43 | }
44 | },
45 | "node_modules/@octokit/endpoint": {
46 | "version": "6.0.11",
47 | "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz",
48 | "integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==",
49 | "dependencies": {
50 | "@octokit/types": "^6.0.3",
51 | "is-plain-object": "^5.0.0",
52 | "universal-user-agent": "^6.0.0"
53 | }
54 | },
55 | "node_modules/@octokit/graphql": {
56 | "version": "4.6.1",
57 | "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.1.tgz",
58 | "integrity": "sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA==",
59 | "dependencies": {
60 | "@octokit/request": "^5.3.0",
61 | "@octokit/types": "^6.0.3",
62 | "universal-user-agent": "^6.0.0"
63 | }
64 | },
65 | "node_modules/@octokit/openapi-types": {
66 | "version": "6.0.0",
67 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz",
68 | "integrity": "sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ=="
69 | },
70 | "node_modules/@octokit/plugin-paginate-rest": {
71 | "version": "2.13.3",
72 | "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz",
73 | "integrity": "sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==",
74 | "dependencies": {
75 | "@octokit/types": "^6.11.0"
76 | },
77 | "peerDependencies": {
78 | "@octokit/core": ">=2"
79 | }
80 | },
81 | "node_modules/@octokit/plugin-request-log": {
82 | "version": "1.0.3",
83 | "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz",
84 | "integrity": "sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ==",
85 | "peerDependencies": {
86 | "@octokit/core": ">=3"
87 | }
88 | },
89 | "node_modules/@octokit/plugin-rest-endpoint-methods": {
90 | "version": "5.0.0",
91 | "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz",
92 | "integrity": "sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==",
93 | "dependencies": {
94 | "@octokit/types": "^6.13.0",
95 | "deprecation": "^2.3.1"
96 | },
97 | "peerDependencies": {
98 | "@octokit/core": ">=3"
99 | }
100 | },
101 | "node_modules/@octokit/request": {
102 | "version": "5.4.14",
103 | "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.14.tgz",
104 | "integrity": "sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==",
105 | "dependencies": {
106 | "@octokit/endpoint": "^6.0.1",
107 | "@octokit/request-error": "^2.0.0",
108 | "@octokit/types": "^6.7.1",
109 | "deprecation": "^2.0.0",
110 | "is-plain-object": "^5.0.0",
111 | "node-fetch": "^2.6.1",
112 | "once": "^1.4.0",
113 | "universal-user-agent": "^6.0.0"
114 | }
115 | },
116 | "node_modules/@octokit/request-error": {
117 | "version": "2.0.5",
118 | "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz",
119 | "integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==",
120 | "dependencies": {
121 | "@octokit/types": "^6.0.3",
122 | "deprecation": "^2.0.0",
123 | "once": "^1.4.0"
124 | }
125 | },
126 | "node_modules/@octokit/rest": {
127 | "version": "18.5.2",
128 | "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.2.tgz",
129 | "integrity": "sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==",
130 | "dependencies": {
131 | "@octokit/core": "^3.2.3",
132 | "@octokit/plugin-paginate-rest": "^2.6.2",
133 | "@octokit/plugin-request-log": "^1.0.2",
134 | "@octokit/plugin-rest-endpoint-methods": "5.0.0"
135 | }
136 | },
137 | "node_modules/@octokit/types": {
138 | "version": "6.13.0",
139 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz",
140 | "integrity": "sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==",
141 | "dependencies": {
142 | "@octokit/openapi-types": "^6.0.0"
143 | }
144 | },
145 | "node_modules/ansi-regex": {
146 | "version": "5.0.1",
147 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
148 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
149 | "engines": {
150 | "node": ">=8"
151 | }
152 | },
153 | "node_modules/before-after-hook": {
154 | "version": "2.2.0",
155 | "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.0.tgz",
156 | "integrity": "sha512-jH6rKQIfroBbhEXVmI7XmXe3ix5S/PgJqpzdDPnR8JGLHWNYLsYZ6tK5iWOF/Ra3oqEX0NobXGlzbiylIzVphQ=="
157 | },
158 | "node_modules/cli-progress": {
159 | "version": "3.6.1",
160 | "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.6.1.tgz",
161 | "integrity": "sha512-OVRgcyeI0viJW47MnyS10Jw/0RTpk7wwNbrCOPyXT0TVi2o3Q/u+Os8vQUFYhvkdXSbguSdFvMv1ia+UuwgIQQ==",
162 | "dependencies": {
163 | "colors": "^1.1.2",
164 | "string-width": "^4.2.0"
165 | },
166 | "engines": {
167 | "node": ">=4"
168 | }
169 | },
170 | "node_modules/colors": {
171 | "version": "1.4.0",
172 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
173 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
174 | "engines": {
175 | "node": ">=0.1.90"
176 | }
177 | },
178 | "node_modules/dedent": {
179 | "version": "0.7.0",
180 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
181 | "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw="
182 | },
183 | "node_modules/deprecation": {
184 | "version": "2.3.1",
185 | "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
186 | "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
187 | },
188 | "node_modules/emoji-regex": {
189 | "version": "8.0.0",
190 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
191 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
192 | },
193 | "node_modules/inherits": {
194 | "version": "2.0.3",
195 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
196 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
197 | },
198 | "node_modules/is-fullwidth-code-point": {
199 | "version": "3.0.0",
200 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
201 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
202 | "engines": {
203 | "node": ">=8"
204 | }
205 | },
206 | "node_modules/is-plain-object": {
207 | "version": "5.0.0",
208 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
209 | "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
210 | "engines": {
211 | "node": ">=0.10.0"
212 | }
213 | },
214 | "node_modules/node-fetch": {
215 | "version": "2.6.1",
216 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
217 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
218 | "engines": {
219 | "node": "4.x || >=6.0.0"
220 | }
221 | },
222 | "node_modules/once": {
223 | "version": "1.4.0",
224 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
225 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
226 | "dependencies": {
227 | "wrappy": "1"
228 | }
229 | },
230 | "node_modules/path": {
231 | "version": "0.12.7",
232 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
233 | "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=",
234 | "dependencies": {
235 | "process": "^0.11.1",
236 | "util": "^0.10.3"
237 | }
238 | },
239 | "node_modules/process": {
240 | "version": "0.11.10",
241 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
242 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
243 | "engines": {
244 | "node": ">= 0.6.0"
245 | }
246 | },
247 | "node_modules/string-width": {
248 | "version": "4.2.3",
249 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
250 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
251 | "dependencies": {
252 | "emoji-regex": "^8.0.0",
253 | "is-fullwidth-code-point": "^3.0.0",
254 | "strip-ansi": "^6.0.1"
255 | },
256 | "engines": {
257 | "node": ">=8"
258 | }
259 | },
260 | "node_modules/strip-ansi": {
261 | "version": "6.0.1",
262 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
263 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
264 | "dependencies": {
265 | "ansi-regex": "^5.0.1"
266 | },
267 | "engines": {
268 | "node": ">=8"
269 | }
270 | },
271 | "node_modules/universal-user-agent": {
272 | "version": "6.0.0",
273 | "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
274 | "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
275 | },
276 | "node_modules/util": {
277 | "version": "0.10.4",
278 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
279 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
280 | "dependencies": {
281 | "inherits": "2.0.3"
282 | }
283 | },
284 | "node_modules/wrappy": {
285 | "version": "1.0.2",
286 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
287 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
288 | }
289 | },
290 | "dependencies": {
291 | "@octokit/auth-token": {
292 | "version": "2.4.5",
293 | "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
294 | "integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
295 | "requires": {
296 | "@octokit/types": "^6.0.3"
297 | }
298 | },
299 | "@octokit/core": {
300 | "version": "3.3.1",
301 | "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.3.1.tgz",
302 | "integrity": "sha512-Dc5NNQOYjgZU5S1goN6A/E500yXOfDUFRGQB8/2Tl16AcfvS3H9PudyOe3ZNE/MaVyHPIfC0htReHMJb1tMrvw==",
303 | "requires": {
304 | "@octokit/auth-token": "^2.4.4",
305 | "@octokit/graphql": "^4.5.8",
306 | "@octokit/request": "^5.4.12",
307 | "@octokit/request-error": "^2.0.5",
308 | "@octokit/types": "^6.0.3",
309 | "before-after-hook": "^2.2.0",
310 | "universal-user-agent": "^6.0.0"
311 | }
312 | },
313 | "@octokit/endpoint": {
314 | "version": "6.0.11",
315 | "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.11.tgz",
316 | "integrity": "sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==",
317 | "requires": {
318 | "@octokit/types": "^6.0.3",
319 | "is-plain-object": "^5.0.0",
320 | "universal-user-agent": "^6.0.0"
321 | }
322 | },
323 | "@octokit/graphql": {
324 | "version": "4.6.1",
325 | "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.6.1.tgz",
326 | "integrity": "sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA==",
327 | "requires": {
328 | "@octokit/request": "^5.3.0",
329 | "@octokit/types": "^6.0.3",
330 | "universal-user-agent": "^6.0.0"
331 | }
332 | },
333 | "@octokit/openapi-types": {
334 | "version": "6.0.0",
335 | "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz",
336 | "integrity": "sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ=="
337 | },
338 | "@octokit/plugin-paginate-rest": {
339 | "version": "2.13.3",
340 | "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz",
341 | "integrity": "sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==",
342 | "requires": {
343 | "@octokit/types": "^6.11.0"
344 | }
345 | },
346 | "@octokit/plugin-request-log": {
347 | "version": "1.0.3",
348 | "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz",
349 | "integrity": "sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ==",
350 | "requires": {}
351 | },
352 | "@octokit/plugin-rest-endpoint-methods": {
353 | "version": "5.0.0",
354 | "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz",
355 | "integrity": "sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==",
356 | "requires": {
357 | "@octokit/types": "^6.13.0",
358 | "deprecation": "^2.3.1"
359 | }
360 | },
361 | "@octokit/request": {
362 | "version": "5.4.14",
363 | "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.4.14.tgz",
364 | "integrity": "sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==",
365 | "requires": {
366 | "@octokit/endpoint": "^6.0.1",
367 | "@octokit/request-error": "^2.0.0",
368 | "@octokit/types": "^6.7.1",
369 | "deprecation": "^2.0.0",
370 | "is-plain-object": "^5.0.0",
371 | "node-fetch": "^2.6.1",
372 | "once": "^1.4.0",
373 | "universal-user-agent": "^6.0.0"
374 | }
375 | },
376 | "@octokit/request-error": {
377 | "version": "2.0.5",
378 | "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz",
379 | "integrity": "sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==",
380 | "requires": {
381 | "@octokit/types": "^6.0.3",
382 | "deprecation": "^2.0.0",
383 | "once": "^1.4.0"
384 | }
385 | },
386 | "@octokit/rest": {
387 | "version": "18.5.2",
388 | "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.2.tgz",
389 | "integrity": "sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==",
390 | "requires": {
391 | "@octokit/core": "^3.2.3",
392 | "@octokit/plugin-paginate-rest": "^2.6.2",
393 | "@octokit/plugin-request-log": "^1.0.2",
394 | "@octokit/plugin-rest-endpoint-methods": "5.0.0"
395 | }
396 | },
397 | "@octokit/types": {
398 | "version": "6.13.0",
399 | "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz",
400 | "integrity": "sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==",
401 | "requires": {
402 | "@octokit/openapi-types": "^6.0.0"
403 | }
404 | },
405 | "ansi-regex": {
406 | "version": "5.0.1",
407 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
408 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
409 | },
410 | "before-after-hook": {
411 | "version": "2.2.0",
412 | "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.0.tgz",
413 | "integrity": "sha512-jH6rKQIfroBbhEXVmI7XmXe3ix5S/PgJqpzdDPnR8JGLHWNYLsYZ6tK5iWOF/Ra3oqEX0NobXGlzbiylIzVphQ=="
414 | },
415 | "cli-progress": {
416 | "version": "3.6.1",
417 | "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.6.1.tgz",
418 | "integrity": "sha512-OVRgcyeI0viJW47MnyS10Jw/0RTpk7wwNbrCOPyXT0TVi2o3Q/u+Os8vQUFYhvkdXSbguSdFvMv1ia+UuwgIQQ==",
419 | "requires": {
420 | "colors": "^1.1.2",
421 | "string-width": "^4.2.0"
422 | }
423 | },
424 | "colors": {
425 | "version": "1.4.0",
426 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
427 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
428 | },
429 | "dedent": {
430 | "version": "0.7.0",
431 | "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
432 | "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw="
433 | },
434 | "deprecation": {
435 | "version": "2.3.1",
436 | "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
437 | "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
438 | },
439 | "emoji-regex": {
440 | "version": "8.0.0",
441 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
442 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
443 | },
444 | "inherits": {
445 | "version": "2.0.3",
446 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
447 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
448 | },
449 | "is-fullwidth-code-point": {
450 | "version": "3.0.0",
451 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
452 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
453 | },
454 | "is-plain-object": {
455 | "version": "5.0.0",
456 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
457 | "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
458 | },
459 | "node-fetch": {
460 | "version": "2.6.1",
461 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
462 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
463 | },
464 | "once": {
465 | "version": "1.4.0",
466 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
467 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
468 | "requires": {
469 | "wrappy": "1"
470 | }
471 | },
472 | "path": {
473 | "version": "0.12.7",
474 | "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
475 | "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=",
476 | "requires": {
477 | "process": "^0.11.1",
478 | "util": "^0.10.3"
479 | }
480 | },
481 | "process": {
482 | "version": "0.11.10",
483 | "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
484 | "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
485 | },
486 | "string-width": {
487 | "version": "4.2.3",
488 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
489 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
490 | "requires": {
491 | "emoji-regex": "^8.0.0",
492 | "is-fullwidth-code-point": "^3.0.0",
493 | "strip-ansi": "^6.0.1"
494 | }
495 | },
496 | "strip-ansi": {
497 | "version": "6.0.1",
498 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
499 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
500 | "requires": {
501 | "ansi-regex": "^5.0.1"
502 | }
503 | },
504 | "universal-user-agent": {
505 | "version": "6.0.0",
506 | "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
507 | "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
508 | },
509 | "util": {
510 | "version": "0.10.4",
511 | "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
512 | "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
513 | "requires": {
514 | "inherits": "2.0.3"
515 | }
516 | },
517 | "wrappy": {
518 | "version": "1.0.2",
519 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
520 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
521 | }
522 | }
523 | }
524 |
--------------------------------------------------------------------------------
/dashboard-app/client/src/assets/freeCodeCampLogo.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | import React from 'react';
3 |
4 | function freeCodeCampLogo() {
5 | return (
6 |
109 | );
110 | }
111 |
112 | freeCodeCampLogo.displayName = 'freeCodeCampLogo';
113 |
114 | export default freeCodeCampLogo;
115 |
--------------------------------------------------------------------------------