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');
const Container = {

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@ -8,7 +8,11 @@ router.get('/:number', (request, response) => {
const index = indices[refNumber];
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;
}
@ -18,7 +22,7 @@ router.get('/:number', (request, response) => {
prs.forEach(({ number, filenames, username, title }) => {
if (number != refNumber) {
const matchedFilenames = filenames.filter((filename) => {
const matchedFilenames = filenames.filter(filename => {
return refFilenames.includes(filename);
});
@ -29,7 +33,11 @@ router.get('/:number', (request, response) => {
});
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;
}

View File

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

View File

@ -1,7 +1,7 @@
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const multer = require('multer');
const router = require('express').Router();
const container = require('../data');
@ -16,12 +16,14 @@ router.post('/', upload.single('file'), (request, response) => {
}
if (!!secret && password === secret) {
const { file: { path: filePath } } = request;
const {
file: { path: filePath }
} = request;
const uploaded = path.resolve(__dirname, '../' + filePath);
const dest = path.resolve(__dirname, '../data.json');
const data = JSON.parse(fs.readFileSync(uploaded));
const { indices, prs } = data;
const dataOK = Object.keys(data).every((key) => {
const dataOK = Object.keys(data).every(key => {
return !!data[key];
});
const lengthsMatch = Object.keys(indices).length === prs.length;
@ -29,8 +31,7 @@ router.post('/', upload.single('file'), (request, response) => {
if (dataOK && lengthsMatch) {
container.update(data);
fs.renameSync(uploaded, dest);
}
else {
} else {
const logPath = path.resolve(__dirname, '../log.txt');
const errorMsg = `Upload failed with ${uploaded}, dataOK: ${dataOK}, lengthsMatch: ${lengthsMatch}.`;
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 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((request, response, next) => {
response.header('Access-Control-Allow-Origin', '*');
response.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
response.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
response.header('Access-Control-Allow-Methods', 'GET');
next();
});

View File

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

View File

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

View File

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

View File

@ -2,31 +2,34 @@ import React from 'react';
import styled from 'styled-components';
const Container = styled.div`
margin-top: 5px;
text-align: center;
margin-top: 5px;
text-align: center;
`;
const Info = styled.div`
font-size: 14px;
padding: 2px;
font-size: 14px;
padding: 2px;
`;
const Footer = (props) => {
const localTime = (lastUpdate) => {
const newTime = new Date(lastUpdate);
return newTime.toLocaleString();
}
const { footerInfo: { numPRs, prRange, lastUpdate } } = props;
return (
lastUpdate &&
<Container>
<Info>Last Update: {localTime(lastUpdate)}</Info>
<Info># of open PRs: {numPRs} ({prRange})</Info>
</Container>
);
const Footer = props => {
const localTime = lastUpdate => {
const newTime = new Date(lastUpdate);
return newTime.toLocaleString();
};
const {
footerInfo: { numPRs, prRange, lastUpdate }
} = props;
return (
lastUpdate && (
<Container>
<Info>Last Update: {localTime(lastUpdate)}</Info>
<Info>
# of open PRs: {numPRs} ({prRange})
</Info>
</Container>
)
);
};
export default Footer;

View File

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

View File

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

View File

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

View File

@ -19,82 +19,106 @@ class Search extends Component {
inputRef = React.createRef();
handleInputEvent = (event) => {
const { type, key, target: { value: searchValue } } = event;
handleInputEvent = event => {
const {
type,
key,
target: { value: searchValue }
} = event;
if (type === 'change') {
if (this.state.selectedOption === 'pr'){
if (this.state.selectedOption === 'pr') {
if (Number(searchValue) || searchValue === '') {
this.setState((prevState) => ({ searchValue, results: [] }));
this.setState(prevState => ({ searchValue, results: [] }));
}
} else {
this.setState(prevState => ({ searchValue, results: [] }));
}
else {
this.setState((prevState) => ({ searchValue, results: [] }));
}
}
else if (type === 'keypress' && key === 'Enter') {
} else if (type === 'keypress' && key === 'Enter') {
this.searchPRs(searchValue);
}
}
};
handleButtonClick = () => {
const { searchValue } = this.state;
if (searchValue) {
this.searchPRs(searchValue);
}
else {
} else {
this.inputRef.current.focus();
}
}
};
handleOptionChange = (changeEvent) => {
handleOptionChange = changeEvent => {
const selectedOption = changeEvent.target.value;
this.setState((prevState) => ({ selectedOption, ...this.clearObj }));
this.setState(prevState => ({ selectedOption, ...this.clearObj }));
this.inputRef.current.focus();
}
};
searchPRs = (value) => {
searchPRs = value => {
const { selectedOption } = this.state;
const fetchUrl = selectedOption === 'pr' ?
`${ENDPOINT_PR}/${value}` :
`${ENDPOINT_SEARCH}/?value=${value}`;
const fetchUrl =
selectedOption === 'pr'
? `${ENDPOINT_PR}/${value}`
: `${ENDPOINT_SEARCH}/?value=${value}`;
fetch(fetchUrl)
.then((response) => response.json())
.then(response => response.json())
.then(({ ok, message, results }) => {
if (ok) {
this.setState((prevState) => ({ message, results }));
this.setState(prevState => ({ message, results }));
}
})
.catch(() => {
this.setState((prevState) => (this.clearObj));
this.setState(prevState => this.clearObj);
});
}
};
componentDidMount() {
this.inputRef.current.focus();
}
render() {
const { handleButtonClick, handleInputEvent, inputRef, handleOptionChange, state } = this;
const {
handleButtonClick,
handleInputEvent,
inputRef,
handleOptionChange,
state
} = this;
const { searchValue, message, results, selectedOption } = state;
return (
<>
<div>
<SearchOption value="pr" onOptionChange={handleOptionChange} selectedOption={selectedOption}>
<SearchOption
value="pr"
onOptionChange={handleOptionChange}
selectedOption={selectedOption}
>
PR #
</SearchOption>
<SearchOption value="filename" onOptionChange={handleOptionChange} selectedOption={selectedOption}>
<SearchOption
value="filename"
onOptionChange={handleOptionChange}
selectedOption={selectedOption}
>
Filename
</SearchOption>
</div>
<Input ref={inputRef} value={searchValue} onInputEvent={handleInputEvent} />
<Input
ref={inputRef}
value={searchValue}
onInputEvent={handleInputEvent}
/>
<button onClick={handleButtonClick}>Search</button>
{message}
{selectedOption === 'pr' && <PrResults searchValue={searchValue} results={results} /> }
{selectedOption === 'filename' && <FilenameResults searchValue={searchValue} results={results} /> }
{selectedOption === 'pr' && (
<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`
background: ${({ active, theme }) => active ? theme.primary : 'white'};
color: ${({ active, theme }) => active ? 'white' : theme.primary};
background: ${({ active, theme }) => (active ? theme.primary : 'white')};
color: ${({ active, theme }) => (active ? 'white' : theme.primary)};
font-size: 18px;
padding: 5px;
border: 2px solid ${({ theme }) => theme.primary};
@ -20,7 +20,7 @@ const Tab = styled.div`
&:hover {
cursor: pointer;
background: #EEEEEE;
background: #eeeeee;
color: ${({ theme }) => theme.primary};
}
@ -32,8 +32,12 @@ const Tab = styled.div`
const Tabs = ({ view, onViewChange }) => {
return (
<Container>
<Tab id="tabs-search" onClick={onViewChange} active={view === 'search'}>Search</Tab>
<Tab id="tabs-reports" onClick={onViewChange} active={view === 'reports'}>Pareto</Tab>
<Tab id="tabs-search" onClick={onViewChange} active={view === 'search'}>
Search
</Tab>
<Tab id="tabs-reports" onClick={onViewChange} active={view === 'reports'}>
Pareto
</Tab>
</Container>
);
};

View File

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

View File

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

View File

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

View File

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

View File

@ -1,98 +1,98 @@
class Presolver {
constructor (context, { owner, repo, logger = console, ...config }) {
this.context = context
this.github = context.github
this.logger = logger
constructor(context, { owner, repo, logger = console, ...config }) {
this.context = context;
this.github = context.github;
this.logger = logger;
this.config = Object.assign({}, require('./defaults'), config || {}, {
owner,
repo
})
});
// console.log(this.config)
this.pullRequest = {}
this.conflictingFiles = []
this.pullRequest = {};
this.conflictingFiles = [];
}
async presolve (pullRequest) {
Object.assign(this.pullRequest, pullRequest)
await this._ensurePresolverLabelExists()
await this._getState()
const labelObj = this.config.labelPRConflict
async presolve(pullRequest) {
Object.assign(this.pullRequest, pullRequest);
await this._ensurePresolverLabelExists();
await this._getState();
const labelObj = this.config.labelPRConflict;
if (this.conflictingFiles.length) {
await this._addLabel(labelObj)
await this._addLabel(labelObj);
}
}
async _getState () {
async _getState() {
// 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)
const {owner, repo} = this.config
const prs = await this.github.pullRequests.getAll({ owner, repo })
.data || []
const { owner, repo } = this.config;
const prs =
(await this.github.pullRequests.getAll({ owner, repo }).data) || [];
// console.log(prs)
await this._getConflictingFiles(prs, files)
await this._getConflictingFiles(prs, files);
}
async _getConflictingFiles (prs, files) {
const {owner, repo} = this.config
const github = this.github
const conflictingFiles = this.conflictingFiles
async _getConflictingFiles(prs, files) {
const { owner, repo } = this.config;
const github = this.github;
const conflictingFiles = this.conflictingFiles;
// console.log(prs, files)
prs.forEach((pr) => {
prs.forEach(pr => {
const prIssue = {
number: pr.number,
owner: owner,
repo: repo
}
var prFiles = github.pullRequests.getFiles(prIssue)
prFiles.data.forEach((file) => {
files.data.forEach((f) => {
};
var prFiles = github.pullRequests.getFiles(prIssue);
prFiles.data.forEach(file => {
files.data.forEach(f => {
// console.log(f, file)
if (f.filename === file.filename) {
conflictingFiles.push(file.filename)
conflictingFiles.push(file.filename);
}
})
})
})
});
});
});
}
async _ensurePresolverLabelExists () {
const label = this.config.labelPRConflict
await this._createLabel(label)
async _ensurePresolverLabelExists() {
const label = this.config.labelPRConflict;
await this._createLabel(label);
}
async _createLabel (labelObj) {
const { owner, repo } = this.config
const github = this.github
async _createLabel(labelObj) {
const { owner, repo } = this.config;
const github = this.github;
//console.log(this.github.issues.getLabel({ owner, repo, name: labelObj.name }))
return this.github.issues
.getLabel({ owner, repo, name: labelObj.name })
.catch(() => {
console.log(labelObj)
console.log(labelObj);
return github.issues.createLabel({
owner,
repo,
name: labelObj.name,
color: labelObj.color
})
})
});
});
}
_getLabel (labelObj) {
_getLabel(labelObj) {
return new Promise((resolve, reject) => {
for (const label of this.pullRequest.labels) {
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) {
const { owner, repo } = this.config
const number = this.pullRequest.number
const label = this.config.labelPRConflict
const github = this.github
async _addLabel(labelObj) {
const { owner, repo } = this.config;
const number = this.pullRequest.number;
const label = this.config.labelPRConflict;
const github = this.github;
// Check if a label does not exist. If it does, it addes the label.
return this._getLabel(label).catch(() => {
// console.log(labelObj)
@ -101,9 +101,9 @@ class Presolver {
repo,
number,
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,
"merge_commit_sha": "fc696e1a288b07f66318e775fdd9ea619122d09e",
"assignee": null,
"assignees": [
],
"requested_reviewers": [
],
"requested_teams": [
],
"assignees": [],
"requested_reviewers": [],
"requested_teams": [],
"labels": [
{
"id": 511502933,
@ -458,4 +452,4 @@
"id": 421598,
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
}
}
}

View File

@ -40,18 +40,10 @@
"merged_at": null,
"merge_commit_sha": null,
"assignee": null,
"assignees": [
],
"requested_reviewers": [
],
"requested_teams": [
],
"labels": [
],
"assignees": [],
"requested_reviewers": [],
"requested_teams": [],
"labels": [],
"milestone": null,
"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",
@ -451,4 +443,4 @@
"id": 421598,
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
}
}
}

View File

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

View File

@ -6,21 +6,24 @@ const fccBaseUrl = `https://github.com/${owner}/${repo}/`;
const prBaseUrl = `${fccBaseUrl}pull/`;
const octokitConfig = {
timeout: 0, // 0 means no request timeout
headers: {
accept: 'application/vnd.github.v3+json',
'user-agent': 'octokit/rest.js v1.2.3' // v1.2.3 will be current version
},
// custom GitHub Enterprise URL
baseUrl: 'https://api.github.com',
// Node only: advanced request options can be passed as http(s) agent
agent: undefined
}
const octokitAuth = {
type: 'basic',
username: process.env.GITHUB_USERNAME,
password: process.env.GITHUB_ACCESS_TOKEN
// 0 means no request timeout
timeout: 0,
headers: {
accept: 'application/vnd.github.v3+json',
// v1.2.3 will be current version
'user-agent': 'octokit/rest.js v1.2.3'
},
// custom GitHub Enterprise URL
baseUrl: 'https://api.github.com',
// Node only: advanced request options can be passed as http(s) agent
// eslint-disable-next-line no-undefined
agent: undefined
};
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);
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 filtered = [];
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) => {
obj[prop] = pr[prop];
return obj;
} ,{});
}, {});
filtered.push(propsObj);
}
if (pr.number >= last) {
@ -29,16 +35,21 @@ const paginate = async function paginate (method, octokit, firstPR, lastPR, prPr
};
const methodProps = {
owner, repo, state: 'open',
base: 'master', sort: 'created',
direction: 'asc', page: 1, per_page: 100
owner,
repo,
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 response = await method(methodProps);
let { data } = response;
data = prFilter(data, firstPR, lastPR, prPropsToGet);
while (octokit.hasNextPage(response) && !done ) {
while (octokit.hasNextPage(response) && !done) {
response = await octokit.getNextPage(response);
let dataFiltered = prFilter(response.data, firstPR, lastPR, prPropsToGet);
data = data.concat(dataFiltered);
@ -53,32 +64,31 @@ const getUserInput = async (rangeType = '') => {
data = await getRange().then(data => data);
firstPR = data[0];
lastPR = data[1];
}
else {
let [ n, f, type, start, end ] = process.argv;
} else {
let [n, f, type, start, end] = process.argv;
data = await getRange().then(data => data);
firstPR = data[0];
lastPR = data[1];
if (type !== 'all' && type !== 'range') {
throw `Please specify either all or range for 1st arg.`;
throw `Please specify either all or range for 1st arg.`;
}
if (type === 'range') {
start = parseInt(start);
end = parseInt(end);
if (!start || !end) {
throw `Please specify both a starting PR # (2nd arg) and ending PR # (3rd arg).`;
}
if (start > end) {
throw `Starting PR # must be less than or equal to end PR #.`;
}
if (start < firstPR) {
throw `Starting PR # can not be less than first open PR # (${firstPR})`;
}
firstPR = start
if (end > lastPR) {
throw `Ending PR # can not be greater than last open PR # (${lastPR})`;
}
lastPR = end;
start = parseInt(start);
end = parseInt(end);
if (!start || !end) {
throw `Please specify both a starting PR # (2nd arg) and ending PR # (3rd arg).`;
}
if (start > end) {
throw `Starting PR # must be less than or equal to end PR #.`;
}
if (start < firstPR) {
throw `Starting PR # can not be less than first open PR # (${firstPR})`;
}
firstPR = start;
if (end > lastPR) {
throw `Ending PR # can not be greater than last open PR # (${lastPR})`;
}
lastPR = end;
}
}
const totalPRs = await getCount().then(data => data);
@ -86,16 +96,26 @@ const getUserInput = async (rangeType = '') => {
};
const getPRs = async (totalPRs, firstPR, lastPR, prPropsToGet) => {
const getPRsBar = new _cliProgress.Bar({
format: `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] {percentage}% | Elapsed Time: {duration_formatted} | ETA: {eta_formatted}`,
etaBuffer: 50
}, _cliProgress.Presets.shades_classic);
const getPRsBar = new _cliProgress.Bar(
{
format: `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] {percentage}% | Elapsed Time: {duration_formatted} | ETA: {eta_formatted}`,
etaBuffer: 50
},
_cliProgress.Presets.shades_classic
);
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.stop();
console.log(`# of PRs retrieved: ${openPRs.length}`);
return { firstPR, lastPR, openPRs };
}
};
module.exports = { getPRs, getUserInput };

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ const log = new ProcessingLog('prs-closed-reopened');
log.start();
const getUserInput = async () => {
let [ n, f, filename ] = process.argv;
let [n, f, filename] = process.argv;
if (!filename) {
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 { prs } = fileObj;
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 };
};
@ -24,27 +24,26 @@ const getUserInput = async () => {
const { prs } = await getUserInput();
return prs;
})()
.then(async (prs) => {
const firstPR = prs[0].number;
const lastPR = prs[prs.length - 1].number;
for (let { number, errorDesc } of prs) {
if (errorDesc !== "unknown error") {
log.add(number, { number, closedOpened: true, errorDesc });
if (process.env.PRODUCTION_RUN === 'true') {
await closeOpen(number);
await rateLimiter(90000);
.then(async prs => {
const firstPR = prs[0].number;
const lastPR = prs[prs.length - 1].number;
for (let { number, errorDesc } of prs) {
if (errorDesc !== 'unknown error') {
log.add(number, { number, closedOpened: true, errorDesc });
if (process.env.PRODUCTION_RUN === 'true') {
await closeOpen(number);
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');
})
.catch(err => {
log.finish();
console.log(err)
})
})
.then(() => {
log.finish();
console.log('closing/reopening of PRs complete');
})
.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 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 { prs } = fileObj;
let count = 0;
let prsWithComments = prs.reduce((text, { number, data: { comment, labels } }) => {
if (comment !== 'none' || labels !== 'none added') {
text += dedent`
let prsWithComments = prs.reduce(
(text, { number, data: { comment, labels } }) => {
if (comment !== 'none' || labels !== 'none added') {
text += dedent`
PR #${number}
Comment: ${comment}
@ -28,10 +32,12 @@ const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper
*************************\n
`;
count++;
}
return text;
}, '');
count++;
}
return text;
},
''
);
prsWithComments = dedent`
# of PRs with comments or labels added: ${count}
@ -40,6 +46,9 @@ const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper
${prsWithComments}
`;
saveToFile(path.resolve(__dirname, `../work-logs/guideErrorComments.txt`), prsWithComments);
saveToFile(
path.resolve(__dirname, `../work-logs/guideErrorComments.txt`),
prsWithComments
);
console.log('guideErrorComments.txt created');
})()
})();

View File

@ -19,7 +19,10 @@ octokit.authenticate(octokitAuth);
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 () => {
const { totalPRs, firstPR, lastPR } = await getUserInput();
@ -31,11 +34,23 @@ const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToF
log.start();
console.log('Starting error finding process...');
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);
if (!existingLabels.includes('status: merge conflict') && !existingLabels.includes('status: needs update') && !existingLabels.includes('status: discussing')) {
const { data: statuses } = await octokit.repos.listStatusesForRef({ owner, repo, ref });
if (
!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) {
const { state, target_url } = statuses[0]; // first element contain most recent status
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 = ++buildNum;
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();
let found = false;
let error;
@ -64,11 +79,11 @@ const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToF
}
}
})()
.then(() => {
log.finish();
console.log('Successfully finished finding all specified errors.');
})
.catch(err => {
log.finish();
console.log(err)
})
.then(() => {
log.finish();
console.log('Successfully finished finding all specified errors.');
})
.catch(err => {
log.finish();
console.log(err);
});

View File

@ -31,38 +31,61 @@ const log = new ProcessingLog('pr-relations');
log.start();
const { indices: oldIndices, prs: oldPRs } = await getExistingData();
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 { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
if (openPRs.length) {
const getFilesBar = new _cliProgress.Bar({
format: `Update PRs [{bar}] {percentage}% | {value}/{total} | Time Elapsed: {duration_formatted} | ETA: {eta_formatted}`,
etaBuffer: 50
}, _cliProgress.Presets.shades_classic);
const getFilesBar = new _cliProgress.Bar(
{
format: `Update PRs [{bar}] {percentage}% | {value}/{total} | Time Elapsed: {duration_formatted} | ETA: {eta_formatted}`,
etaBuffer: 50
},
_cliProgress.Presets.shades_classic
);
getFilesBar.start(openPRs.length, 0);
let prsUpdated = '';
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 oldPrData = oldPRs[oldIndices[number]];
if (oldPrData) {
oldUpdated_at = oldPrData.updated_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);
log.add(number, { number, updated_at, title, username, filenames });
if (+count > 3000 ) {
if (+count > 3000) {
await rateLimiter(1400);
}
prsUpdated += `PR #${number} added or updated\n`;
}
else {
let { username: oldUsername, title: oldTitle, filenames: oldFilenames } = oldPrData;
log.add(number, { number, updated_at: oldUpdated_at, username: oldUsername, title: oldTitle, filenames: oldFilenames });
} else {
let {
username: oldUsername,
title: oldTitle,
filenames: oldFilenames
} = oldPrData;
log.add(number, {
number,
updated_at: oldUpdated_at,
username: oldUsername,
title: oldTitle,
filenames: oldFilenames
});
}
if (+count % 10 === 0) {
@ -72,29 +95,31 @@ const log = new ProcessingLog('pr-relations');
getFilesBar.update(openPRs.length);
getFilesBar.stop();
console.log(prsUpdated);
}
else {
} else {
throw 'There were no open PRs received from Github';
}
})()
.then(async () => {
log.finish();
console.log('Finished retrieving Dashboard data');
.then(async () => {
log.finish();
console.log('Finished retrieving Dashboard data');
const formData = new FormData();
formData.append('file', fs.createReadStream(log._logfile));
const result = await fetch(`${HOST}/upload?password=${process.env.GLITCH_UPLOAD_SECRET}`, {
method: 'POST',
body: formData
const formData = new FormData();
formData.append('file', fs.createReadStream(log._logfile));
const result = await fetch(
`${HOST}/upload?password=${process.env.GLITCH_UPLOAD_SECRET}`,
{
method: 'POST',
body: formData
}
)
.then(() => {
console.log(`Finished uploading data for ${HOST}`);
})
.catch(err => {
console.log(err);
});
})
.then(() => {
console.log(`Finished uploading data for ${HOST}`);
})
.catch((err) => {
.catch(err => {
log.finish();
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);
if (openPRs.length) {
for (let count in openPRs) {
let { number, head: { repo: headRepo } } = openPRs[count];
let {
number,
head: { repo: headRepo }
} = openPRs[count];
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') {
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);
}
}
}
else {
} else {
throw 'There were no open PRs received from Github';
}
})()
.then(async () => {
log.finish();
console.log('Finished finding unknown repo PRs with merge conflicts');
})
.catch(err => {
log.finish();
console.log(err)
})
.then(async () => {
log.finish();
console.log('Finished finding unknown repo PRs with merge conflicts');
})
.catch(err => {
log.finish();
console.log(err);
});

View File

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

View File

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

View File

@ -8,28 +8,45 @@ const octokit = require('@octokit/rest')(octokitConfig);
octokit.authenticate(octokitAuth);
/* closes and reopens an open PR with applicable comment */
const closeOpen = async (number) => {
const result = await octokit.pullRequests.update({ owner, repo , number, state: 'closed', base: 'master' })
.then(async () => {
await rateLimiter(5000);
return octokit.pullRequests.update({ owner, repo , number, state: 'open', base: 'master' })
})
.then(async () => {
await rateLimiter(1000);
await addComment(number, 'Closed and Reopened this PR to attempt to resolve a specific Travis build failure.');
})
.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.") {
const closeOpen = async number => {
const result = await octokit.pullRequests
.update({ owner, repo, number, state: 'closed', base: 'master' })
.then(async () => {
await rateLimiter(5000);
return octokit.pullRequests.update({
owner,
repo,
number,
state: 'open',
base: 'master'
});
})
.then(async () => {
await rateLimiter(1000);
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);
}
})
await addComment(
number,
'Closed and Reopened this PR to attempt to resolve a specific Travis build failure.'
);
})
.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 };

View File

@ -3,7 +3,12 @@ const { validLabels } = require('../validation');
const { addLabels } = require('./add-labels');
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
if (guideFolderErrorsComment) {
labelsToAdd['status: needs update'] = 1;
@ -11,22 +16,27 @@ const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment)
const existingLabels = currentLabels.map(({ name }) => name);
prFiles.forEach(({ filename }) => {
/* remove '/challenges' from filename so language variable hold the language */
const filenameReplacement = filename.replace(/^curriculum\/challenges\//, 'curriculum\/');
const regex = /^(docs|curriculum|guide)(?:\/)(arabic|chinese|portuguese|russian|spanish)?\/?/
const [ _, articleType, language ] = filenameReplacement.match(regex) || []; // need an array to pass to labelsAdder
const filenameReplacement = filename.replace(
/^curriculum\/challenges\//,
'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]) {
labelsToAdd[validLabels[articleType]] = 1
labelsToAdd[validLabels[articleType]] = 1;
}
if (language && validLabels[language]) {
labelsToAdd[validLabels[language]] = 1
labelsToAdd[validLabels[language]] = 1;
}
if (articleType === 'curriculum') {
labelsToAdd['status: need to test locally'] = 1;
}
})
});
/* 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 (process.env.PRODUCTION_RUN === 'true') {
addLabels(number, newLabels);

View File

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

View File

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

View File

@ -1,4 +1,4 @@
const rateLimiter = (delay) => {
const rateLimiter = 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 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(`PR Range: ${firstPR} - ${lastPR}`);
saveToFile(filename, JSON.stringify(openPRs));

View File

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

View File

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

View File

@ -1,4 +1,4 @@
const dedent = require("dedent");
const dedent = require('dedent');
const createErrorMsg = (errors, user) => {
let errorMsgHeader = dedent`
@ -13,16 +13,18 @@ const createErrorMsg = (errors, user) => {
`;
let errorMsgTable = errors.reduce((msgStr, { msg, fullPath }, idx) => {
return (msgStr += "\n" + dedent`
return (msgStr +=
'\n' +
dedent`
| ${idx + 1} | ${msg} | ${fullPath} |
`);
}, "");
}, '');
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.
`;
return errorMsgHeader + errorMsgTable + "\n\n" + errorMsgFooter;
return errorMsgHeader + errorMsgTable + '\n\n' + errorMsgFooter;
};
module.exports = { createErrorMsg };

View File

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

View File

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