refactor(lint): apply linting from the tools (#39)

This commit is contained in:
mrugesh mohapatra
2018-12-28 00:59:42 +05:30
committed by Randell Dawson
parent 00e5cf247e
commit 8be7d12cb3
54 changed files with 71356 additions and 10392 deletions

View File

@ -1,5 +1,3 @@
const fs = require('fs');
let data = require('./data.json'); let data = require('./data.json');
const Container = { const Container = {

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
const router = require('express').Router(); const router = require('express').Router();
const container = require ('../data'); const container = require('../data');
router.get('/', (request, response) => { router.get('/', (request, response) => {
response.json(container.data); response.json(container.data);

View File

@ -2,7 +2,7 @@ const router = require('express').Router();
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const container = require ('../data'); const container = require('../data');
router.get('/', (request, response) => { router.get('/', (request, response) => {
const { prs, startTime } = container.data; const { prs, startTime } = container.data;

View File

@ -1,6 +1,6 @@
const router = require('express').Router(); const router = require('express').Router();
const container = require ('../data'); const container = require('../data');
router.get('/', (reqeust, response) => { router.get('/', (reqeust, response) => {
const { indices, prs } = container.data; const { indices, prs } = container.data;
@ -8,13 +8,15 @@ router.get('/', (reqeust, response) => {
const reportObj = prs.reduce((obj, pr) => { const reportObj = prs.reduce((obj, pr) => {
const { number, filenames, username, title } = pr; const { number, filenames, username, title } = pr;
filenames.forEach((filename) => { filenames.forEach(filename => {
if (obj[filename]) { if (obj[filename]) {
const { count, prs } = obj[filename]; const { count, prs } = obj[filename];
obj[filename] = { count: count + 1, prs: prs.concat({ number, username, title } ) }; obj[filename] = {
} count: count + 1,
else { prs: prs.concat({ number, username, title })
obj[filename] = { count: 1, prs: [ { number, username, title } ] }; };
} else {
obj[filename] = { count: 1, prs: [{ number, username, title }] };
} }
}); });
return obj; return obj;
@ -29,7 +31,7 @@ router.get('/', (reqeust, response) => {
}, []) }, [])
.sort((a, b) => b.count - a.count); .sort((a, b) => b.count - a.count);
response.json({ ok: true, pareto }); response.json({ ok: true, pareto });
}); });
module.exports = router; module.exports = router;

View File

@ -8,7 +8,11 @@ router.get('/:number', (request, response) => {
const index = indices[refNumber]; const index = indices[refNumber];
if (!index && index !== 0) { if (!index && index !== 0) {
response.json({ ok: true, message: 'Unable to find that open PR #.', results: [] }); response.json({
ok: true,
message: 'Unable to find that open PR #.',
results: []
});
return; return;
} }
@ -18,7 +22,7 @@ router.get('/:number', (request, response) => {
prs.forEach(({ number, filenames, username, title }) => { prs.forEach(({ number, filenames, username, title }) => {
if (number != refNumber) { if (number != refNumber) {
const matchedFilenames = filenames.filter((filename) => { const matchedFilenames = filenames.filter(filename => {
return refFilenames.includes(filename); return refFilenames.includes(filename);
}); });
@ -29,7 +33,11 @@ router.get('/:number', (request, response) => {
}); });
if (!results.length) { if (!results.length) {
response.json({ ok: true, message: `No other open PRs with at least one filename which PR #${refNumber} has.`, results: [] }); response.json({
ok: true,
message: `No other open PRs with at least one filename which PR #${refNumber} has.`,
results: []
});
return; return;
} }

View File

@ -1,16 +1,16 @@
const router = require('express').Router(); const router = require('express').Router();
const container = require ('../data'); const container = require('../data');
router.get('/', (request, response) => { router.get('/', (request, response) => {
const { indices, prs } = container.data; const { indices, prs } = container.data;
const value = request.query.value; const value = request.query.value;
if (value) { if (value) {
const filesFound = {}; const filesFound = {};
prs.forEach(({ number, filenames, username, title }) => { prs.forEach(({ number, filenames, username, title }) => {
filenames.forEach((filename) => { filenames.forEach(filename => {
if (filename.toLowerCase().includes(value.toLowerCase())) { if (filename.toLowerCase().includes(value.toLowerCase())) {
const prObj = { const prObj = {
number, number,
@ -21,17 +21,18 @@ router.get('/', (request, response) => {
if (filesFound.hasOwnProperty(filename)) { if (filesFound.hasOwnProperty(filename)) {
filesFound[filename].push(prObj); filesFound[filename].push(prObj);
} } else {
else { filesFound[filename] = [prObj];
filesFound[filename] = [prObj]
} }
} }
}); });
}); });
let results = Object.keys(filesFound) let results = Object.keys(filesFound)
.map((filename) => ({ filename, prs: filesFound[filename] })) .map(filename => ({ filename, prs: filesFound[filename] }))
.sort((a, b) => a.filename === b.filename ? 0 : a.filename < b.filename ? -1 : 1); .sort((a, b) =>
a.filename === b.filename ? 0 : a.filename < b.filename ? -1 : 1
);
if (!results.length) { if (!results.length) {
response.json({ ok: true, message: 'No matching results.', results: [] }); response.json({ ok: true, message: 'No matching results.', results: [] });

View File

@ -1,7 +1,7 @@
require('dotenv').config(); require('dotenv').config();
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const multer = require('multer'); const multer = require('multer');
const router = require('express').Router(); const router = require('express').Router();
const container = require('../data'); const container = require('../data');
@ -16,12 +16,14 @@ router.post('/', upload.single('file'), (request, response) => {
} }
if (!!secret && password === secret) { if (!!secret && password === secret) {
const { file: { path: filePath } } = request; const {
file: { path: filePath }
} = request;
const uploaded = path.resolve(__dirname, '../' + filePath); const uploaded = path.resolve(__dirname, '../' + filePath);
const dest = path.resolve(__dirname, '../data.json'); const dest = path.resolve(__dirname, '../data.json');
const data = JSON.parse(fs.readFileSync(uploaded)); const data = JSON.parse(fs.readFileSync(uploaded));
const { indices, prs } = data; const { indices, prs } = data;
const dataOK = Object.keys(data).every((key) => { const dataOK = Object.keys(data).every(key => {
return !!data[key]; return !!data[key];
}); });
const lengthsMatch = Object.keys(indices).length === prs.length; const lengthsMatch = Object.keys(indices).length === prs.length;
@ -29,8 +31,7 @@ router.post('/', upload.single('file'), (request, response) => {
if (dataOK && lengthsMatch) { if (dataOK && lengthsMatch) {
container.update(data); container.update(data);
fs.renameSync(uploaded, dest); fs.renameSync(uploaded, dest);
} } else {
else {
const logPath = path.resolve(__dirname, '../log.txt'); const logPath = path.resolve(__dirname, '../log.txt');
const errorMsg = `Upload failed with ${uploaded}, dataOK: ${dataOK}, lengthsMatch: ${lengthsMatch}.`; const errorMsg = `Upload failed with ${uploaded}, dataOK: ${dataOK}, lengthsMatch: ${lengthsMatch}.`;
fs.appendFileSync(logPath, errorMsg); fs.appendFileSync(logPath, errorMsg);

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,23 @@
const fs = require('fs');
const express = require('express'); const express = require('express');
const app = express(); const app = express();
const { catchAll, pareto, pr, search, info, getCurrData, upload } = require('./routes'); const {
catchAll,
pareto,
pr,
search,
info,
getCurrData,
upload
} = require('./routes');
app.use(express.static('public')); app.use(express.static('public'));
app.use((request, response, next) => { app.use((request, response, next) => {
response.header('Access-Control-Allow-Origin', '*'); response.header('Access-Control-Allow-Origin', '*');
response.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); response.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
response.header('Access-Control-Allow-Methods', 'GET'); response.header('Access-Control-Allow-Methods', 'GET');
next(); next();
}); });

View File

@ -1,25 +1,25 @@
{ {
"name": "@freecodecamp/dashboard-client", "name": "@freecodecamp/dashboard-client",
"private": true, "private": true,
"dependencies": { "dependencies": {
"react": "^16.6.3", "react": "^16.6.3",
"react-dom": "^16.6.3", "react-dom": "^16.6.3",
"react-scripts": "2.1.1", "react-scripts": "2.1.1",
"styled-components": "^4.1.1" "styled-components": "^4.1.1"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build && rm build/static/js/*.map && rm build/static/css/*.map", "build": "react-scripts build && rm build/static/js/*.map && rm build/static/css/*.map",
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
}, },
"browserslist": [ "browserslist": [
">0.2%", ">0.2%",
"not dead", "not dead",
"not ie <= 11", "not ie <= 11",
"not op_mini all" "not op_mini all"
] ]
} }

View File

@ -10,82 +10,92 @@ import { ENDPOINT_INFO } from './constants';
console.log(ENDPOINT_INFO); console.log(ENDPOINT_INFO);
const PageContainer = styled.div` const PageContainer = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
`; `;
const Container = styled.div` const Container = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
max-width: 960px; max-width: 960px;
width: 90vw; width: 90vw;
padding: 15px; padding: 15px;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 0 4px 0 #777; box-shadow: 0 0 4px 0 #777;
`; `;
const Title = styled.h1` const Title = styled.h1`
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background: ${({ theme }) => theme.primary}; background: ${({ theme }) => theme.primary};
color: white; color: white;
width: 100%; width: 100%;
padding: 3px; padding: 3px;
`; `;
const imgStyle = { const imgStyle = {
paddingRight: '20px', paddingRight: '20px',
paddingTop: '6px' paddingTop: '6px'
}; };
class App extends Component { class App extends Component {
state = { state = {
view: 'search', view: 'search',
footerInfo: null footerInfo: null
}; };
updateInfo() { updateInfo() {
fetch(ENDPOINT_INFO) fetch(ENDPOINT_INFO)
.then((response) => response.json()) .then(response => response.json())
.then(({ ok, numPRs, prRange, lastUpdate }) => { .then(({ ok, numPRs, prRange, lastUpdate }) => {
if (ok) { if (ok) {
const footerInfo = { numPRs, prRange, lastUpdate }; const footerInfo = { numPRs, prRange, lastUpdate };
this.setState((prevState) => ({ footerInfo })); this.setState(prevState => ({ footerInfo }));
} }
}) })
.catch(() => { .catch(() => {
// do nothing // do nothing
}); });
} }
handleViewChange = ( { target: { id } }) => { handleViewChange = ({ target: { id } }) => {
const view = id.replace('tabs-', ''); const view = id.replace('tabs-', '');
this.setState((prevState) => ({ ...this.clearObj, view })); this.setState(prevState => ({ ...this.clearObj, view }));
this.updateInfo(); this.updateInfo();
} };
componentDidMount() { componentDidMount() {
this.updateInfo(); this.updateInfo();
} }
render() { render() {
const { handleViewChange, state: { view, footerInfo } } = this; const {
return ( handleViewChange,
<PageContainer> state: { view, footerInfo }
<Title><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" /> Moderator Tools</Title> } = this;
<Tabs view={view} onViewChange={handleViewChange}/> return (
<Container> <PageContainer>
{ view === 'search' && <Search /> } <Title>
{ view === 'reports' && <Pareto /> } <img
</Container> style={imgStyle}
{ footerInfo && <Footer footerInfo={footerInfo}/> } src="https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/e/d/ed1c70bda321aaeee9e6c20ab650ce8bc34899fa.svg"
</PageContainer> alt="Free Code Camp Logo"
); />{' '}
} Moderator Tools
</Title>
<Tabs view={view} onViewChange={handleViewChange} />
<Container>
{view === 'search' && <Search />}
{view === 'reports' && <Pareto />}
</Container>
{footerInfo && <Footer footerInfo={footerInfo} />}
</PageContainer>
);
}
} }
export default App; export default App;

View File

@ -14,25 +14,20 @@ const List = styled.div`
const filenameTitle = { fontWeight: '600' }; const filenameTitle = { fontWeight: '600' };
const FilenameResults = ({ searchValue, results }) => { const FilenameResults = ({ searchValue, results }) => {
const elements = results.map((result) => { const elements = results.map(result => {
const { filename, prs: prObjects } = result; const { filename, prs: prObjects } = result;
const prs = prObjects.map(({ number, username, title }, index) => { const prs = prObjects.map(({ number, username, title }, index) => {
return ( return <ListItem number={number} username={username} prTitle={title} />;
<ListItem
number={number}
username={username}
prTitle={title}
/>
);
}); });
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`; const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
return ( return (
<Result key={filename}> <Result key={filename}>
<span style={filenameTitle}>{filename}</span> <a href={fileOnMaster} rel="noopener noreferrer" target="_blank">(File on Master)</a> <span style={filenameTitle}>{filename}</span>{' '}
<List> <a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
{prs} (File on Master)
</List> </a>
<List>{prs}</List>
</Result> </Result>
); );
}); });

View File

@ -2,31 +2,34 @@ import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
const Container = styled.div` const Container = styled.div`
margin-top: 5px; margin-top: 5px;
text-align: center; text-align: center;
`; `;
const Info = styled.div` const Info = styled.div`
font-size: 14px; font-size: 14px;
padding: 2px; padding: 2px;
`; `;
const Footer = (props) => { const Footer = props => {
const localTime = lastUpdate => {
const localTime = (lastUpdate) => { const newTime = new Date(lastUpdate);
const newTime = new Date(lastUpdate); return newTime.toLocaleString();
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>
);
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; export default Footer;

View File

@ -16,7 +16,12 @@ const ListItem = ({ number, username, prTitle: title }) => {
const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}`; const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}`;
return ( return (
<Container> <Container>
<a style={prNumStyle} href={prUrl} rel="noopener noreferrer" target="_blank"> <a
style={prNumStyle}
href={prUrl}
rel="noopener noreferrer"
target="_blank"
>
#{number} #{number}
</a> </a>
<span style={usernameStyle}>{username}</span> <span style={usernameStyle}>{username}</span>

View File

@ -22,38 +22,42 @@ class Pareto extends React.Component {
componentDidMount() { componentDidMount() {
fetch(ENDPOINT_PARETO) fetch(ENDPOINT_PARETO)
.then((response) => response.json()) .then(response => response.json())
.then(({ ok, pareto }) => { .then(({ ok, pareto }) => {
if (ok) { if (ok) {
if (!pareto.length) { if (!pareto.length) {
pareto.push({ filename: 'Nothing to show in Pareto Report', count: 0, prs: [] }); pareto.push({
filename: 'Nothing to show in Pareto Report',
count: 0,
prs: []
});
} }
this.setState((prevState) => ({ data: pareto })); this.setState(prevState => ({ data: pareto }));
} }
}) })
.catch(() => { .catch(() => {
const pareto = [{ filename: 'Nothing to show in Pareto Report', count: 0, prs: [] }]; const pareto = [
this.setState((prevState) => ({ data: pareto })); { filename: 'Nothing to show in Pareto Report', count: 0, prs: [] }
];
this.setState(prevState => ({ data: pareto }));
}); });
} }
render() { render() {
const { data } = this.state; const { data } = this.state;
const elements = data.map((entry) => { const elements = data.map(entry => {
const { filename, count, prs } = entry; const { filename, count, prs } = entry;
const prsList = prs.map(({ number, username, title }) => { const prsList = prs.map(({ number, username, title }) => {
return ( return <ListItem number={number} username={username} prTitle={title} />;
<ListItem
number={number}
username={username}
prTitle={title}
/>
)
}); });
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`; const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
return ( return (
<Result key={filename}> <Result key={filename}>
<span style={filenameTitle}>{filename}</span> <a href={fileOnMaster} rel="noopener noreferrer" target="_blank">(File on Master)</a><br /> <span style={filenameTitle}>{filename}</span>{' '}
<a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
(File on Master)
</a>
<br />
<details style={detailsStyle}> <details style={detailsStyle}>
<summary># of PRs: {count}</summary> <summary># of PRs: {count}</summary>
<List>{prsList}</List> <List>{prsList}</List>

View File

@ -16,27 +16,24 @@ const PrResults = ({ searchValue, results }) => {
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`; const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
return ( return (
<li key={`${number}-${index}`}> <li key={`${number}-${index}`}>
{filename} <a href={fileOnMaster} rel="noopener noreferrer" target="_blank">(File on Master)</a> {filename}{' '}
<a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
(File on Master)
</a>
</li> </li>
); );
}); });
return ( return (
<Result key={`${number}-${idx}`}> <Result key={`${number}-${idx}`}>
<ListItem <ListItem number={number} username={username} prTitle={title} />
number={number} <List>{files}</List>
username={username}
prTitle={title}
/>
<List>
{files}
</List>
</Result> </Result>
); );
}); });
return ( return (
<FullWidthDiv style={{width: '100%'}}> <FullWidthDiv style={{ width: '100%' }}>
{results.length ? <h3>Results for PR# {searchValue}</h3> : null} {results.length ? <h3>Results for PR# {searchValue}</h3> : null}
{elements} {elements}
</FullWidthDiv> </FullWidthDiv>

View File

@ -19,82 +19,106 @@ class Search extends Component {
inputRef = React.createRef(); inputRef = React.createRef();
handleInputEvent = (event) => { handleInputEvent = event => {
const { type, key, target: { value: searchValue } } = event; const {
type,
key,
target: { value: searchValue }
} = event;
if (type === 'change') { if (type === 'change') {
if (this.state.selectedOption === 'pr'){ if (this.state.selectedOption === 'pr') {
if (Number(searchValue) || searchValue === '') { if (Number(searchValue) || searchValue === '') {
this.setState((prevState) => ({ searchValue, results: [] })); this.setState(prevState => ({ searchValue, results: [] }));
} }
} else {
this.setState(prevState => ({ searchValue, results: [] }));
} }
else { } else if (type === 'keypress' && key === 'Enter') {
this.setState((prevState) => ({ searchValue, results: [] }));
}
}
else if (type === 'keypress' && key === 'Enter') {
this.searchPRs(searchValue); this.searchPRs(searchValue);
} }
} };
handleButtonClick = () => { handleButtonClick = () => {
const { searchValue } = this.state; const { searchValue } = this.state;
if (searchValue) { if (searchValue) {
this.searchPRs(searchValue); this.searchPRs(searchValue);
} } else {
else {
this.inputRef.current.focus(); this.inputRef.current.focus();
} }
} };
handleOptionChange = (changeEvent) => { handleOptionChange = changeEvent => {
const selectedOption = changeEvent.target.value; const selectedOption = changeEvent.target.value;
this.setState((prevState) => ({ selectedOption, ...this.clearObj })); this.setState(prevState => ({ selectedOption, ...this.clearObj }));
this.inputRef.current.focus(); this.inputRef.current.focus();
} };
searchPRs = (value) => { searchPRs = value => {
const { selectedOption } = this.state; const { selectedOption } = this.state;
const fetchUrl = selectedOption === 'pr' ? const fetchUrl =
`${ENDPOINT_PR}/${value}` : selectedOption === 'pr'
`${ENDPOINT_SEARCH}/?value=${value}`; ? `${ENDPOINT_PR}/${value}`
: `${ENDPOINT_SEARCH}/?value=${value}`;
fetch(fetchUrl) fetch(fetchUrl)
.then((response) => response.json()) .then(response => response.json())
.then(({ ok, message, results }) => { .then(({ ok, message, results }) => {
if (ok) { if (ok) {
this.setState((prevState) => ({ message, results })); this.setState(prevState => ({ message, results }));
} }
}) })
.catch(() => { .catch(() => {
this.setState((prevState) => (this.clearObj)); this.setState(prevState => this.clearObj);
}); });
} };
componentDidMount() { componentDidMount() {
this.inputRef.current.focus(); this.inputRef.current.focus();
} }
render() { render() {
const { handleButtonClick, handleInputEvent, inputRef, handleOptionChange, state } = this; const {
handleButtonClick,
handleInputEvent,
inputRef,
handleOptionChange,
state
} = this;
const { searchValue, message, results, selectedOption } = state; const { searchValue, message, results, selectedOption } = state;
return ( return (
<> <>
<div> <div>
<SearchOption value="pr" onOptionChange={handleOptionChange} selectedOption={selectedOption}> <SearchOption
value="pr"
onOptionChange={handleOptionChange}
selectedOption={selectedOption}
>
PR # PR #
</SearchOption> </SearchOption>
<SearchOption value="filename" onOptionChange={handleOptionChange} selectedOption={selectedOption}> <SearchOption
value="filename"
onOptionChange={handleOptionChange}
selectedOption={selectedOption}
>
Filename Filename
</SearchOption> </SearchOption>
</div> </div>
<Input ref={inputRef} value={searchValue} onInputEvent={handleInputEvent} /> <Input
ref={inputRef}
value={searchValue}
onInputEvent={handleInputEvent}
/>
<button onClick={handleButtonClick}>Search</button> <button onClick={handleButtonClick}>Search</button>
{message} {message}
{selectedOption === 'pr' && <PrResults searchValue={searchValue} results={results} /> } {selectedOption === 'pr' && (
{selectedOption === 'filename' && <FilenameResults searchValue={searchValue} results={results} /> } <PrResults searchValue={searchValue} results={results} />
)}
{selectedOption === 'filename' && (
<FilenameResults searchValue={searchValue} results={results} />
)}
</> </>
); );
} }

View File

@ -9,8 +9,8 @@ const Container = styled.div`
`; `;
const Tab = styled.div` const Tab = styled.div`
background: ${({ active, theme }) => active ? theme.primary : 'white'}; background: ${({ active, theme }) => (active ? theme.primary : 'white')};
color: ${({ active, theme }) => active ? 'white' : theme.primary}; color: ${({ active, theme }) => (active ? 'white' : theme.primary)};
font-size: 18px; font-size: 18px;
padding: 5px; padding: 5px;
border: 2px solid ${({ theme }) => theme.primary}; border: 2px solid ${({ theme }) => theme.primary};
@ -20,7 +20,7 @@ const Tab = styled.div`
&:hover { &:hover {
cursor: pointer; cursor: pointer;
background: #EEEEEE; background: #eeeeee;
color: ${({ theme }) => theme.primary}; color: ${({ theme }) => theme.primary};
} }
@ -32,8 +32,12 @@ const Tab = styled.div`
const Tabs = ({ view, onViewChange }) => { const Tabs = ({ view, onViewChange }) => {
return ( return (
<Container> <Container>
<Tab id="tabs-search" onClick={onViewChange} active={view === 'search'}>Search</Tab> <Tab id="tabs-search" onClick={onViewChange} active={view === 'search'}>
<Tab id="tabs-reports" onClick={onViewChange} active={view === 'reports'}>Pareto</Tab> Search
</Tab>
<Tab id="tabs-reports" onClick={onViewChange} active={view === 'reports'}>
Pareto
</Tab>
</Container> </Container>
); );
}; };

View File

@ -1,10 +1,10 @@
const API_HOST = !!process.env.REACT_APP_DEV ? const API_HOST = !!process.env.REACT_APP_DEV
'http://localhost:3001' : ? 'http://localhost:3001'
'https://pr-relations.glitch.me'; : 'https://pr-relations.glitch.me';
const ENDPOINT_INFO = API_HOST + '/info'; const ENDPOINT_INFO = API_HOST + '/info';
const ENDPOINT_PARETO = API_HOST + '/pareto'; const ENDPOINT_PARETO = API_HOST + '/pareto';
const ENDPOINT_PR = API_HOST + '/pr'; const ENDPOINT_PR = API_HOST + '/pr';
const ENDPOINT_SEARCH = API_HOST + '/search' const ENDPOINT_SEARCH = API_HOST + '/search';
export { export {
API_HOST, API_HOST,

View File

@ -12,6 +12,7 @@ ReactDOM.render(
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<App /> <App />
</ThemeProvider>, </ThemeProvider>,
document.getElementById('root')); document.getElementById('root')
);
serviceWorker.unregister(); serviceWorker.unregister();

View File

@ -1,9 +1,4 @@
{ {
"packages": [ "packages": ["dashboard-api", "dashboard-client", "probot", "sweeper"],
"dashboard-api",
"dashboard-client",
"probot",
"sweeper"
],
"version": "independent" "version": "independent"
} }

View File

@ -1,31 +1,31 @@
const debug = require('debug')('probot:presolver') const debug = require('debug')('probot:presolver');
const Presolver = require('./lib/presolver') const Presolver = require('./lib/presolver');
async function probotPlugin (robot) { async function probotPlugin(robot) {
const events = [ const events = [
'pull_request.opened', 'pull_request.opened',
'pull_request.edited', 'pull_request.edited',
'pull_request.synchronize', 'pull_request.synchronize',
'pull_request.reopened', 'pull_request.reopened',
'pull_request.labeled' 'pull_request.labeled'
] ];
robot.on(events, presolve.bind(null, robot)) robot.on(events, presolve.bind(null, robot));
} }
async function presolve (app, context) { async function presolve(app, context) {
const presolver = forRepository(context) const presolver = forRepository(context);
const pullRequest = getPullRequest(context) const pullRequest = getPullRequest(context);
return presolver.presolve(pullRequest) return presolver.presolve(pullRequest);
} }
function forRepository (context) { function forRepository(context) {
const config = Object.assign({}, context.repo({ logger: debug })) const config = ({...context.repo({ logger: debug })});
return new Presolver(context, config) return new Presolver(context, config);
} }
function getPullRequest (context) { function getPullRequest(context) {
return context.payload.pull_request || context.payload.review.pull_request return context.payload.pull_request || context.payload.review.pull_request;
} }
module.exports = probotPlugin module.exports = probotPlugin;

View File

@ -3,4 +3,4 @@ module.exports = {
name: 'PR: potential-conflict', name: 'PR: potential-conflict',
color: 'c2e0c6' color: 'c2e0c6'
} }
} };

View File

@ -1,98 +1,98 @@
class Presolver { class Presolver {
constructor (context, { owner, repo, logger = console, ...config }) { constructor(context, { owner, repo, logger = console, ...config }) {
this.context = context this.context = context;
this.github = context.github this.github = context.github;
this.logger = logger this.logger = logger;
this.config = Object.assign({}, require('./defaults'), config || {}, { this.config = Object.assign({}, require('./defaults'), config || {}, {
owner, owner,
repo repo
}) });
// console.log(this.config) // console.log(this.config)
this.pullRequest = {} this.pullRequest = {};
this.conflictingFiles = [] this.conflictingFiles = [];
} }
async presolve (pullRequest) { async presolve(pullRequest) {
Object.assign(this.pullRequest, pullRequest) Object.assign(this.pullRequest, pullRequest);
await this._ensurePresolverLabelExists() await this._ensurePresolverLabelExists();
await this._getState() await this._getState();
const labelObj = this.config.labelPRConflict const labelObj = this.config.labelPRConflict;
if (this.conflictingFiles.length) { if (this.conflictingFiles.length) {
await this._addLabel(labelObj) await this._addLabel(labelObj);
} }
} }
async _getState () { async _getState() {
// console.log(this.context.issue()) // console.log(this.context.issue())
const files = await this.github.pullRequests.getFiles(this.context.issue()) const files = await this.github.pullRequests.getFiles(this.context.issue());
// console.log(files) // console.log(files)
const {owner, repo} = this.config const { owner, repo } = this.config;
const prs = await this.github.pullRequests.getAll({ owner, repo }) const prs =
.data || [] (await this.github.pullRequests.getAll({ owner, repo }).data) || [];
// console.log(prs) // console.log(prs)
await this._getConflictingFiles(prs, files) await this._getConflictingFiles(prs, files);
} }
async _getConflictingFiles (prs, files) { async _getConflictingFiles(prs, files) {
const {owner, repo} = this.config const { owner, repo } = this.config;
const github = this.github const github = this.github;
const conflictingFiles = this.conflictingFiles const conflictingFiles = this.conflictingFiles;
// console.log(prs, files) // console.log(prs, files)
prs.forEach((pr) => { prs.forEach(pr => {
const prIssue = { const prIssue = {
number: pr.number, number: pr.number,
owner: owner, owner: owner,
repo: repo repo: repo
} };
var prFiles = github.pullRequests.getFiles(prIssue) var prFiles = github.pullRequests.getFiles(prIssue);
prFiles.data.forEach((file) => { prFiles.data.forEach(file => {
files.data.forEach((f) => { files.data.forEach(f => {
// console.log(f, file) // console.log(f, file)
if (f.filename === file.filename) { if (f.filename === file.filename) {
conflictingFiles.push(file.filename) conflictingFiles.push(file.filename);
} }
}) });
}) });
}) });
} }
async _ensurePresolverLabelExists () { async _ensurePresolverLabelExists() {
const label = this.config.labelPRConflict const label = this.config.labelPRConflict;
await this._createLabel(label) await this._createLabel(label);
} }
async _createLabel (labelObj) { async _createLabel(labelObj) {
const { owner, repo } = this.config const { owner, repo } = this.config;
const github = this.github const github = this.github;
//console.log(this.github.issues.getLabel({ owner, repo, name: labelObj.name })) //console.log(this.github.issues.getLabel({ owner, repo, name: labelObj.name }))
return this.github.issues return this.github.issues
.getLabel({ owner, repo, name: labelObj.name }) .getLabel({ owner, repo, name: labelObj.name })
.catch(() => { .catch(() => {
console.log(labelObj) console.log(labelObj);
return github.issues.createLabel({ return github.issues.createLabel({
owner, owner,
repo, repo,
name: labelObj.name, name: labelObj.name,
color: labelObj.color color: labelObj.color
}) });
}) });
} }
_getLabel (labelObj) { _getLabel(labelObj) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
for (const label of this.pullRequest.labels) { for (const label of this.pullRequest.labels) {
if (labelObj && labelObj.name && label.name === labelObj.name) { if (labelObj && labelObj.name && label.name === labelObj.name) {
resolve(labelObj) resolve(labelObj);
} }
} }
reject(new Error('Not found')) reject(new Error('Not found'));
}) });
} }
async _addLabel (labelObj) { async _addLabel(labelObj) {
const { owner, repo } = this.config const { owner, repo } = this.config;
const number = this.pullRequest.number const number = this.pullRequest.number;
const label = this.config.labelPRConflict const label = this.config.labelPRConflict;
const github = this.github const github = this.github;
// Check if a label does not exist. If it does, it addes the label. // Check if a label does not exist. If it does, it addes the label.
return this._getLabel(label).catch(() => { return this._getLabel(label).catch(() => {
// console.log(labelObj) // console.log(labelObj)
@ -101,9 +101,9 @@ class Presolver {
repo, repo,
number, number,
labels: [labelObj.name] labels: [labelObj.name]
}) });
}) });
} }
} }
module.exports = Presolver module.exports = Presolver;

18354
probot/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,15 +40,9 @@
"merged_at": null, "merged_at": null,
"merge_commit_sha": "fc696e1a288b07f66318e775fdd9ea619122d09e", "merge_commit_sha": "fc696e1a288b07f66318e775fdd9ea619122d09e",
"assignee": null, "assignee": null,
"assignees": [ "assignees": [],
"requested_reviewers": [],
], "requested_teams": [],
"requested_reviewers": [
],
"requested_teams": [
],
"labels": [ "labels": [
{ {
"id": 511502933, "id": 511502933,
@ -458,4 +452,4 @@
"id": 421598, "id": 421598,
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4" "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
} }
} }

View File

@ -40,18 +40,10 @@
"merged_at": null, "merged_at": null,
"merge_commit_sha": null, "merge_commit_sha": null,
"assignee": null, "assignee": null,
"assignees": [ "assignees": [],
"requested_reviewers": [],
], "requested_teams": [],
"requested_reviewers": [ "labels": [],
],
"requested_teams": [
],
"labels": [
],
"milestone": null, "milestone": null,
"commits_url": "https://api.github.com/repos/tbushman/pu/pulls/15/commits", "commits_url": "https://api.github.com/repos/tbushman/pu/pulls/15/commits",
"review_comments_url": "https://api.github.com/repos/tbushman/pu/pulls/15/comments", "review_comments_url": "https://api.github.com/repos/tbushman/pu/pulls/15/comments",
@ -451,4 +443,4 @@
"id": 421598, "id": 421598,
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4" "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
} }
} }

View File

@ -1,16 +1,16 @@
const expect = require('expect') const expect = require('expect');
const { Probot } = require('probot') const { Probot } = require('probot');
const prSuccessEvent = require('./events/pullRequests.opened') const prSuccessEvent = require('./events/pullRequests.opened');
const prExisting = require('./events/pullRequests.existing') const prExisting = require('./events/pullRequests.existing');
const probotPlugin = require('..') const probotPlugin = require('..');
describe('Presolver', () => { describe('Presolver', () => {
let probot, github let probot, github;
beforeEach(() => { beforeEach(() => {
probot = new Probot({}) probot = new Probot({});
// Load our app into probot // Load our app into probot
let app = probot.load(probotPlugin) let app = probot.load(probotPlugin);
// This is an easy way to mock out the GitHub API // This is an easy way to mock out the GitHub API
// https://probot.github.io/docs/testing/ // https://probot.github.io/docs/testing/
github = { github = {
@ -21,31 +21,39 @@ describe('Presolver', () => {
createLabel: jest.fn() createLabel: jest.fn()
}, },
repos: { repos: {
getContent: () => Promise.resolve({data: Buffer.from(` getContent: () =>
Promise.resolve({
data: Buffer.from(
`
issueOpened: Message issueOpened: Message
pullRequestOpened: Message pullRequestOpened: Message
`).toString('base64')}) `
).toString('base64')
})
}, },
pullRequests: { pullRequests: {
getFiles: jest.fn().mockImplementation(() => ({ getFiles: jest.fn().mockImplementation(() => ({
data: [ data: [{ filename: 'test.txt' }]
{filename: 'test.txt'}
]
})), })),
getAll: jest.fn().mockImplementation(() => ({ data: [prExisting.pull_request] })) getAll: jest
.fn()
.mockImplementation(() => ({ data: [prExisting.pull_request] }))
} }
} };
app.auth = () => Promise.resolve(github) app.auth = () => Promise.resolve(github);
// just return a test token // just return a test token
// app.app = () => 'test' // app.app = () => 'test'
}) });
test('adds a label if a PR has changes to files targeted by an existing PR', async () => { test('adds a label if a PR has changes to files targeted by an existing PR', async () => {
// Receive a webhook event // Receive a webhook event
await probot.receive({name: 'pull_request.opened', payload: prSuccessEvent}) await probot.receive({
expect(github.issues.addLabels).toHaveBeenCalled() name: 'pull_request.opened',
}) payload: prSuccessEvent
}) });
expect(github.issues.addLabels).toHaveBeenCalled();
});
});
// For more information about testing with Jest see: // For more information about testing with Jest see:
// https://facebook.github.io/jest/ // https://facebook.github.io/jest/

View File

@ -6,21 +6,24 @@ const fccBaseUrl = `https://github.com/${owner}/${repo}/`;
const prBaseUrl = `${fccBaseUrl}pull/`; const prBaseUrl = `${fccBaseUrl}pull/`;
const octokitConfig = { const octokitConfig = {
timeout: 0, // 0 means no request timeout // 0 means no request timeout
headers: { timeout: 0,
accept: 'application/vnd.github.v3+json', headers: {
'user-agent': 'octokit/rest.js v1.2.3' // v1.2.3 will be current version accept: 'application/vnd.github.v3+json',
}, // v1.2.3 will be current version
// custom GitHub Enterprise URL 'user-agent': 'octokit/rest.js v1.2.3'
baseUrl: 'https://api.github.com', },
// Node only: advanced request options can be passed as http(s) agent // custom GitHub Enterprise URL
agent: undefined baseUrl: 'https://api.github.com',
} // Node only: advanced request options can be passed as http(s) agent
// eslint-disable-next-line no-undefined
const octokitAuth = { agent: undefined
type: 'basic',
username: process.env.GITHUB_USERNAME,
password: process.env.GITHUB_ACCESS_TOKEN
}; };
module.exports = { owner, repo, prBaseUrl, octokitConfig, octokitAuth } const octokitAuth = {
type: 'basic',
username: process.env.GITHUB_USERNAME,
password: process.env.GITHUB_ACCESS_TOKEN
};
module.exports = { owner, repo, prBaseUrl, octokitConfig, octokitAuth };

View File

@ -8,8 +8,14 @@ const { getRange, getCount } = require('./pr-stats');
octokit.authenticate(octokitAuth); octokit.authenticate(octokitAuth);
const paginate = async function paginate (method, octokit, firstPR, lastPR, prPropsToGet, progressBar) { const paginate = async function paginate(
method,
octokit,
firstPR,
lastPR,
prPropsToGet,
progressBar
) {
const prFilter = (prs, first, last, prPropsToGet) => { const prFilter = (prs, first, last, prPropsToGet) => {
const filtered = []; const filtered = [];
for (let pr of prs) { for (let pr of prs) {
@ -17,7 +23,7 @@ const paginate = async function paginate (method, octokit, firstPR, lastPR, prPr
const propsObj = prPropsToGet.reduce((obj, prop) => { const propsObj = prPropsToGet.reduce((obj, prop) => {
obj[prop] = pr[prop]; obj[prop] = pr[prop];
return obj; return obj;
} ,{}); }, {});
filtered.push(propsObj); filtered.push(propsObj);
} }
if (pr.number >= last) { if (pr.number >= last) {
@ -29,16 +35,21 @@ const paginate = async function paginate (method, octokit, firstPR, lastPR, prPr
}; };
const methodProps = { const methodProps = {
owner, repo, state: 'open', owner,
base: 'master', sort: 'created', repo,
direction: 'asc', page: 1, per_page: 100 state: 'open',
base: 'master',
sort: 'created',
direction: 'asc',
page: 1,
per_page: 100
}; };
let done = false; // will be true when lastPR is seen in paginated results let done = false; // will be true when lastPR is seen in paginated results
let response = await method(methodProps); let response = await method(methodProps);
let { data } = response; let { data } = response;
data = prFilter(data, firstPR, lastPR, prPropsToGet); data = prFilter(data, firstPR, lastPR, prPropsToGet);
while (octokit.hasNextPage(response) && !done ) { while (octokit.hasNextPage(response) && !done) {
response = await octokit.getNextPage(response); response = await octokit.getNextPage(response);
let dataFiltered = prFilter(response.data, firstPR, lastPR, prPropsToGet); let dataFiltered = prFilter(response.data, firstPR, lastPR, prPropsToGet);
data = data.concat(dataFiltered); data = data.concat(dataFiltered);
@ -53,32 +64,31 @@ const getUserInput = async (rangeType = '') => {
data = await getRange().then(data => data); data = await getRange().then(data => data);
firstPR = data[0]; firstPR = data[0];
lastPR = data[1]; lastPR = data[1];
} } else {
else { let [n, f, type, start, end] = process.argv;
let [ n, f, type, start, end ] = process.argv;
data = await getRange().then(data => data); data = await getRange().then(data => data);
firstPR = data[0]; firstPR = data[0];
lastPR = data[1]; lastPR = data[1];
if (type !== 'all' && type !== 'range') { if (type !== 'all' && type !== 'range') {
throw `Please specify either all or range for 1st arg.`; throw `Please specify either all or range for 1st arg.`;
} }
if (type === 'range') { if (type === 'range') {
start = parseInt(start); start = parseInt(start);
end = parseInt(end); end = parseInt(end);
if (!start || !end) { if (!start || !end) {
throw `Please specify both a starting PR # (2nd arg) and ending PR # (3rd arg).`; throw `Please specify both a starting PR # (2nd arg) and ending PR # (3rd arg).`;
} }
if (start > end) { if (start > end) {
throw `Starting PR # must be less than or equal to end PR #.`; throw `Starting PR # must be less than or equal to end PR #.`;
} }
if (start < firstPR) { if (start < firstPR) {
throw `Starting PR # can not be less than first open PR # (${firstPR})`; throw `Starting PR # can not be less than first open PR # (${firstPR})`;
} }
firstPR = start firstPR = start;
if (end > lastPR) { if (end > lastPR) {
throw `Ending PR # can not be greater than last open PR # (${lastPR})`; throw `Ending PR # can not be greater than last open PR # (${lastPR})`;
} }
lastPR = end; lastPR = end;
} }
} }
const totalPRs = await getCount().then(data => data); const totalPRs = await getCount().then(data => data);
@ -86,16 +96,26 @@ const getUserInput = async (rangeType = '') => {
}; };
const getPRs = async (totalPRs, firstPR, lastPR, prPropsToGet) => { const getPRs = async (totalPRs, firstPR, lastPR, prPropsToGet) => {
const getPRsBar = new _cliProgress.Bar({ const getPRsBar = new _cliProgress.Bar(
format: `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] {percentage}% | Elapsed Time: {duration_formatted} | ETA: {eta_formatted}`, {
etaBuffer: 50 format: `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] {percentage}% | Elapsed Time: {duration_formatted} | ETA: {eta_formatted}`,
}, _cliProgress.Presets.shades_classic); etaBuffer: 50
},
_cliProgress.Presets.shades_classic
);
getPRsBar.start(totalPRs, 0); getPRsBar.start(totalPRs, 0);
let openPRs = await paginate(octokit.pullRequests.list, octokit, firstPR, lastPR, prPropsToGet, getPRsBar); let openPRs = await paginate(
octokit.pullRequests.list,
octokit,
firstPR,
lastPR,
prPropsToGet,
getPRsBar
);
getPRsBar.update(totalPRs); getPRsBar.update(totalPRs);
getPRsBar.stop(); getPRsBar.stop();
console.log(`# of PRs retrieved: ${openPRs.length}`); console.log(`# of PRs retrieved: ${openPRs.length}`);
return { firstPR, lastPR, openPRs }; return { firstPR, lastPR, openPRs };
} };
module.exports = { getPRs, getUserInput }; module.exports = { getPRs, getUserInput };

View File

@ -13,27 +13,60 @@ const page = 1;
const per_page = 1; const per_page = 1;
const getCount = async () => { const getCount = async () => {
const { data: { total_count: count } } = await octokit.search.issues({ const {
q: `repo:${owner}/${repo}+is:open+type:pr+base:master`, data: { total_count: count }
sort: 'created', order: 'asc', page: 1, per_page: 1 } = await octokit.search
}) .issues({
.catch((err) => { q: `repo:${owner}/${repo}+is:open+type:pr+base:master`,
console.log(err); sort: 'created',
}); order: 'asc',
page: 1,
per_page: 1
})
.catch(err => {
console.log(err);
});
return count; return count;
}; };
const getFirst = async () => { const getFirst = async () => {
let methodProps = {owner, repo, state, base, sort, direction: 'asc', page, per_page}; let methodProps = {
owner,
repo,
state,
base,
sort,
direction: 'asc',
page,
per_page
};
let response = await octokit.pullRequests.list(methodProps); let response = await octokit.pullRequests.list(methodProps);
return response.data[0].number; return response.data[0].number;
}; };
const getRange = async () => { const getRange = async () => {
let methodProps = {owner, repo, state, base, sort, direction: 'asc', page, per_page}; let methodProps = {
owner,
repo,
state,
base,
sort,
direction: 'asc',
page,
per_page
};
let response = await octokit.pullRequests.list(methodProps); let response = await octokit.pullRequests.list(methodProps);
const firstPR = response.data[0].number; const firstPR = response.data[0].number;
methodProps = {owner, repo, state, base, sort, direction: 'desc', page, per_page}; methodProps = {
owner,
repo,
state,
base,
sort,
direction: 'desc',
page,
per_page
};
response = await octokit.pullRequests.list(methodProps); response = await octokit.pullRequests.list(methodProps);
const lastPR = response.data[0].number; const lastPR = response.data[0].number;
return [firstPR, lastPR]; return [firstPR, lastPR];

View File

@ -14,23 +14,32 @@ const octokit = require('@octokit/rest')(octokitConfig);
const { getPRs, getUserInput } = require('../get-prs'); const { getPRs, getUserInput } = require('../get-prs');
const { addLabels, addComment } = require('../pr-tasks'); const { addLabels, addComment } = require('../pr-tasks');
const { rateLimiter, savePrData, ProcessingLog } = require('../utils'); const { rateLimiter, savePrData, ProcessingLog } = require('../utils');
const { frontmatterCheck } = require('../validation/guide-folder-checks/frontmatter-check'); const {
const { createErrorMsg } = require('../validation/guide-folder-checks/create-error-msg'); frontmatterCheck
} = require('../validation/guide-folder-checks/frontmatter-check');
const {
createErrorMsg
} = require('../validation/guide-folder-checks/create-error-msg');
const allowedLangDirNames = [ const allowedLangDirNames = [
"arabic", 'arabic',
"chinese", 'chinese',
"english", 'english',
"portuguese", 'portuguese',
"russian", 'russian',
"spanish" 'spanish'
]; ];
octokit.authenticate(octokitAuth); octokit.authenticate(octokitAuth);
const log = new ProcessingLog('all-frontmatter-checks'); const log = new ProcessingLog('all-frontmatter-checks');
const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment) => { const labeler = async (
number,
prFiles,
currentLabels,
guideFolderErrorsComment
) => {
const labelsToAdd = {}; // holds potential labels to add based on file path const labelsToAdd = {}; // holds potential labels to add based on file path
if (guideFolderErrorsComment) { if (guideFolderErrorsComment) {
labelsToAdd['status: needs update'] = 1; labelsToAdd['status: needs update'] = 1;
@ -38,7 +47,9 @@ const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment)
const existingLabels = currentLabels.map(({ name }) => name); const existingLabels = currentLabels.map(({ name }) => name);
/* this next section only adds needed labels which are NOT currently on the PR. */ /* this next section only adds needed labels which are NOT currently on the PR. */
const newLabels = Object.keys(labelsToAdd).filter(label => !existingLabels.includes(label)); const newLabels = Object.keys(labelsToAdd).filter(
label => !existingLabels.includes(label)
);
if (newLabels.length) { if (newLabels.length) {
if (process.env.PRODUCTION_RUN === 'true') { if (process.env.PRODUCTION_RUN === 'true') {
addLabels(number, newLabels); addLabels(number, newLabels);
@ -50,9 +61,14 @@ const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment)
const checkPath = (fullPath, fileContent) => { const checkPath = (fullPath, fileContent) => {
let errorMsgs = []; let errorMsgs = [];
const remaining = fullPath.split("/"); const remaining = fullPath.split('/');
const isTranslation = allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english'; const isTranslation =
const frontMatterErrMsgs = frontmatterCheck(fullPath, isTranslation, fileContent); allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english';
const frontMatterErrMsgs = frontmatterCheck(
fullPath,
isTranslation,
fileContent
);
return errorMsgs.concat(frontMatterErrMsgs); return errorMsgs.concat(frontMatterErrMsgs);
}; };
@ -71,14 +87,13 @@ const guideFolderChecks = async (number, prFiles, user) => {
} }
if (prErrors.length) { if (prErrors.length) {
const comment = createErrorMsg(prErrors, user) const comment = createErrorMsg(prErrors, user);
if (process.env.PRODUCTION_RUN === 'true') { if (process.env.PRODUCTION_RUN === 'true') {
const result = await addComment(number, comment); const result = await addComment(number, comment);
} }
await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500); await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500);
return comment; return comment;
} } else {
else {
return null; return null;
} }
}; };
@ -95,13 +110,32 @@ const guideFolderChecks = async (number, prFiles, user) => {
log.start(); log.start();
console.log('Starting frontmatter checks process...'); console.log('Starting frontmatter checks process...');
for (let count in openPRs) { for (let count in openPRs) {
let { number, labels: currentLabels, user: { login: username } } = openPRs[count]; let {
const { data: prFiles } = await octokit.pullRequests.listFiles({ owner, repo, number }); number,
labels: currentLabels,
user: { login: username }
} = openPRs[count];
const { data: prFiles } = await octokit.pullRequests.listFiles({
owner,
repo,
number
});
const guideFolderErrorsComment = await guideFolderChecks(number, prFiles, username); const guideFolderErrorsComment = await guideFolderChecks(
const commentLogVal = guideFolderErrorsComment ? guideFolderErrorsComment : 'none'; number,
prFiles,
username
);
const commentLogVal = guideFolderErrorsComment
? guideFolderErrorsComment
: 'none';
const labelsAdded = await labeler(number, prFiles, currentLabels, guideFolderErrorsComment); const labelsAdded = await labeler(
number,
prFiles,
currentLabels,
guideFolderErrorsComment
);
const labelLogVal = labelsAdded.length ? labelsAdded : 'none added'; const labelLogVal = labelsAdded.length ? labelsAdded : 'none added';
log.add(number, { number, comment: commentLogVal, labels: labelLogVal }); log.add(number, { number, comment: commentLogVal, labels: labelLogVal });
@ -109,11 +143,11 @@ const guideFolderChecks = async (number, prFiles, user) => {
} }
} }
})() })()
.then(() => { .then(() => {
log.finish(); log.finish();
console.log('Successfully completed frontmatter checks'); console.log('Successfully completed frontmatter checks');
}) })
.catch(err => { .catch(err => {
log.finish(); log.finish();
console.log(err) console.log(err);
}) });

View File

@ -35,25 +35,26 @@ const log = new ProcessingLog('all-locally-tested-labels');
} }
/* this next section only adds needed labels which are NOT currently on the PR. */ /* this next section only adds needed labels which are NOT currently on the PR. */
const newLabels = Object.keys(labelsToAdd).filter(label => !existingLabels.includes(label)); const newLabels = Object.keys(labelsToAdd).filter(
label => !existingLabels.includes(label)
);
if (newLabels.length) { if (newLabels.length) {
log.add(number, { labels: newLabels }); log.add(number, { labels: newLabels });
if (process.env.PRODUCTION_RUN === 'true') { if (process.env.PRODUCTION_RUN === 'true') {
addLabels(number, newLabels, log); addLabels(number, newLabels, log);
await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500); await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500);
} }
} } else {
else {
log.add(number, { number, labels: 'none added' }); log.add(number, { number, labels: 'none added' });
} }
} }
} }
})() })()
.then(() => { .then(() => {
log.finish(); log.finish();
console.log('Successfully completed labeling'); console.log('Successfully completed labeling');
}) })
.catch(err => { .catch(err => {
log.finish(); log.finish();
console.log(err) console.log(err);
}) });

View File

@ -2,7 +2,7 @@ require('dotenv').config({ path: '../.env' });
const { closeOpen } = require('../pr-tasks'); const { closeOpen } = require('../pr-tasks');
const getUserInput = async () => { const getUserInput = async () => {
let [ n, f, prNum ] = process.argv; let [n, f, prNum] = process.argv;
if (!Number(prNum)) { if (!Number(prNum)) {
throw `Please specify a PR # to close and reopen.`; throw `Please specify a PR # to close and reopen.`;
@ -14,9 +14,9 @@ const getUserInput = async () => {
const { prNum } = await getUserInput(); const { prNum } = await getUserInput();
return prNum; return prNum;
})() })()
.then((prNum) => { .then(prNum => {
closeOpen(prNum); closeOpen(prNum);
}) })
.catch((err) => { .catch(err => {
console.log(err) console.log(err);
}); });

View File

@ -6,7 +6,7 @@ const log = new ProcessingLog('prs-closed-reopened');
log.start(); log.start();
const getUserInput = async () => { const getUserInput = async () => {
let [ n, f, filename ] = process.argv; let [n, f, filename] = process.argv;
if (!filename) { if (!filename) {
throw `Please specify a file with PRs which needed to be closed and reopened.`; throw `Please specify a file with PRs which needed to be closed and reopened.`;
@ -15,7 +15,7 @@ const getUserInput = async () => {
let fileObj = openJSONFile(filename); let fileObj = openJSONFile(filename);
let { prs } = fileObj; let { prs } = fileObj;
if (!prs.length) { if (!prs.length) {
throw `Either no PRs found in file or there or an error occurred.` throw `Either no PRs found in file or there or an error occurred.`;
} }
return { prs }; return { prs };
}; };
@ -24,27 +24,26 @@ const getUserInput = async () => {
const { prs } = await getUserInput(); const { prs } = await getUserInput();
return prs; return prs;
})() })()
.then(async (prs) => { .then(async prs => {
const firstPR = prs[0].number; const firstPR = prs[0].number;
const lastPR = prs[prs.length - 1].number; const lastPR = prs[prs.length - 1].number;
for (let { number, errorDesc } of prs) { for (let { number, errorDesc } of prs) {
if (errorDesc !== "unknown error") { if (errorDesc !== 'unknown error') {
log.add(number, { number, closedOpened: true, errorDesc }); log.add(number, { number, closedOpened: true, errorDesc });
if (process.env.PRODUCTION_RUN === 'true') { if (process.env.PRODUCTION_RUN === 'true') {
await closeOpen(number); await closeOpen(number);
await rateLimiter(90000); await rateLimiter(90000);
}
} else {
log.add(number, { number, closedOpened: false, errorDesc });
} }
} }
else { })
log.add(number, { number, closedOpened: false, errorDesc }); .then(() => {
} log.finish();
} console.log('closing/reopening of PRs complete');
}) })
.then(() => { .catch(err => {
log.finish(); log.finish();
console.log('closing/reopening of PRs complete'); console.log(err);
}) });
.catch(err => {
log.finish();
console.log(err)
})

View File

@ -7,18 +7,22 @@ would be added (test) based on data stored in the specific JSON log file.
const { saveToFile, openJSONFile } = require('../utils'); const { saveToFile, openJSONFile } = require('../utils');
const path = require('path'); const path = require('path');
const dedent = require("dedent"); const dedent = require('dedent');
const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper_3-6_2018-11-23T003553.json`); const specificLogFile = path.resolve(
__dirname,
`../work-logs/production_sweeper_3-6_2018-11-23T003553.json`
);
(() => { (() => {
let fileObj = openJSONFile(specificLogFile); let fileObj = openJSONFile(specificLogFile);
let { prs } = fileObj; let { prs } = fileObj;
let count = 0; let count = 0;
let prsWithComments = prs.reduce((text, { number, data: { comment, labels } }) => { let prsWithComments = prs.reduce(
if (comment !== 'none' || labels !== 'none added') { (text, { number, data: { comment, labels } }) => {
text += dedent` if (comment !== 'none' || labels !== 'none added') {
text += dedent`
PR #${number} PR #${number}
Comment: ${comment} Comment: ${comment}
@ -28,10 +32,12 @@ const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper
*************************\n *************************\n
`; `;
count++; count++;
} }
return text; return text;
}, ''); },
''
);
prsWithComments = dedent` prsWithComments = dedent`
# of PRs with comments or labels added: ${count} # of PRs with comments or labels added: ${count}
@ -40,6 +46,9 @@ const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper
${prsWithComments} ${prsWithComments}
`; `;
saveToFile(path.resolve(__dirname, `../work-logs/guideErrorComments.txt`), prsWithComments); saveToFile(
path.resolve(__dirname, `../work-logs/guideErrorComments.txt`),
prsWithComments
);
console.log('guideErrorComments.txt created'); console.log('guideErrorComments.txt created');
})() })();

View File

@ -19,7 +19,10 @@ octokit.authenticate(octokitAuth);
const log = new ProcessingLog('find-failures-script'); const log = new ProcessingLog('find-failures-script');
const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToFind.json')); const errorsToFind = require(path.resolve(
__dirname,
'../input-files/failuresToFind.json'
));
(async () => { (async () => {
const { totalPRs, firstPR, lastPR } = await getUserInput(); const { totalPRs, firstPR, lastPR } = await getUserInput();
@ -31,11 +34,23 @@ const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToF
log.start(); log.start();
console.log('Starting error finding process...'); console.log('Starting error finding process...');
for (let count in openPRs) { for (let count in openPRs) {
let { number, labels, head: { sha: ref } } = openPRs[count]; let {
number,
labels,
head: { sha: ref }
} = openPRs[count];
const existingLabels = labels.map(({ name }) => name); const existingLabels = labels.map(({ name }) => name);
if (!existingLabels.includes('status: merge conflict') && !existingLabels.includes('status: needs update') && !existingLabels.includes('status: discussing')) { if (
const { data: statuses } = await octokit.repos.listStatusesForRef({ owner, repo, ref }); !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) { if (statuses.length) {
const { state, target_url } = statuses[0]; // first element contain most recent status const { state, target_url } = statuses[0]; // first element contain most recent status
const hasProblem = state === 'failure' || state === 'error'; const hasProblem = state === 'failure' || state === 'error';
@ -44,7 +59,7 @@ const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToF
//const logNumber = 'need to use Travis api to access the full log for the buildNum above' //const logNumber = 'need to use Travis api to access the full log for the buildNum above'
const logNumber = ++buildNum; const logNumber = ++buildNum;
const travisLogUrl = `https://api.travis-ci.org/v3/job/${logNumber}/log.txt`; const travisLogUrl = `https://api.travis-ci.org/v3/job/${logNumber}/log.txt`;
const response = await fetch(travisLogUrl) const response = await fetch(travisLogUrl);
const logText = await response.text(); const logText = await response.text();
let found = false; let found = false;
let error; let error;
@ -64,11 +79,11 @@ const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToF
} }
} }
})() })()
.then(() => { .then(() => {
log.finish(); log.finish();
console.log('Successfully finished finding all specified errors.'); console.log('Successfully finished finding all specified errors.');
}) })
.catch(err => { .catch(err => {
log.finish(); log.finish();
console.log(err) console.log(err);
}) });

View File

@ -31,38 +31,61 @@ const log = new ProcessingLog('pr-relations');
log.start(); log.start();
const { indices: oldIndices, prs: oldPRs } = await getExistingData(); const { indices: oldIndices, prs: oldPRs } = await getExistingData();
if (!oldPRs.length) { if (!oldPRs.length) {
console.log('No existing PRs data found, so it will take a while to download PRs/filenames data.'); console.log(
'No existing PRs data found, so it will take a while to download PRs/filenames data.'
);
} }
const prPropsToGet = ['number', 'user', 'title', 'updated_at', 'files']; const prPropsToGet = ['number', 'user', 'title', 'updated_at', 'files'];
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet); const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
if (openPRs.length) { if (openPRs.length) {
const getFilesBar = new _cliProgress.Bar({ const getFilesBar = new _cliProgress.Bar(
format: `Update PRs [{bar}] {percentage}% | {value}/{total} | Time Elapsed: {duration_formatted} | ETA: {eta_formatted}`, {
etaBuffer: 50 format: `Update PRs [{bar}] {percentage}% | {value}/{total} | Time Elapsed: {duration_formatted} | ETA: {eta_formatted}`,
}, _cliProgress.Presets.shades_classic); etaBuffer: 50
},
_cliProgress.Presets.shades_classic
);
getFilesBar.start(openPRs.length, 0); getFilesBar.start(openPRs.length, 0);
let prsUpdated = ''; let prsUpdated = '';
for (let count in openPRs) { for (let count in openPRs) {
let { number, updated_at, title, user: { login: username } } = openPRs[count]; let {
number,
updated_at,
title,
user: { login: username }
} = openPRs[count];
let oldUpdated_at; let oldUpdated_at;
let oldPrData = oldPRs[oldIndices[number]]; let oldPrData = oldPRs[oldIndices[number]];
if (oldPrData) { if (oldPrData) {
oldUpdated_at = oldPrData.updated_at; oldUpdated_at = oldPrData.updated_at;
} }
if (!oldIndices.hasOwnProperty(number) || updated_at > oldUpdated_at) { if (!oldIndices.hasOwnProperty(number) || updated_at > oldUpdated_at) {
const { data: prFiles } = await octokit.pullRequests.listFiles({ owner, repo, number }); const { data: prFiles } = await octokit.pullRequests.listFiles({
owner,
repo,
number
});
const filenames = prFiles.map(({ filename }) => filename); const filenames = prFiles.map(({ filename }) => filename);
log.add(number, { number, updated_at, title, username, filenames }); log.add(number, { number, updated_at, title, username, filenames });
if (+count > 3000 ) { if (+count > 3000) {
await rateLimiter(1400); await rateLimiter(1400);
} }
prsUpdated += `PR #${number} added or updated\n`; prsUpdated += `PR #${number} added or updated\n`;
} } else {
else { let {
let { username: oldUsername, title: oldTitle, filenames: oldFilenames } = oldPrData; username: oldUsername,
log.add(number, { number, updated_at: oldUpdated_at, username: oldUsername, title: oldTitle, filenames: oldFilenames }); title: oldTitle,
filenames: oldFilenames
} = oldPrData;
log.add(number, {
number,
updated_at: oldUpdated_at,
username: oldUsername,
title: oldTitle,
filenames: oldFilenames
});
} }
if (+count % 10 === 0) { if (+count % 10 === 0) {
@ -72,29 +95,31 @@ const log = new ProcessingLog('pr-relations');
getFilesBar.update(openPRs.length); getFilesBar.update(openPRs.length);
getFilesBar.stop(); getFilesBar.stop();
console.log(prsUpdated); console.log(prsUpdated);
} } else {
else {
throw 'There were no open PRs received from Github'; throw 'There were no open PRs received from Github';
} }
})() })()
.then(async () => { .then(async () => {
log.finish(); log.finish();
console.log('Finished retrieving Dashboard data'); console.log('Finished retrieving Dashboard data');
const formData = new FormData(); const formData = new FormData();
formData.append('file', fs.createReadStream(log._logfile)); formData.append('file', fs.createReadStream(log._logfile));
const result = await fetch(`${HOST}/upload?password=${process.env.GLITCH_UPLOAD_SECRET}`, { const result = await fetch(
method: 'POST', `${HOST}/upload?password=${process.env.GLITCH_UPLOAD_SECRET}`,
body: formData {
method: 'POST',
body: formData
}
)
.then(() => {
console.log(`Finished uploading data for ${HOST}`);
})
.catch(err => {
console.log(err);
});
}) })
.then(() => { .catch(err => {
console.log(`Finished uploading data for ${HOST}`); log.finish();
})
.catch((err) => {
console.log(err); console.log(err);
}); });
})
.catch(err => {
log.finish();
console.log(err)
})

View File

@ -20,26 +20,32 @@ log.start();
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet); const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
if (openPRs.length) { if (openPRs.length) {
for (let count in openPRs) { for (let count in openPRs) {
let { number, head: { repo: headRepo } } = openPRs[count]; let {
number,
head: { repo: headRepo }
} = openPRs[count];
if (headRepo === null) { if (headRepo === null) {
const { data: { mergeable, mergeable_state } } = await octokit.pullRequests.get({ owner, repo, number }); const {
data: { mergeable, mergeable_state }
} = await octokit.pullRequests.get({ owner, repo, number });
if (mergeable_state === 'dirty' || mergeable_state === 'unknown') { if (mergeable_state === 'dirty' || mergeable_state === 'unknown') {
log.add(number, { number, mergeable_state }); log.add(number, { number, mergeable_state });
console.log(`[${number} (mergeable_state: ${mergeable_state})](https://github.com/freeCodeCamp/freeCodeCamp/pull/${number})`); console.log(
`[${number} (mergeable_state: ${mergeable_state})](https://github.com/freeCodeCamp/freeCodeCamp/pull/${number})`
);
} }
await rateLimiter(1000); await rateLimiter(1000);
} }
} }
} } else {
else {
throw 'There were no open PRs received from Github'; throw 'There were no open PRs received from Github';
} }
})() })()
.then(async () => { .then(async () => {
log.finish(); log.finish();
console.log('Finished finding unknown repo PRs with merge conflicts'); console.log('Finished finding unknown repo PRs with merge conflicts');
}) })
.catch(err => { .catch(err => {
log.finish(); log.finish();
console.log(err) console.log(err);
}) });

View File

@ -5,11 +5,12 @@ const octokit = require('@octokit/rest')(octokitConfig);
octokit.authenticate(octokitAuth); octokit.authenticate(octokitAuth);
const addComment = async (number, comment) => { const addComment = async (number, comment) => {
const result = await octokit.issues.createComment({ owner, repo, number, body: comment }) const result = await octokit.issues
.catch((err) => { .createComment({ owner, repo, number, body: comment })
console.log(`PR #${number} had an error when trying to add a comment\n`); .catch(err => {
console.log(err); console.log(`PR #${number} had an error when trying to add a comment\n`);
}); console.log(err);
});
if (result) { if (result) {
console.log(`PR #${number} successfully added a comment\n`); console.log(`PR #${number} successfully added a comment\n`);

View File

@ -6,15 +6,20 @@ const octokit = require('@octokit/rest')(octokitConfig);
octokit.authenticate(octokitAuth); octokit.authenticate(octokitAuth);
const addLabels = (number, labels, log) => { const addLabels = (number, labels, log) => {
octokit.issues.addLabels({ owner, repo, number, labels }) octokit.issues
.then(() => { .addLabels({ owner, repo, number, labels })
console.log(`PR #${number} added ${JSON.stringify(labels)}\n`); .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`); .catch(err => {
console.log(err) console.log(
log.finish() `PR #${number} had an error when trying to labels: ${JSON.stringify(
}) labels
)}\n`
);
console.log(err);
log.finish();
});
}; };
module.exports = { addLabels }; module.exports = { addLabels };

View File

@ -8,28 +8,45 @@ const octokit = require('@octokit/rest')(octokitConfig);
octokit.authenticate(octokitAuth); octokit.authenticate(octokitAuth);
/* closes and reopens an open PR with applicable comment */ /* closes and reopens an open PR with applicable comment */
const closeOpen = async (number) => { const closeOpen = async number => {
const result = await octokit.pullRequests.update({ owner, repo , number, state: 'closed', base: 'master' }) const result = await octokit.pullRequests
.then(async () => { .update({ owner, repo, number, state: 'closed', base: 'master' })
await rateLimiter(5000); .then(async () => {
return octokit.pullRequests.update({ owner, repo , number, state: 'open', base: 'master' }) await rateLimiter(5000);
}) return octokit.pullRequests.update({
.then(async () => { owner,
await rateLimiter(1000); repo,
await addComment(number, 'Closed and Reopened this PR to attempt to resolve a specific Travis build failure.'); number,
}) state: 'open',
.catch(async (err) => { base: 'master'
const { errors } = JSON.parse(err.message); // Octokit stores message as a stringified object });
const errorMg = errors[0].message; })
if (errorMg === "state cannot be changed. The repository that submitted this pull request has been deleted.") { .then(async () => {
await rateLimiter(1000); await rateLimiter(1000);
await addComment(number, `This PR was closed because user's repo was deleted.`); await addComment(
console.log(`PR #${number} was closed because user's repo was deleted.`); number,
} 'Closed and Reopened this PR to attempt to resolve a specific Travis build failure.'
else { );
console.log(err); })
} .catch(async err => {
}) const { errors } = JSON.parse(err.message); // Octokit stores message as a stringified object
const errorMg = errors[0].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 {
console.log(err);
}
});
}; };
module.exports = { closeOpen }; module.exports = { closeOpen };

View File

@ -3,7 +3,12 @@ const { validLabels } = require('../validation');
const { addLabels } = require('./add-labels'); const { addLabels } = require('./add-labels');
const { rateLimiter } = require('../utils'); const { rateLimiter } = require('../utils');
const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment) => { const labeler = async (
number,
prFiles,
currentLabels,
guideFolderErrorsComment
) => {
const labelsToAdd = {}; // holds potential labels to add based on file path const labelsToAdd = {}; // holds potential labels to add based on file path
if (guideFolderErrorsComment) { if (guideFolderErrorsComment) {
labelsToAdd['status: needs update'] = 1; labelsToAdd['status: needs update'] = 1;
@ -11,22 +16,27 @@ const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment)
const existingLabels = currentLabels.map(({ name }) => name); const existingLabels = currentLabels.map(({ name }) => name);
prFiles.forEach(({ filename }) => { prFiles.forEach(({ filename }) => {
/* remove '/challenges' from filename so language variable hold the language */ /* remove '/challenges' from filename so language variable hold the language */
const filenameReplacement = filename.replace(/^curriculum\/challenges\//, 'curriculum\/'); const filenameReplacement = filename.replace(
const regex = /^(docs|curriculum|guide)(?:\/)(arabic|chinese|portuguese|russian|spanish)?\/?/ /^curriculum\/challenges\//,
const [ _, articleType, language ] = filenameReplacement.match(regex) || []; // need an array to pass to labelsAdder 'curriculum/'
);
const regex = /^(docs|curriculum|guide)(?:\/)(arabic|chinese|portuguese|russian|spanish)?\/?/;
const [_, articleType, language] = filenameReplacement.match(regex) || []; // need an array to pass to labelsAdder
if (articleType && validLabels[articleType]) { if (articleType && validLabels[articleType]) {
labelsToAdd[validLabels[articleType]] = 1 labelsToAdd[validLabels[articleType]] = 1;
} }
if (language && validLabels[language]) { if (language && validLabels[language]) {
labelsToAdd[validLabels[language]] = 1 labelsToAdd[validLabels[language]] = 1;
} }
if (articleType === 'curriculum') { if (articleType === 'curriculum') {
labelsToAdd['status: need to test locally'] = 1; labelsToAdd['status: need to test locally'] = 1;
} }
}) });
/* this next section only adds needed labels which are NOT currently on the PR. */ /* this next section only adds needed labels which are NOT currently on the PR. */
const newLabels = Object.keys(labelsToAdd).filter(label => !existingLabels.includes(label)); const newLabels = Object.keys(labelsToAdd).filter(
label => !existingLabels.includes(label)
);
if (newLabels.length) { if (newLabels.length) {
if (process.env.PRODUCTION_RUN === 'true') { if (process.env.PRODUCTION_RUN === 'true') {
addLabels(number, newLabels); addLabels(number, newLabels);

View File

@ -1,12 +1,13 @@
/* /*
This script was originally created to iterate over all open PRs to label and comment This script was originally created to iterate over all open
on specific PR errors (i.e. guide related filenmame syntax and frontmatter). PRs to label and comment on specific PR errors (i.e. guide related
filename syntax and front-matter).
Since the first run which covered over 10,000+ PRs, it is curently ran every couple of days Since the first run which covered over 10,000+ PRs, it is currently
for just the most recent PRs. ran every couple of days for just the most recent PRs.
To run the script for a specific range (i.e. label and comment on guide errors), To run the script for a specific range (i.e. label and comment on
run `node sweeper.js range startingPrNumber endingPrNumber` guide errors), run `node sweeper.js range startingPrNumber endingPrNumber`
*/ */
const { owner, repo, octokitConfig, octokitAuth } = require('./constants'); const { owner, repo, octokitConfig, octokitAuth } = require('./constants');
@ -14,8 +15,8 @@ const { owner, repo, octokitConfig, octokitAuth } = require('./constants');
const octokit = require('@octokit/rest')(octokitConfig); const octokit = require('@octokit/rest')(octokitConfig);
const { getPRs, getUserInput } = require('./get-prs'); const { getPRs, getUserInput } = require('./get-prs');
const { guideFolderChecks } = require('./validation'); const { guideFolderChecks } = require('./validation');
const { savePrData, ProcessingLog, rateLimiter } = require('./utils'); const { ProcessingLog, rateLimiter } = require('./utils');
const { labeler } = require('./pr-tasks'); const { labeler } = require('./pr-tasks');
octokit.authenticate(octokitAuth); octokit.authenticate(octokitAuth);
@ -24,33 +25,55 @@ const log = new ProcessingLog('sweeper');
log.start(); log.start();
console.log('Sweeper started...'); console.log('Sweeper started...');
(async () => { (async() => {
const { totalPRs, firstPR, lastPR } = await getUserInput(); const { totalPRs, firstPR, lastPR } = await getUserInput();
const prPropsToGet = ['number', 'labels', 'user']; const prPropsToGet = ['number', 'labels', 'user'];
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet); const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
if (openPRs.length) { console.log('Processing PRs...');
console.log('Processing PRs...'); for (let count in openPRs) {
for (let count in openPRs) { if (openPRs.length) {
let { number, labels: currentLabels, user: { login: username } } = openPRs[count]; let {
const { data: prFiles } = await octokit.pullRequests.listFiles({ owner, repo, number }); number,
labels: currentLabels,
user: { login: username }
} = openPRs[count];
const guideFolderErrorsComment = await guideFolderChecks(number, prFiles, username); const { data: prFiles } = await octokit.pullRequests.listFiles({
const commentLogVal = guideFolderErrorsComment ? guideFolderErrorsComment : 'none'; owner,
repo,
number
});
const guideFolderErrorsComment = await guideFolderChecks(
number,
prFiles,
username
);
const commentLogVal = guideFolderErrorsComment
? guideFolderErrorsComment
: 'none';
const labelsAdded = await labeler(
number,
prFiles,
currentLabels,
guideFolderErrorsComment
);
const labelsAdded = await labeler(number, prFiles, currentLabels, guideFolderErrorsComment);
const labelLogVal = labelsAdded.length ? labelsAdded : 'none added'; const labelLogVal = labelsAdded.length ? labelsAdded : 'none added';
log.add(number, { number, comment: commentLogVal, labels: labelLogVal }); log.add(number, { number, comment: commentLogVal, labels: labelLogVal });
await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500); await rateLimiter(+process.env.RATELIMIT_INTERVAL || 1500);
} }
} }
})() })()
.then(() => { .then(() => {
log.finish(); log.finish();
console.log('Sweeper complete'); console.log('Sweeper complete');
}) })
.catch(err => { .catch(err => {
log.finish(); log.finish();
console.log(err) console.log(err);
}) });

View File

@ -4,4 +4,10 @@ const { saveToFile } = require('./save-to-file');
const { openJSONFile } = require('./open-json-file'); const { openJSONFile } = require('./open-json-file');
const { ProcessingLog } = require('./processing-log'); const { ProcessingLog } = require('./processing-log');
module.exports = { rateLimiter, savePrData, saveToFile, openJSONFile, ProcessingLog }; module.exports = {
rateLimiter,
savePrData,
saveToFile,
openJSONFile,
ProcessingLog
};

View File

@ -14,7 +14,10 @@ class ProcessingLog {
this._prs = []; this._prs = [];
this._indicesObj = {}; this._indicesObj = {};
this._prCount = null; this._prCount = null;
this._logfile = path.resolve(__dirname, `../work-logs/data-for_${this.getRunType()}_${this._script}.json`); this._logfile = path.resolve(
__dirname,
`../work-logs/data-for_${this.getRunType()}_${this._script}.json`
);
} }
getRunType() { getRunType() {
@ -32,18 +35,18 @@ class ProcessingLog {
indices: this._indicesObj, indices: this._indicesObj,
prs: this._prs prs: this._prs
}; };
saveToFile(this._logfile, JSON.stringify(log)) saveToFile(this._logfile, JSON.stringify(log));
} }
add(prNum, props) { add(prNum, props) {
this._prs.push(props); this._prs.push(props);
this._indicesObj[prNum] = this._prs.length -1; this._indicesObj[prNum] = this._prs.length - 1;
} }
getPrRange() { getPrRange() {
if (this._prs.length) { if (this._prs.length) {
const first = this._prs[0].number; const first = this._prs[0].number;
const last = this._prs[this._prs.length -1].number; const last = this._prs[this._prs.length - 1].number;
return [first, last]; return [first, last];
} }
console.log('Current log file does not contain any PRs'); console.log('Current log file does not contain any PRs');
@ -58,8 +61,8 @@ class ProcessingLog {
finish() { finish() {
this._finishTime = new Date(); this._finishTime = new Date();
const minutesElapsed = (this._finishTime - this._startTime) / 1000 / 60; const minutesElapsed = (this._finishTime - this._startTime) / 1000 / 60;
this._elapsedTime = minutesElapsed.toFixed(2) + ' mins'; this._elapsedTime = minutesElapsed.toFixed(2) + ' mins';
let [ first, last ] = this.getPrRange(); let [first, last] = this.getPrRange();
this._firstPR = first; this._firstPR = first;
this._lastPR = last; this._lastPR = last;
this.export(); this.export();
@ -68,14 +71,19 @@ class ProcessingLog {
changeFilename() { changeFilename() {
const now = formatDate(new Date(), 'YYYY-MM-DDTHHmmss'); const now = formatDate(new Date(), 'YYYY-MM-DDTHHmmss');
const finalFilename = `${this.getRunType()}_${this._script}_${this._firstPR}-${this._lastPR}_${now}.json`; const finalFilename = `${this.getRunType()}_${this._script}_${
const newFilename = path.resolve(__dirname,`../work-logs/${finalFilename}`); this._firstPR
}-${this._lastPR}_${now}.json`;
const newFilename = path.resolve(
__dirname,
`../work-logs/${finalFilename}`
);
fs.renameSync(this._logfile, newFilename); fs.renameSync(this._logfile, newFilename);
if (!fs.existsSync(newFilename)) { if (!fs.existsSync(newFilename)) {
throw `File rename unsuccessful.`; throw `File rename unsuccessful.`;
} }
this._logfile = newFilename; this._logfile = newFilename;
} }
}; }
module.exports = { ProcessingLog }; module.exports = { ProcessingLog };

View File

@ -1,4 +1,4 @@
const rateLimiter = (delay) => { const rateLimiter = delay => {
return new Promise(resolve => setTimeout(() => resolve(true), delay)); return new Promise(resolve => setTimeout(() => resolve(true), delay));
}; };

View File

@ -5,7 +5,10 @@ const { saveToFile } = require('./save-to-file');
const savePrData = (openPRs, firstPR, lastPR) => { const savePrData = (openPRs, firstPR, lastPR) => {
const now = formatDate(new Date(), 'YYYY-MM-DDTHHmmss'); const now = formatDate(new Date(), 'YYYY-MM-DDTHHmmss');
const filename = path.resolve(__dirname, `../work-logs/data-for-openprs_${firstPR}-${lastPR}_${now}.json`); const filename = path.resolve(
__dirname,
`../work-logs/data-for-openprs_${firstPR}-${lastPR}_${now}.json`
);
console.log(`# of PRs Retrieved: ${openPRs.length}`); console.log(`# of PRs Retrieved: ${openPRs.length}`);
console.log(`PR Range: ${firstPR} - ${lastPR}`); console.log(`PR Range: ${firstPR} - ${lastPR}`);
saveToFile(filename, JSON.stringify(openPRs)); saveToFile(filename, JSON.stringify(openPRs));

View File

@ -2,8 +2,10 @@ const fs = require('fs');
const saveToFile = (fileName, data) => { const saveToFile = (fileName, data) => {
fs.writeFileSync(fileName, data, err => { fs.writeFileSync(fileName, data, err => {
if (err) { return console.log(err) } if (err) {
return console.log(err);
}
}); });
} };
module.exports = { saveToFile }; module.exports = { saveToFile };

View File

@ -1,17 +1,17 @@
const { frontmatterCheck } = require('./frontmatter-check'); const { frontmatterCheck } = require('./frontmatter-check');
const allowedLangDirNames = [ const allowedLangDirNames = [
"arabic", 'arabic',
"chinese", 'chinese',
"english", 'english',
"portuguese", 'portuguese',
"russian", 'russian',
"spanish" 'spanish'
]; ];
const checkPath = (fullPath, fileContent) => { const checkPath = (fullPath, fileContent) => {
let errorMsgs = []; let errorMsgs = [];
const remaining = fullPath.split("/"); const remaining = fullPath.split('/');
if (!allowedLangDirNames.includes(remaining[1])) { if (!allowedLangDirNames.includes(remaining[1])) {
errorMsgs.push({ errorMsgs.push({
@ -20,20 +20,22 @@ const checkPath = (fullPath, fileContent) => {
}); });
} }
if (remaining[remaining.length - 1] !== "index.md") { if (remaining[remaining.length - 1] !== 'index.md') {
errorMsgs.push({ errorMsgs.push({
msg: `\`${remaining[remaining.length - 1]}\` is not a valid file name, please use \`index.md\``, msg: `\`${
remaining[remaining.length - 1]
}\` is not a valid file name, please use \`index.md\``,
fullPath fullPath
}); });
} else if (remaining[2] === "index.md") { } else if (remaining[2] === 'index.md') {
errorMsgs.push({ errorMsgs.push({
msg: `This file is not in its own sub-directory`, msg: `This file is not in its own sub-directory`,
fullPath fullPath
}); });
} }
const dirName = fullPath.replace("/index.md", ""); const dirName = fullPath.replace('/index.md', '');
if (dirName.replace(/(\s|\_)/, "") !== dirName) { if (dirName.replace(/(\s|\_)/, '') !== dirName) {
errorMsgs.push({ errorMsgs.push({
msg: `Invalid character found in a directory name, please use \`-\` as separators`, msg: `Invalid character found in a directory name, please use \`-\` as separators`,
fullPath fullPath
@ -47,8 +49,13 @@ const checkPath = (fullPath, fileContent) => {
}); });
} }
const isTranslation = allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english'; const isTranslation =
const frontMatterErrMsgs = frontmatterCheck(fullPath, isTranslation, fileContent); allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english';
const frontMatterErrMsgs = frontmatterCheck(
fullPath,
isTranslation,
fileContent
);
return errorMsgs.concat(frontMatterErrMsgs); return errorMsgs.concat(frontMatterErrMsgs);
}; };

View File

@ -1,4 +1,4 @@
const dedent = require("dedent"); const dedent = require('dedent');
const createErrorMsg = (errors, user) => { const createErrorMsg = (errors, user) => {
let errorMsgHeader = dedent` let errorMsgHeader = dedent`
@ -13,16 +13,18 @@ const createErrorMsg = (errors, user) => {
`; `;
let errorMsgTable = errors.reduce((msgStr, { msg, fullPath }, idx) => { let errorMsgTable = errors.reduce((msgStr, { msg, fullPath }, idx) => {
return (msgStr += "\n" + dedent` return (msgStr +=
'\n' +
dedent`
| ${idx + 1} | ${msg} | ${fullPath} | | ${idx + 1} | ${msg} | ${fullPath} |
`); `);
}, ""); }, '');
let errorMsgFooter = dedent` let errorMsgFooter = dedent`
P.S: I am just friendly bot. Review our [Guidelines for Contributing](https://github.com/FreeCodeCamp/FreeCodeCamp/blob/master/CONTRIBUTING.md) and reach out to the [Contributors Chat room](https://gitter.im/FreeCodeCamp/Contributors) for more help. P.S: I am just friendly bot. Review our [Guidelines for Contributing](https://github.com/FreeCodeCamp/FreeCodeCamp/blob/master/CONTRIBUTING.md) and reach out to the [Contributors Chat room](https://gitter.im/FreeCodeCamp/Contributors) for more help.
`; `;
return errorMsgHeader + errorMsgTable + "\n\n" + errorMsgFooter; return errorMsgHeader + errorMsgTable + '\n\n' + errorMsgFooter;
}; };
module.exports = { createErrorMsg }; module.exports = { createErrorMsg };

View File

@ -17,15 +17,13 @@ const frontmatterCheck = (fullPath, isTranslation, fileContent) => {
fullPath fullPath
}); });
} }
} } catch (err) {
catch(err) {
errors.push({ errors.push({
msg: `Unexpected syntax found in the front matter block. Review Travis CI build Details link for more details.`, msg: `Unexpected syntax found in the front matter block. Review Travis CI build Details link for more details.`,
fullPath fullPath
}); });
} }
return errors; return errors;
}; };

View File

@ -22,14 +22,13 @@ const guideFolderChecks = async (number, prFiles, user) => {
} }
if (prErrors.length) { if (prErrors.length) {
const comment = createErrorMsg(prErrors, user) const comment = createErrorMsg(prErrors, user);
if (process.env.PRODUCTION_RUN === 'true') { if (process.env.PRODUCTION_RUN === 'true') {
const result = await addComment(number, comment); const result = await addComment(number, comment);
} }
await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500); await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500);
return comment; return comment;
} } else {
else {
return null; return null;
} }
}; };