refactor(lint): apply linting from the tools (#39)
This commit is contained in:
committed by
Randell Dawson
parent
00e5cf247e
commit
8be7d12cb3
@ -1,5 +1,3 @@
|
|||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
let data = require('./data.json');
|
let data = require('./data.json');
|
||||||
|
|
||||||
const Container = {
|
const Container = {
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) => {
|
||||||
const { indices, prs } = container.data;
|
const { indices, prs } = container.data;
|
||||||
@ -10,7 +10,7 @@ router.get('/', (request, response) => {
|
|||||||
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: [] });
|
||||||
|
@ -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
@ -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();
|
||||||
});
|
});
|
||||||
|
@ -51,11 +51,11 @@ class App extends Component {
|
|||||||
|
|
||||||
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(() => {
|
||||||
@ -63,27 +63,37 @@ class App extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
handleViewChange,
|
||||||
|
state: { view, footerInfo }
|
||||||
|
} = this;
|
||||||
return (
|
return (
|
||||||
<PageContainer>
|
<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>
|
<Title>
|
||||||
<Tabs view={view} onViewChange={handleViewChange}/>
|
<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>
|
<Container>
|
||||||
{ view === 'search' && <Search /> }
|
{view === 'search' && <Search />}
|
||||||
{ view === 'reports' && <Pareto /> }
|
{view === 'reports' && <Pareto />}
|
||||||
</Container>
|
</Container>
|
||||||
{ footerInfo && <Footer footerInfo={footerInfo}/> }
|
{footerInfo && <Footer footerInfo={footerInfo} />}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -11,22 +11,25 @@ const Info = styled.div`
|
|||||||
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;
|
const {
|
||||||
|
footerInfo: { numPRs, prRange, lastUpdate }
|
||||||
|
} = props;
|
||||||
return (
|
return (
|
||||||
lastUpdate &&
|
lastUpdate && (
|
||||||
<Container>
|
<Container>
|
||||||
<Info>Last Update: {localTime(lastUpdate)}</Info>
|
<Info>Last Update: {localTime(lastUpdate)}</Info>
|
||||||
<Info># of open PRs: {numPRs} ({prRange})</Info>
|
<Info>
|
||||||
|
# of open PRs: {numPRs} ({prRange})
|
||||||
|
</Info>
|
||||||
</Container>
|
</Container>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Footer;
|
export default Footer;
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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} />
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
@ -1,9 +1,4 @@
|
|||||||
{
|
{
|
||||||
"packages": [
|
"packages": ["dashboard-api", "dashboard-client", "probot", "sweeper"],
|
||||||
"dashboard-api",
|
|
||||||
"dashboard-client",
|
|
||||||
"probot",
|
|
||||||
"sweeper"
|
|
||||||
],
|
|
||||||
"version": "independent"
|
"version": "independent"
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -3,4 +3,4 @@ module.exports = {
|
|||||||
name: 'PR: potential-conflict',
|
name: 'PR: potential-conflict',
|
||||||
color: 'c2e0c6'
|
color: 'c2e0c6'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
|
@ -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/
|
||||||
|
@ -6,16 +6,19 @@ 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
|
||||||
|
timeout: 0,
|
||||||
headers: {
|
headers: {
|
||||||
accept: 'application/vnd.github.v3+json',
|
accept: 'application/vnd.github.v3+json',
|
||||||
'user-agent': 'octokit/rest.js v1.2.3' // v1.2.3 will be current version
|
// v1.2.3 will be current version
|
||||||
|
'user-agent': 'octokit/rest.js v1.2.3'
|
||||||
},
|
},
|
||||||
// custom GitHub Enterprise URL
|
// custom GitHub Enterprise URL
|
||||||
baseUrl: 'https://api.github.com',
|
baseUrl: 'https://api.github.com',
|
||||||
// Node only: advanced request options can be passed as http(s) agent
|
// Node only: advanced request options can be passed as http(s) agent
|
||||||
|
// eslint-disable-next-line no-undefined
|
||||||
agent: undefined
|
agent: undefined
|
||||||
}
|
};
|
||||||
|
|
||||||
const octokitAuth = {
|
const octokitAuth = {
|
||||||
type: 'basic',
|
type: 'basic',
|
||||||
@ -23,4 +26,4 @@ const octokitAuth = {
|
|||||||
password: process.env.GITHUB_ACCESS_TOKEN
|
password: process.env.GITHUB_ACCESS_TOKEN
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { owner, repo, prBaseUrl, octokitConfig, octokitAuth }
|
module.exports = { owner, repo, prBaseUrl, octokitConfig, octokitAuth };
|
||||||
|
@ -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,9 +64,8 @@ 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];
|
||||||
@ -74,7 +84,7 @@ const getUserInput = async (rangeType = '') => {
|
|||||||
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})`;
|
||||||
}
|
}
|
||||||
@ -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}`,
|
format: `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] {percentage}% | Elapsed Time: {duration_formatted} | ETA: {eta_formatted}`,
|
||||||
etaBuffer: 50
|
etaBuffer: 50
|
||||||
}, _cliProgress.Presets.shades_classic);
|
},
|
||||||
|
_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 };
|
||||||
|
@ -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 {
|
||||||
|
data: { total_count: count }
|
||||||
|
} = await octokit.search
|
||||||
|
.issues({
|
||||||
q: `repo:${owner}/${repo}+is:open+type:pr+base:master`,
|
q: `repo:${owner}/${repo}+is:open+type:pr+base:master`,
|
||||||
sort: 'created', order: 'asc', page: 1, per_page: 1
|
sort: 'created',
|
||||||
|
order: 'asc',
|
||||||
|
page: 1,
|
||||||
|
per_page: 1
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
console.log(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];
|
||||||
|
@ -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);
|
||||||
})
|
});
|
||||||
|
@ -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);
|
||||||
})
|
});
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
|
@ -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 {
|
||||||
else {
|
|
||||||
log.add(number, { number, closedOpened: false, errorDesc });
|
log.add(number, { number, closedOpened: false, errorDesc });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
log.finish();
|
log.finish();
|
||||||
console.log('closing/reopening of PRs complete');
|
console.log('closing/reopening of PRs complete');
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
log.finish();
|
log.finish();
|
||||||
console.log(err)
|
console.log(err);
|
||||||
})
|
});
|
||||||
|
@ -7,16 +7,20 @@ 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(
|
||||||
|
(text, { number, data: { comment, labels } }) => {
|
||||||
if (comment !== 'none' || labels !== 'none added') {
|
if (comment !== 'none' || labels !== 'none added') {
|
||||||
text += dedent`
|
text += dedent`
|
||||||
|
|
||||||
@ -31,7 +35,9 @@ const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper
|
|||||||
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');
|
||||||
})()
|
})();
|
||||||
|
@ -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);
|
||||||
})
|
});
|
||||||
|
@ -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}`,
|
format: `Update PRs [{bar}] {percentage}% | {value}/{total} | Time Elapsed: {duration_formatted} | ETA: {eta_formatted}`,
|
||||||
etaBuffer: 50
|
etaBuffer: 50
|
||||||
}, _cliProgress.Presets.shades_classic);
|
},
|
||||||
|
_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(
|
||||||
|
`${HOST}/upload?password=${process.env.GLITCH_UPLOAD_SECRET}`,
|
||||||
|
{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
})
|
}
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(`Finished uploading data for ${HOST}`);
|
console.log(`Finished uploading data for ${HOST}`);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
log.finish();
|
log.finish();
|
||||||
console.log(err)
|
console.log(err);
|
||||||
})
|
});
|
||||||
|
@ -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);
|
||||||
})
|
});
|
||||||
|
@ -5,8 +5,9 @@ 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 })
|
||||||
|
.catch(err => {
|
||||||
console.log(`PR #${number} had an error when trying to add a comment\n`);
|
console.log(`PR #${number} had an error when trying to add a comment\n`);
|
||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
|
@ -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
|
||||||
|
.addLabels({ owner, repo, number, labels })
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log(`PR #${number} added ${JSON.stringify(labels)}\n`);
|
console.log(`PR #${number} added ${JSON.stringify(labels)}\n`);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch(err => {
|
||||||
console.log(`PR #${number} had an error when trying to labels: ${JSON.stringify(labels)}\n`);
|
console.log(
|
||||||
console.log(err)
|
`PR #${number} had an error when trying to labels: ${JSON.stringify(
|
||||||
log.finish()
|
labels
|
||||||
})
|
)}\n`
|
||||||
|
);
|
||||||
|
console.log(err);
|
||||||
|
log.finish();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { addLabels };
|
module.exports = { addLabels };
|
||||||
|
@ -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
|
||||||
|
.update({ owner, repo, number, state: 'closed', base: 'master' })
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await rateLimiter(5000);
|
await rateLimiter(5000);
|
||||||
return octokit.pullRequests.update({ owner, repo , number, state: 'open', base: 'master' })
|
return octokit.pullRequests.update({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
number,
|
||||||
|
state: 'open',
|
||||||
|
base: 'master'
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
await rateLimiter(1000);
|
await rateLimiter(1000);
|
||||||
await addComment(number, 'Closed and Reopened this PR to attempt to resolve a specific Travis build failure.');
|
await addComment(
|
||||||
|
number,
|
||||||
|
'Closed and Reopened this PR to attempt to resolve a specific Travis build failure.'
|
||||||
|
);
|
||||||
})
|
})
|
||||||
.catch(async (err) => {
|
.catch(async err => {
|
||||||
const { errors } = JSON.parse(err.message); // Octokit stores message as a stringified object
|
const { errors } = JSON.parse(err.message); // Octokit stores message as a stringified object
|
||||||
const errorMg = errors[0].message;
|
const errorMg = errors[0].message;
|
||||||
if (errorMg === "state cannot be changed. The repository that submitted this pull request has been deleted.") {
|
if (
|
||||||
|
errorMg ===
|
||||||
|
'state cannot be changed. The repository that submitted this pull request has been deleted.'
|
||||||
|
) {
|
||||||
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,
|
||||||
}
|
`This PR was closed because user's repo was deleted.`
|
||||||
else {
|
);
|
||||||
|
console.log(
|
||||||
|
`PR #${number} was closed because user's repo was deleted.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = { closeOpen };
|
module.exports = { closeOpen };
|
||||||
|
@ -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);
|
||||||
|
@ -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) {
|
||||||
let { number, labels: currentLabels, user: { login: username } } = openPRs[count];
|
if (openPRs.length) {
|
||||||
const { data: prFiles } = await octokit.pullRequests.listFiles({ owner, repo, number });
|
let {
|
||||||
|
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);
|
||||||
})
|
});
|
||||||
|
@ -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
|
||||||
|
};
|
||||||
|
@ -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');
|
||||||
@ -59,7 +62,7 @@ class ProcessingLog {
|
|||||||
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 };
|
||||||
|
@ -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));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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));
|
||||||
|
@ -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 };
|
||||||
|
@ -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);
|
||||||
};
|
};
|
||||||
|
@ -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 };
|
||||||
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user