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');
|
||||
|
||||
const Container = {
|
||||
|
1158
dashboard-api/package-lock.json
generated
1158
dashboard-api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
const router = require('express').Router();
|
||||
|
||||
const container = require ('../data');
|
||||
const container = require('../data');
|
||||
|
||||
router.get('/', (request, response) => {
|
||||
response.json(container.data);
|
||||
|
@ -2,7 +2,7 @@ const router = require('express').Router();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const container = require ('../data');
|
||||
const container = require('../data');
|
||||
|
||||
router.get('/', (request, response) => {
|
||||
const { prs, startTime } = container.data;
|
||||
|
@ -1,6 +1,6 @@
|
||||
const router = require('express').Router();
|
||||
|
||||
const container = require ('../data');
|
||||
const container = require('../data');
|
||||
|
||||
router.get('/', (reqeust, response) => {
|
||||
const { indices, prs } = container.data;
|
||||
@ -8,13 +8,15 @@ router.get('/', (reqeust, response) => {
|
||||
const reportObj = prs.reduce((obj, pr) => {
|
||||
const { number, filenames, username, title } = pr;
|
||||
|
||||
filenames.forEach((filename) => {
|
||||
filenames.forEach(filename => {
|
||||
if (obj[filename]) {
|
||||
const { count, prs } = obj[filename];
|
||||
obj[filename] = { count: count + 1, prs: prs.concat({ number, username, title } ) };
|
||||
}
|
||||
else {
|
||||
obj[filename] = { count: 1, prs: [ { number, username, title } ] };
|
||||
obj[filename] = {
|
||||
count: count + 1,
|
||||
prs: prs.concat({ number, username, title })
|
||||
};
|
||||
} else {
|
||||
obj[filename] = { count: 1, prs: [{ number, username, title }] };
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
@ -29,7 +31,7 @@ router.get('/', (reqeust, response) => {
|
||||
}, [])
|
||||
.sort((a, b) => b.count - a.count);
|
||||
|
||||
response.json({ ok: true, pareto });
|
||||
response.json({ ok: true, pareto });
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
@ -8,7 +8,11 @@ router.get('/:number', (request, response) => {
|
||||
const index = indices[refNumber];
|
||||
|
||||
if (!index && index !== 0) {
|
||||
response.json({ ok: true, message: 'Unable to find that open PR #.', results: [] });
|
||||
response.json({
|
||||
ok: true,
|
||||
message: 'Unable to find that open PR #.',
|
||||
results: []
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@ -18,7 +22,7 @@ router.get('/:number', (request, response) => {
|
||||
|
||||
prs.forEach(({ number, filenames, username, title }) => {
|
||||
if (number != refNumber) {
|
||||
const matchedFilenames = filenames.filter((filename) => {
|
||||
const matchedFilenames = filenames.filter(filename => {
|
||||
return refFilenames.includes(filename);
|
||||
});
|
||||
|
||||
@ -29,7 +33,11 @@ router.get('/:number', (request, response) => {
|
||||
});
|
||||
|
||||
if (!results.length) {
|
||||
response.json({ ok: true, message: `No other open PRs with at least one filename which PR #${refNumber} has.`, results: [] });
|
||||
response.json({
|
||||
ok: true,
|
||||
message: `No other open PRs with at least one filename which PR #${refNumber} has.`,
|
||||
results: []
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
const router = require('express').Router();
|
||||
|
||||
const container = require ('../data');
|
||||
const container = require('../data');
|
||||
|
||||
router.get('/', (request, response) => {
|
||||
const { indices, prs } = container.data;
|
||||
const value = request.query.value;
|
||||
const value = request.query.value;
|
||||
|
||||
if (value) {
|
||||
const filesFound = {};
|
||||
|
||||
prs.forEach(({ number, filenames, username, title }) => {
|
||||
filenames.forEach((filename) => {
|
||||
filenames.forEach(filename => {
|
||||
if (filename.toLowerCase().includes(value.toLowerCase())) {
|
||||
const prObj = {
|
||||
number,
|
||||
@ -21,17 +21,18 @@ router.get('/', (request, response) => {
|
||||
|
||||
if (filesFound.hasOwnProperty(filename)) {
|
||||
filesFound[filename].push(prObj);
|
||||
}
|
||||
else {
|
||||
filesFound[filename] = [prObj]
|
||||
} else {
|
||||
filesFound[filename] = [prObj];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let results = Object.keys(filesFound)
|
||||
.map((filename) => ({ filename, prs: filesFound[filename] }))
|
||||
.sort((a, b) => a.filename === b.filename ? 0 : a.filename < b.filename ? -1 : 1);
|
||||
.map(filename => ({ filename, prs: filesFound[filename] }))
|
||||
.sort((a, b) =>
|
||||
a.filename === b.filename ? 0 : a.filename < b.filename ? -1 : 1
|
||||
);
|
||||
|
||||
if (!results.length) {
|
||||
response.json({ ok: true, message: 'No matching results.', results: [] });
|
||||
|
@ -1,7 +1,7 @@
|
||||
require('dotenv').config();
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const multer = require('multer');
|
||||
const multer = require('multer');
|
||||
const router = require('express').Router();
|
||||
|
||||
const container = require('../data');
|
||||
@ -16,12 +16,14 @@ router.post('/', upload.single('file'), (request, response) => {
|
||||
}
|
||||
|
||||
if (!!secret && password === secret) {
|
||||
const { file: { path: filePath } } = request;
|
||||
const {
|
||||
file: { path: filePath }
|
||||
} = request;
|
||||
const uploaded = path.resolve(__dirname, '../' + filePath);
|
||||
const dest = path.resolve(__dirname, '../data.json');
|
||||
const data = JSON.parse(fs.readFileSync(uploaded));
|
||||
const { indices, prs } = data;
|
||||
const dataOK = Object.keys(data).every((key) => {
|
||||
const dataOK = Object.keys(data).every(key => {
|
||||
return !!data[key];
|
||||
});
|
||||
const lengthsMatch = Object.keys(indices).length === prs.length;
|
||||
@ -29,8 +31,7 @@ router.post('/', upload.single('file'), (request, response) => {
|
||||
if (dataOK && lengthsMatch) {
|
||||
container.update(data);
|
||||
fs.renameSync(uploaded, dest);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const logPath = path.resolve(__dirname, '../log.txt');
|
||||
const errorMsg = `Upload failed with ${uploaded}, dataOK: ${dataOK}, lengthsMatch: ${lengthsMatch}.`;
|
||||
fs.appendFileSync(logPath, errorMsg);
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,13 +1,23 @@
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
|
||||
const app = express();
|
||||
const { catchAll, pareto, pr, search, info, getCurrData, upload } = require('./routes');
|
||||
const {
|
||||
catchAll,
|
||||
pareto,
|
||||
pr,
|
||||
search,
|
||||
info,
|
||||
getCurrData,
|
||||
upload
|
||||
} = require('./routes');
|
||||
|
||||
app.use(express.static('public'));
|
||||
app.use((request, response, next) => {
|
||||
response.header('Access-Control-Allow-Origin', '*');
|
||||
response.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
|
||||
response.header(
|
||||
'Access-Control-Allow-Headers',
|
||||
'Origin, X-Requested-With, Content-Type, Accept'
|
||||
);
|
||||
response.header('Access-Control-Allow-Methods', 'GET');
|
||||
next();
|
||||
});
|
||||
|
@ -1,25 +1,25 @@
|
||||
{
|
||||
"name": "@freecodecamp/dashboard-client",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react": "^16.6.3",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-scripts": "2.1.1",
|
||||
"styled-components": "^4.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build && rm build/static/js/*.map && rm build/static/css/*.map",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
{
|
||||
"name": "@freecodecamp/dashboard-client",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react": "^16.6.3",
|
||||
"react-dom": "^16.6.3",
|
||||
"react-scripts": "2.1.1",
|
||||
"styled-components": "^4.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build && rm build/static/js/*.map && rm build/static/css/*.map",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
||||
|
@ -10,82 +10,92 @@ import { ENDPOINT_INFO } from './constants';
|
||||
|
||||
console.log(ENDPOINT_INFO);
|
||||
const PageContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 960px;
|
||||
width: 90vw;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 4px 0 #777;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-width: 960px;
|
||||
width: 90vw;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 4px 0 #777;
|
||||
`;
|
||||
|
||||
const Title = styled.h1`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.primary};
|
||||
color: white;
|
||||
width: 100%;
|
||||
padding: 3px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.primary};
|
||||
color: white;
|
||||
width: 100%;
|
||||
padding: 3px;
|
||||
`;
|
||||
|
||||
const imgStyle = {
|
||||
paddingRight: '20px',
|
||||
paddingTop: '6px'
|
||||
paddingRight: '20px',
|
||||
paddingTop: '6px'
|
||||
};
|
||||
|
||||
class App extends Component {
|
||||
state = {
|
||||
view: 'search',
|
||||
footerInfo: null
|
||||
};
|
||||
state = {
|
||||
view: 'search',
|
||||
footerInfo: null
|
||||
};
|
||||
|
||||
updateInfo() {
|
||||
fetch(ENDPOINT_INFO)
|
||||
.then((response) => response.json())
|
||||
.then(({ ok, numPRs, prRange, lastUpdate }) => {
|
||||
if (ok) {
|
||||
const footerInfo = { numPRs, prRange, lastUpdate };
|
||||
this.setState((prevState) => ({ footerInfo }));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
updateInfo() {
|
||||
fetch(ENDPOINT_INFO)
|
||||
.then(response => response.json())
|
||||
.then(({ ok, numPRs, prRange, lastUpdate }) => {
|
||||
if (ok) {
|
||||
const footerInfo = { numPRs, prRange, lastUpdate };
|
||||
this.setState(prevState => ({ footerInfo }));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// do nothing
|
||||
});
|
||||
}
|
||||
|
||||
handleViewChange = ( { target: { id } }) => {
|
||||
const view = id.replace('tabs-', '');
|
||||
this.setState((prevState) => ({ ...this.clearObj, view }));
|
||||
this.updateInfo();
|
||||
}
|
||||
handleViewChange = ({ target: { id } }) => {
|
||||
const view = id.replace('tabs-', '');
|
||||
this.setState(prevState => ({ ...this.clearObj, view }));
|
||||
this.updateInfo();
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.updateInfo();
|
||||
}
|
||||
componentDidMount() {
|
||||
this.updateInfo();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { handleViewChange, state: { view, footerInfo } } = this;
|
||||
return (
|
||||
<PageContainer>
|
||||
<Title><img style={imgStyle} src="https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/e/d/ed1c70bda321aaeee9e6c20ab650ce8bc34899fa.svg" alt="Free Code Camp Logo" /> Moderator Tools</Title>
|
||||
<Tabs view={view} onViewChange={handleViewChange}/>
|
||||
<Container>
|
||||
{ view === 'search' && <Search /> }
|
||||
{ view === 'reports' && <Pareto /> }
|
||||
</Container>
|
||||
{ footerInfo && <Footer footerInfo={footerInfo}/> }
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
render() {
|
||||
const {
|
||||
handleViewChange,
|
||||
state: { view, footerInfo }
|
||||
} = this;
|
||||
return (
|
||||
<PageContainer>
|
||||
<Title>
|
||||
<img
|
||||
style={imgStyle}
|
||||
src="https://discourse-user-assets.s3.dualstack.us-east-1.amazonaws.com/original/3X/e/d/ed1c70bda321aaeee9e6c20ab650ce8bc34899fa.svg"
|
||||
alt="Free Code Camp Logo"
|
||||
/>{' '}
|
||||
Moderator Tools
|
||||
</Title>
|
||||
<Tabs view={view} onViewChange={handleViewChange} />
|
||||
<Container>
|
||||
{view === 'search' && <Search />}
|
||||
{view === 'reports' && <Pareto />}
|
||||
</Container>
|
||||
{footerInfo && <Footer footerInfo={footerInfo} />}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default App;
|
||||
|
@ -14,25 +14,20 @@ const List = styled.div`
|
||||
const filenameTitle = { fontWeight: '600' };
|
||||
|
||||
const FilenameResults = ({ searchValue, results }) => {
|
||||
const elements = results.map((result) => {
|
||||
const elements = results.map(result => {
|
||||
const { filename, prs: prObjects } = result;
|
||||
const prs = prObjects.map(({ number, username, title }, index) => {
|
||||
return (
|
||||
<ListItem
|
||||
number={number}
|
||||
username={username}
|
||||
prTitle={title}
|
||||
/>
|
||||
);
|
||||
return <ListItem number={number} username={username} prTitle={title} />;
|
||||
});
|
||||
|
||||
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
|
||||
return (
|
||||
<Result key={filename}>
|
||||
<span style={filenameTitle}>{filename}</span> <a href={fileOnMaster} rel="noopener noreferrer" target="_blank">(File on Master)</a>
|
||||
<List>
|
||||
{prs}
|
||||
</List>
|
||||
<span style={filenameTitle}>{filename}</span>{' '}
|
||||
<a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
|
||||
(File on Master)
|
||||
</a>
|
||||
<List>{prs}</List>
|
||||
</Result>
|
||||
);
|
||||
});
|
||||
|
@ -2,31 +2,34 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Container = styled.div`
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const Info = styled.div`
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
`;
|
||||
|
||||
const Footer = (props) => {
|
||||
|
||||
const localTime = (lastUpdate) => {
|
||||
const newTime = new Date(lastUpdate);
|
||||
return newTime.toLocaleString();
|
||||
}
|
||||
|
||||
const { footerInfo: { numPRs, prRange, lastUpdate } } = props;
|
||||
return (
|
||||
lastUpdate &&
|
||||
<Container>
|
||||
<Info>Last Update: {localTime(lastUpdate)}</Info>
|
||||
<Info># of open PRs: {numPRs} ({prRange})</Info>
|
||||
</Container>
|
||||
);
|
||||
const Footer = props => {
|
||||
const localTime = lastUpdate => {
|
||||
const newTime = new Date(lastUpdate);
|
||||
return newTime.toLocaleString();
|
||||
};
|
||||
|
||||
const {
|
||||
footerInfo: { numPRs, prRange, lastUpdate }
|
||||
} = props;
|
||||
return (
|
||||
lastUpdate && (
|
||||
<Container>
|
||||
<Info>Last Update: {localTime(lastUpdate)}</Info>
|
||||
<Info>
|
||||
# of open PRs: {numPRs} ({prRange})
|
||||
</Info>
|
||||
</Container>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default Footer;
|
||||
|
@ -16,7 +16,12 @@ const ListItem = ({ number, username, prTitle: title }) => {
|
||||
const prUrl = `https://github.com/freeCodeCamp/freeCodeCamp/pull/${number}`;
|
||||
return (
|
||||
<Container>
|
||||
<a style={prNumStyle} href={prUrl} rel="noopener noreferrer" target="_blank">
|
||||
<a
|
||||
style={prNumStyle}
|
||||
href={prUrl}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
#{number}
|
||||
</a>
|
||||
<span style={usernameStyle}>{username}</span>
|
||||
|
@ -22,38 +22,42 @@ class Pareto extends React.Component {
|
||||
|
||||
componentDidMount() {
|
||||
fetch(ENDPOINT_PARETO)
|
||||
.then((response) => response.json())
|
||||
.then(response => response.json())
|
||||
.then(({ ok, pareto }) => {
|
||||
if (ok) {
|
||||
if (!pareto.length) {
|
||||
pareto.push({ filename: 'Nothing to show in Pareto Report', count: 0, prs: [] });
|
||||
pareto.push({
|
||||
filename: 'Nothing to show in Pareto Report',
|
||||
count: 0,
|
||||
prs: []
|
||||
});
|
||||
}
|
||||
this.setState((prevState) => ({ data: pareto }));
|
||||
this.setState(prevState => ({ data: pareto }));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
const pareto = [{ filename: 'Nothing to show in Pareto Report', count: 0, prs: [] }];
|
||||
this.setState((prevState) => ({ data: pareto }));
|
||||
const pareto = [
|
||||
{ filename: 'Nothing to show in Pareto Report', count: 0, prs: [] }
|
||||
];
|
||||
this.setState(prevState => ({ data: pareto }));
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { data } = this.state;
|
||||
const elements = data.map((entry) => {
|
||||
const elements = data.map(entry => {
|
||||
const { filename, count, prs } = entry;
|
||||
const prsList = prs.map(({ number, username, title }) => {
|
||||
return (
|
||||
<ListItem
|
||||
number={number}
|
||||
username={username}
|
||||
prTitle={title}
|
||||
/>
|
||||
)
|
||||
return <ListItem number={number} username={username} prTitle={title} />;
|
||||
});
|
||||
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
|
||||
return (
|
||||
<Result key={filename}>
|
||||
<span style={filenameTitle}>{filename}</span> <a href={fileOnMaster} rel="noopener noreferrer" target="_blank">(File on Master)</a><br />
|
||||
<span style={filenameTitle}>{filename}</span>{' '}
|
||||
<a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
|
||||
(File on Master)
|
||||
</a>
|
||||
<br />
|
||||
<details style={detailsStyle}>
|
||||
<summary># of PRs: {count}</summary>
|
||||
<List>{prsList}</List>
|
||||
|
@ -16,27 +16,24 @@ const PrResults = ({ searchValue, results }) => {
|
||||
const fileOnMaster = `https://github.com/freeCodeCamp/freeCodeCamp/blob/master/${filename}`;
|
||||
return (
|
||||
<li key={`${number}-${index}`}>
|
||||
{filename} <a href={fileOnMaster} rel="noopener noreferrer" target="_blank">(File on Master)</a>
|
||||
{filename}{' '}
|
||||
<a href={fileOnMaster} rel="noopener noreferrer" target="_blank">
|
||||
(File on Master)
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Result key={`${number}-${idx}`}>
|
||||
<ListItem
|
||||
number={number}
|
||||
username={username}
|
||||
prTitle={title}
|
||||
/>
|
||||
<List>
|
||||
{files}
|
||||
</List>
|
||||
<ListItem number={number} username={username} prTitle={title} />
|
||||
<List>{files}</List>
|
||||
</Result>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<FullWidthDiv style={{width: '100%'}}>
|
||||
<FullWidthDiv style={{ width: '100%' }}>
|
||||
{results.length ? <h3>Results for PR# {searchValue}</h3> : null}
|
||||
{elements}
|
||||
</FullWidthDiv>
|
||||
|
@ -19,82 +19,106 @@ class Search extends Component {
|
||||
|
||||
inputRef = React.createRef();
|
||||
|
||||
handleInputEvent = (event) => {
|
||||
const { type, key, target: { value: searchValue } } = event;
|
||||
handleInputEvent = event => {
|
||||
const {
|
||||
type,
|
||||
key,
|
||||
target: { value: searchValue }
|
||||
} = event;
|
||||
|
||||
if (type === 'change') {
|
||||
if (this.state.selectedOption === 'pr'){
|
||||
if (this.state.selectedOption === 'pr') {
|
||||
if (Number(searchValue) || searchValue === '') {
|
||||
this.setState((prevState) => ({ searchValue, results: [] }));
|
||||
this.setState(prevState => ({ searchValue, results: [] }));
|
||||
}
|
||||
} else {
|
||||
this.setState(prevState => ({ searchValue, results: [] }));
|
||||
}
|
||||
else {
|
||||
this.setState((prevState) => ({ searchValue, results: [] }));
|
||||
}
|
||||
}
|
||||
else if (type === 'keypress' && key === 'Enter') {
|
||||
} else if (type === 'keypress' && key === 'Enter') {
|
||||
this.searchPRs(searchValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleButtonClick = () => {
|
||||
const { searchValue } = this.state;
|
||||
if (searchValue) {
|
||||
this.searchPRs(searchValue);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
this.inputRef.current.focus();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleOptionChange = (changeEvent) => {
|
||||
handleOptionChange = changeEvent => {
|
||||
const selectedOption = changeEvent.target.value;
|
||||
|
||||
this.setState((prevState) => ({ selectedOption, ...this.clearObj }));
|
||||
this.setState(prevState => ({ selectedOption, ...this.clearObj }));
|
||||
this.inputRef.current.focus();
|
||||
}
|
||||
};
|
||||
|
||||
searchPRs = (value) => {
|
||||
searchPRs = value => {
|
||||
const { selectedOption } = this.state;
|
||||
const fetchUrl = selectedOption === 'pr' ?
|
||||
`${ENDPOINT_PR}/${value}` :
|
||||
`${ENDPOINT_SEARCH}/?value=${value}`;
|
||||
const fetchUrl =
|
||||
selectedOption === 'pr'
|
||||
? `${ENDPOINT_PR}/${value}`
|
||||
: `${ENDPOINT_SEARCH}/?value=${value}`;
|
||||
|
||||
fetch(fetchUrl)
|
||||
.then((response) => response.json())
|
||||
.then(response => response.json())
|
||||
.then(({ ok, message, results }) => {
|
||||
if (ok) {
|
||||
this.setState((prevState) => ({ message, results }));
|
||||
this.setState(prevState => ({ message, results }));
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState((prevState) => (this.clearObj));
|
||||
this.setState(prevState => this.clearObj);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.inputRef.current.focus();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { handleButtonClick, handleInputEvent, inputRef, handleOptionChange, state } = this;
|
||||
const {
|
||||
handleButtonClick,
|
||||
handleInputEvent,
|
||||
inputRef,
|
||||
handleOptionChange,
|
||||
state
|
||||
} = this;
|
||||
const { searchValue, message, results, selectedOption } = state;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<SearchOption value="pr" onOptionChange={handleOptionChange} selectedOption={selectedOption}>
|
||||
<SearchOption
|
||||
value="pr"
|
||||
onOptionChange={handleOptionChange}
|
||||
selectedOption={selectedOption}
|
||||
>
|
||||
PR #
|
||||
</SearchOption>
|
||||
<SearchOption value="filename" onOptionChange={handleOptionChange} selectedOption={selectedOption}>
|
||||
<SearchOption
|
||||
value="filename"
|
||||
onOptionChange={handleOptionChange}
|
||||
selectedOption={selectedOption}
|
||||
>
|
||||
Filename
|
||||
</SearchOption>
|
||||
</div>
|
||||
<Input ref={inputRef} value={searchValue} onInputEvent={handleInputEvent} />
|
||||
<Input
|
||||
ref={inputRef}
|
||||
value={searchValue}
|
||||
onInputEvent={handleInputEvent}
|
||||
/>
|
||||
<button onClick={handleButtonClick}>Search</button>
|
||||
{message}
|
||||
{selectedOption === 'pr' && <PrResults searchValue={searchValue} results={results} /> }
|
||||
{selectedOption === 'filename' && <FilenameResults searchValue={searchValue} results={results} /> }
|
||||
{selectedOption === 'pr' && (
|
||||
<PrResults searchValue={searchValue} results={results} />
|
||||
)}
|
||||
{selectedOption === 'filename' && (
|
||||
<FilenameResults searchValue={searchValue} results={results} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ const Container = styled.div`
|
||||
`;
|
||||
|
||||
const Tab = styled.div`
|
||||
background: ${({ active, theme }) => active ? theme.primary : 'white'};
|
||||
color: ${({ active, theme }) => active ? 'white' : theme.primary};
|
||||
background: ${({ active, theme }) => (active ? theme.primary : 'white')};
|
||||
color: ${({ active, theme }) => (active ? 'white' : theme.primary)};
|
||||
font-size: 18px;
|
||||
padding: 5px;
|
||||
border: 2px solid ${({ theme }) => theme.primary};
|
||||
@ -20,7 +20,7 @@ const Tab = styled.div`
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background: #EEEEEE;
|
||||
background: #eeeeee;
|
||||
color: ${({ theme }) => theme.primary};
|
||||
}
|
||||
|
||||
@ -32,8 +32,12 @@ const Tab = styled.div`
|
||||
const Tabs = ({ view, onViewChange }) => {
|
||||
return (
|
||||
<Container>
|
||||
<Tab id="tabs-search" onClick={onViewChange} active={view === 'search'}>Search</Tab>
|
||||
<Tab id="tabs-reports" onClick={onViewChange} active={view === 'reports'}>Pareto</Tab>
|
||||
<Tab id="tabs-search" onClick={onViewChange} active={view === 'search'}>
|
||||
Search
|
||||
</Tab>
|
||||
<Tab id="tabs-reports" onClick={onViewChange} active={view === 'reports'}>
|
||||
Pareto
|
||||
</Tab>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
const API_HOST = !!process.env.REACT_APP_DEV ?
|
||||
'http://localhost:3001' :
|
||||
'https://pr-relations.glitch.me';
|
||||
const API_HOST = !!process.env.REACT_APP_DEV
|
||||
? 'http://localhost:3001'
|
||||
: 'https://pr-relations.glitch.me';
|
||||
const ENDPOINT_INFO = API_HOST + '/info';
|
||||
const ENDPOINT_PARETO = API_HOST + '/pareto';
|
||||
const ENDPOINT_PR = API_HOST + '/pr';
|
||||
const ENDPOINT_SEARCH = API_HOST + '/search'
|
||||
const ENDPOINT_SEARCH = API_HOST + '/search';
|
||||
|
||||
export {
|
||||
API_HOST,
|
||||
|
@ -12,6 +12,7 @@ ReactDOM.render(
|
||||
<ThemeProvider theme={theme}>
|
||||
<App />
|
||||
</ThemeProvider>,
|
||||
document.getElementById('root'));
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
serviceWorker.unregister();
|
||||
|
@ -1,9 +1,4 @@
|
||||
{
|
||||
"packages": [
|
||||
"dashboard-api",
|
||||
"dashboard-client",
|
||||
"probot",
|
||||
"sweeper"
|
||||
],
|
||||
"packages": ["dashboard-api", "dashboard-client", "probot", "sweeper"],
|
||||
"version": "independent"
|
||||
}
|
||||
|
@ -1,31 +1,31 @@
|
||||
const debug = require('debug')('probot:presolver')
|
||||
const Presolver = require('./lib/presolver')
|
||||
const debug = require('debug')('probot:presolver');
|
||||
const Presolver = require('./lib/presolver');
|
||||
|
||||
async function probotPlugin (robot) {
|
||||
async function probotPlugin(robot) {
|
||||
const events = [
|
||||
'pull_request.opened',
|
||||
'pull_request.edited',
|
||||
'pull_request.synchronize',
|
||||
'pull_request.reopened',
|
||||
'pull_request.labeled'
|
||||
]
|
||||
];
|
||||
|
||||
robot.on(events, presolve.bind(null, robot))
|
||||
robot.on(events, presolve.bind(null, robot));
|
||||
}
|
||||
|
||||
async function presolve (app, context) {
|
||||
const presolver = forRepository(context)
|
||||
const pullRequest = getPullRequest(context)
|
||||
return presolver.presolve(pullRequest)
|
||||
async function presolve(app, context) {
|
||||
const presolver = forRepository(context);
|
||||
const pullRequest = getPullRequest(context);
|
||||
return presolver.presolve(pullRequest);
|
||||
}
|
||||
|
||||
function forRepository (context) {
|
||||
const config = Object.assign({}, context.repo({ logger: debug }))
|
||||
return new Presolver(context, config)
|
||||
function forRepository(context) {
|
||||
const config = ({...context.repo({ logger: debug })});
|
||||
return new Presolver(context, config);
|
||||
}
|
||||
|
||||
function getPullRequest (context) {
|
||||
return context.payload.pull_request || context.payload.review.pull_request
|
||||
function getPullRequest(context) {
|
||||
return context.payload.pull_request || context.payload.review.pull_request;
|
||||
}
|
||||
|
||||
module.exports = probotPlugin
|
||||
module.exports = probotPlugin;
|
||||
|
@ -3,4 +3,4 @@ module.exports = {
|
||||
name: 'PR: potential-conflict',
|
||||
color: 'c2e0c6'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,98 +1,98 @@
|
||||
class Presolver {
|
||||
constructor (context, { owner, repo, logger = console, ...config }) {
|
||||
this.context = context
|
||||
this.github = context.github
|
||||
this.logger = logger
|
||||
constructor(context, { owner, repo, logger = console, ...config }) {
|
||||
this.context = context;
|
||||
this.github = context.github;
|
||||
this.logger = logger;
|
||||
this.config = Object.assign({}, require('./defaults'), config || {}, {
|
||||
owner,
|
||||
repo
|
||||
})
|
||||
});
|
||||
// console.log(this.config)
|
||||
this.pullRequest = {}
|
||||
this.conflictingFiles = []
|
||||
this.pullRequest = {};
|
||||
this.conflictingFiles = [];
|
||||
}
|
||||
|
||||
async presolve (pullRequest) {
|
||||
Object.assign(this.pullRequest, pullRequest)
|
||||
await this._ensurePresolverLabelExists()
|
||||
await this._getState()
|
||||
const labelObj = this.config.labelPRConflict
|
||||
async presolve(pullRequest) {
|
||||
Object.assign(this.pullRequest, pullRequest);
|
||||
await this._ensurePresolverLabelExists();
|
||||
await this._getState();
|
||||
const labelObj = this.config.labelPRConflict;
|
||||
if (this.conflictingFiles.length) {
|
||||
await this._addLabel(labelObj)
|
||||
await this._addLabel(labelObj);
|
||||
}
|
||||
}
|
||||
|
||||
async _getState () {
|
||||
async _getState() {
|
||||
// console.log(this.context.issue())
|
||||
const files = await this.github.pullRequests.getFiles(this.context.issue())
|
||||
const files = await this.github.pullRequests.getFiles(this.context.issue());
|
||||
// console.log(files)
|
||||
const {owner, repo} = this.config
|
||||
const prs = await this.github.pullRequests.getAll({ owner, repo })
|
||||
.data || []
|
||||
const { owner, repo } = this.config;
|
||||
const prs =
|
||||
(await this.github.pullRequests.getAll({ owner, repo }).data) || [];
|
||||
// console.log(prs)
|
||||
await this._getConflictingFiles(prs, files)
|
||||
await this._getConflictingFiles(prs, files);
|
||||
}
|
||||
|
||||
async _getConflictingFiles (prs, files) {
|
||||
const {owner, repo} = this.config
|
||||
const github = this.github
|
||||
const conflictingFiles = this.conflictingFiles
|
||||
async _getConflictingFiles(prs, files) {
|
||||
const { owner, repo } = this.config;
|
||||
const github = this.github;
|
||||
const conflictingFiles = this.conflictingFiles;
|
||||
// console.log(prs, files)
|
||||
prs.forEach((pr) => {
|
||||
prs.forEach(pr => {
|
||||
const prIssue = {
|
||||
number: pr.number,
|
||||
owner: owner,
|
||||
repo: repo
|
||||
}
|
||||
var prFiles = github.pullRequests.getFiles(prIssue)
|
||||
prFiles.data.forEach((file) => {
|
||||
files.data.forEach((f) => {
|
||||
};
|
||||
var prFiles = github.pullRequests.getFiles(prIssue);
|
||||
prFiles.data.forEach(file => {
|
||||
files.data.forEach(f => {
|
||||
// console.log(f, file)
|
||||
if (f.filename === file.filename) {
|
||||
conflictingFiles.push(file.filename)
|
||||
conflictingFiles.push(file.filename);
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async _ensurePresolverLabelExists () {
|
||||
const label = this.config.labelPRConflict
|
||||
await this._createLabel(label)
|
||||
async _ensurePresolverLabelExists() {
|
||||
const label = this.config.labelPRConflict;
|
||||
await this._createLabel(label);
|
||||
}
|
||||
|
||||
async _createLabel (labelObj) {
|
||||
const { owner, repo } = this.config
|
||||
const github = this.github
|
||||
async _createLabel(labelObj) {
|
||||
const { owner, repo } = this.config;
|
||||
const github = this.github;
|
||||
//console.log(this.github.issues.getLabel({ owner, repo, name: labelObj.name }))
|
||||
return this.github.issues
|
||||
.getLabel({ owner, repo, name: labelObj.name })
|
||||
.catch(() => {
|
||||
console.log(labelObj)
|
||||
console.log(labelObj);
|
||||
return github.issues.createLabel({
|
||||
owner,
|
||||
repo,
|
||||
name: labelObj.name,
|
||||
color: labelObj.color
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_getLabel (labelObj) {
|
||||
_getLabel(labelObj) {
|
||||
return new Promise((resolve, reject) => {
|
||||
for (const label of this.pullRequest.labels) {
|
||||
if (labelObj && labelObj.name && label.name === labelObj.name) {
|
||||
resolve(labelObj)
|
||||
resolve(labelObj);
|
||||
}
|
||||
}
|
||||
reject(new Error('Not found'))
|
||||
})
|
||||
reject(new Error('Not found'));
|
||||
});
|
||||
}
|
||||
async _addLabel (labelObj) {
|
||||
const { owner, repo } = this.config
|
||||
const number = this.pullRequest.number
|
||||
const label = this.config.labelPRConflict
|
||||
const github = this.github
|
||||
async _addLabel(labelObj) {
|
||||
const { owner, repo } = this.config;
|
||||
const number = this.pullRequest.number;
|
||||
const label = this.config.labelPRConflict;
|
||||
const github = this.github;
|
||||
// Check if a label does not exist. If it does, it addes the label.
|
||||
return this._getLabel(label).catch(() => {
|
||||
// console.log(labelObj)
|
||||
@ -101,9 +101,9 @@ class Presolver {
|
||||
repo,
|
||||
number,
|
||||
labels: [labelObj.name]
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Presolver
|
||||
module.exports = Presolver;
|
||||
|
18354
probot/package-lock.json
generated
18354
probot/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -40,15 +40,9 @@
|
||||
"merged_at": null,
|
||||
"merge_commit_sha": "fc696e1a288b07f66318e775fdd9ea619122d09e",
|
||||
"assignee": null,
|
||||
"assignees": [
|
||||
|
||||
],
|
||||
"requested_reviewers": [
|
||||
|
||||
],
|
||||
"requested_teams": [
|
||||
|
||||
],
|
||||
"assignees": [],
|
||||
"requested_reviewers": [],
|
||||
"requested_teams": [],
|
||||
"labels": [
|
||||
{
|
||||
"id": 511502933,
|
||||
@ -458,4 +452,4 @@
|
||||
"id": 421598,
|
||||
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,18 +40,10 @@
|
||||
"merged_at": null,
|
||||
"merge_commit_sha": null,
|
||||
"assignee": null,
|
||||
"assignees": [
|
||||
|
||||
],
|
||||
"requested_reviewers": [
|
||||
|
||||
],
|
||||
"requested_teams": [
|
||||
|
||||
],
|
||||
"labels": [
|
||||
|
||||
],
|
||||
"assignees": [],
|
||||
"requested_reviewers": [],
|
||||
"requested_teams": [],
|
||||
"labels": [],
|
||||
"milestone": null,
|
||||
"commits_url": "https://api.github.com/repos/tbushman/pu/pulls/15/commits",
|
||||
"review_comments_url": "https://api.github.com/repos/tbushman/pu/pulls/15/comments",
|
||||
@ -451,4 +443,4 @@
|
||||
"id": 421598,
|
||||
"node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uNDIxNTk4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
const expect = require('expect')
|
||||
const { Probot } = require('probot')
|
||||
const prSuccessEvent = require('./events/pullRequests.opened')
|
||||
const prExisting = require('./events/pullRequests.existing')
|
||||
const probotPlugin = require('..')
|
||||
const expect = require('expect');
|
||||
const { Probot } = require('probot');
|
||||
const prSuccessEvent = require('./events/pullRequests.opened');
|
||||
const prExisting = require('./events/pullRequests.existing');
|
||||
const probotPlugin = require('..');
|
||||
|
||||
describe('Presolver', () => {
|
||||
let probot, github
|
||||
let probot, github;
|
||||
|
||||
beforeEach(() => {
|
||||
probot = new Probot({})
|
||||
probot = new Probot({});
|
||||
// Load our app into probot
|
||||
let app = probot.load(probotPlugin)
|
||||
let app = probot.load(probotPlugin);
|
||||
// This is an easy way to mock out the GitHub API
|
||||
// https://probot.github.io/docs/testing/
|
||||
github = {
|
||||
@ -21,31 +21,39 @@ describe('Presolver', () => {
|
||||
createLabel: jest.fn()
|
||||
},
|
||||
repos: {
|
||||
getContent: () => Promise.resolve({data: Buffer.from(`
|
||||
getContent: () =>
|
||||
Promise.resolve({
|
||||
data: Buffer.from(
|
||||
`
|
||||
issueOpened: Message
|
||||
pullRequestOpened: Message
|
||||
`).toString('base64')})
|
||||
`
|
||||
).toString('base64')
|
||||
})
|
||||
},
|
||||
pullRequests: {
|
||||
getFiles: jest.fn().mockImplementation(() => ({
|
||||
data: [
|
||||
{filename: 'test.txt'}
|
||||
]
|
||||
data: [{ filename: 'test.txt' }]
|
||||
})),
|
||||
getAll: jest.fn().mockImplementation(() => ({ data: [prExisting.pull_request] }))
|
||||
getAll: jest
|
||||
.fn()
|
||||
.mockImplementation(() => ({ data: [prExisting.pull_request] }))
|
||||
}
|
||||
}
|
||||
app.auth = () => Promise.resolve(github)
|
||||
};
|
||||
app.auth = () => Promise.resolve(github);
|
||||
// just return a test token
|
||||
// app.app = () => 'test'
|
||||
})
|
||||
});
|
||||
|
||||
test('adds a label if a PR has changes to files targeted by an existing PR', async () => {
|
||||
// Receive a webhook event
|
||||
await probot.receive({name: 'pull_request.opened', payload: prSuccessEvent})
|
||||
expect(github.issues.addLabels).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
await probot.receive({
|
||||
name: 'pull_request.opened',
|
||||
payload: prSuccessEvent
|
||||
});
|
||||
expect(github.issues.addLabels).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
// For more information about testing with Jest see:
|
||||
// https://facebook.github.io/jest/
|
||||
|
@ -6,21 +6,24 @@ const fccBaseUrl = `https://github.com/${owner}/${repo}/`;
|
||||
const prBaseUrl = `${fccBaseUrl}pull/`;
|
||||
|
||||
const octokitConfig = {
|
||||
timeout: 0, // 0 means no request timeout
|
||||
headers: {
|
||||
accept: 'application/vnd.github.v3+json',
|
||||
'user-agent': 'octokit/rest.js v1.2.3' // v1.2.3 will be current version
|
||||
},
|
||||
// custom GitHub Enterprise URL
|
||||
baseUrl: 'https://api.github.com',
|
||||
// Node only: advanced request options can be passed as http(s) agent
|
||||
agent: undefined
|
||||
}
|
||||
|
||||
const octokitAuth = {
|
||||
type: 'basic',
|
||||
username: process.env.GITHUB_USERNAME,
|
||||
password: process.env.GITHUB_ACCESS_TOKEN
|
||||
// 0 means no request timeout
|
||||
timeout: 0,
|
||||
headers: {
|
||||
accept: 'application/vnd.github.v3+json',
|
||||
// v1.2.3 will be current version
|
||||
'user-agent': 'octokit/rest.js v1.2.3'
|
||||
},
|
||||
// custom GitHub Enterprise URL
|
||||
baseUrl: 'https://api.github.com',
|
||||
// Node only: advanced request options can be passed as http(s) agent
|
||||
// eslint-disable-next-line no-undefined
|
||||
agent: undefined
|
||||
};
|
||||
|
||||
module.exports = { owner, repo, prBaseUrl, octokitConfig, octokitAuth }
|
||||
const octokitAuth = {
|
||||
type: 'basic',
|
||||
username: process.env.GITHUB_USERNAME,
|
||||
password: process.env.GITHUB_ACCESS_TOKEN
|
||||
};
|
||||
|
||||
module.exports = { owner, repo, prBaseUrl, octokitConfig, octokitAuth };
|
||||
|
@ -8,8 +8,14 @@ const { getRange, getCount } = require('./pr-stats');
|
||||
|
||||
octokit.authenticate(octokitAuth);
|
||||
|
||||
const paginate = async function paginate (method, octokit, firstPR, lastPR, prPropsToGet, progressBar) {
|
||||
|
||||
const paginate = async function paginate(
|
||||
method,
|
||||
octokit,
|
||||
firstPR,
|
||||
lastPR,
|
||||
prPropsToGet,
|
||||
progressBar
|
||||
) {
|
||||
const prFilter = (prs, first, last, prPropsToGet) => {
|
||||
const filtered = [];
|
||||
for (let pr of prs) {
|
||||
@ -17,7 +23,7 @@ const paginate = async function paginate (method, octokit, firstPR, lastPR, prPr
|
||||
const propsObj = prPropsToGet.reduce((obj, prop) => {
|
||||
obj[prop] = pr[prop];
|
||||
return obj;
|
||||
} ,{});
|
||||
}, {});
|
||||
filtered.push(propsObj);
|
||||
}
|
||||
if (pr.number >= last) {
|
||||
@ -29,16 +35,21 @@ const paginate = async function paginate (method, octokit, firstPR, lastPR, prPr
|
||||
};
|
||||
|
||||
const methodProps = {
|
||||
owner, repo, state: 'open',
|
||||
base: 'master', sort: 'created',
|
||||
direction: 'asc', page: 1, per_page: 100
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
base: 'master',
|
||||
sort: 'created',
|
||||
direction: 'asc',
|
||||
page: 1,
|
||||
per_page: 100
|
||||
};
|
||||
|
||||
let done = false; // will be true when lastPR is seen in paginated results
|
||||
let response = await method(methodProps);
|
||||
let { data } = response;
|
||||
data = prFilter(data, firstPR, lastPR, prPropsToGet);
|
||||
while (octokit.hasNextPage(response) && !done ) {
|
||||
while (octokit.hasNextPage(response) && !done) {
|
||||
response = await octokit.getNextPage(response);
|
||||
let dataFiltered = prFilter(response.data, firstPR, lastPR, prPropsToGet);
|
||||
data = data.concat(dataFiltered);
|
||||
@ -53,32 +64,31 @@ const getUserInput = async (rangeType = '') => {
|
||||
data = await getRange().then(data => data);
|
||||
firstPR = data[0];
|
||||
lastPR = data[1];
|
||||
}
|
||||
else {
|
||||
let [ n, f, type, start, end ] = process.argv;
|
||||
} else {
|
||||
let [n, f, type, start, end] = process.argv;
|
||||
data = await getRange().then(data => data);
|
||||
firstPR = data[0];
|
||||
lastPR = data[1];
|
||||
if (type !== 'all' && type !== 'range') {
|
||||
throw `Please specify either all or range for 1st arg.`;
|
||||
throw `Please specify either all or range for 1st arg.`;
|
||||
}
|
||||
if (type === 'range') {
|
||||
start = parseInt(start);
|
||||
end = parseInt(end);
|
||||
if (!start || !end) {
|
||||
throw `Please specify both a starting PR # (2nd arg) and ending PR # (3rd arg).`;
|
||||
}
|
||||
if (start > end) {
|
||||
throw `Starting PR # must be less than or equal to end PR #.`;
|
||||
}
|
||||
if (start < firstPR) {
|
||||
throw `Starting PR # can not be less than first open PR # (${firstPR})`;
|
||||
}
|
||||
firstPR = start
|
||||
if (end > lastPR) {
|
||||
throw `Ending PR # can not be greater than last open PR # (${lastPR})`;
|
||||
}
|
||||
lastPR = end;
|
||||
start = parseInt(start);
|
||||
end = parseInt(end);
|
||||
if (!start || !end) {
|
||||
throw `Please specify both a starting PR # (2nd arg) and ending PR # (3rd arg).`;
|
||||
}
|
||||
if (start > end) {
|
||||
throw `Starting PR # must be less than or equal to end PR #.`;
|
||||
}
|
||||
if (start < firstPR) {
|
||||
throw `Starting PR # can not be less than first open PR # (${firstPR})`;
|
||||
}
|
||||
firstPR = start;
|
||||
if (end > lastPR) {
|
||||
throw `Ending PR # can not be greater than last open PR # (${lastPR})`;
|
||||
}
|
||||
lastPR = end;
|
||||
}
|
||||
}
|
||||
const totalPRs = await getCount().then(data => data);
|
||||
@ -86,16 +96,26 @@ const getUserInput = async (rangeType = '') => {
|
||||
};
|
||||
|
||||
const getPRs = async (totalPRs, firstPR, lastPR, prPropsToGet) => {
|
||||
const getPRsBar = new _cliProgress.Bar({
|
||||
format: `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] {percentage}% | Elapsed Time: {duration_formatted} | ETA: {eta_formatted}`,
|
||||
etaBuffer: 50
|
||||
}, _cliProgress.Presets.shades_classic);
|
||||
const getPRsBar = new _cliProgress.Bar(
|
||||
{
|
||||
format: `Retrieve PRs (${firstPR}-${lastPR}) [{bar}] {percentage}% | Elapsed Time: {duration_formatted} | ETA: {eta_formatted}`,
|
||||
etaBuffer: 50
|
||||
},
|
||||
_cliProgress.Presets.shades_classic
|
||||
);
|
||||
getPRsBar.start(totalPRs, 0);
|
||||
let openPRs = await paginate(octokit.pullRequests.list, octokit, firstPR, lastPR, prPropsToGet, getPRsBar);
|
||||
let openPRs = await paginate(
|
||||
octokit.pullRequests.list,
|
||||
octokit,
|
||||
firstPR,
|
||||
lastPR,
|
||||
prPropsToGet,
|
||||
getPRsBar
|
||||
);
|
||||
getPRsBar.update(totalPRs);
|
||||
getPRsBar.stop();
|
||||
console.log(`# of PRs retrieved: ${openPRs.length}`);
|
||||
return { firstPR, lastPR, openPRs };
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { getPRs, getUserInput };
|
||||
|
@ -13,27 +13,60 @@ const page = 1;
|
||||
const per_page = 1;
|
||||
|
||||
const getCount = async () => {
|
||||
const { data: { total_count: count } } = await octokit.search.issues({
|
||||
q: `repo:${owner}/${repo}+is:open+type:pr+base:master`,
|
||||
sort: 'created', order: 'asc', page: 1, per_page: 1
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
const {
|
||||
data: { total_count: count }
|
||||
} = await octokit.search
|
||||
.issues({
|
||||
q: `repo:${owner}/${repo}+is:open+type:pr+base:master`,
|
||||
sort: 'created',
|
||||
order: 'asc',
|
||||
page: 1,
|
||||
per_page: 1
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
const getFirst = async () => {
|
||||
let methodProps = {owner, repo, state, base, sort, direction: 'asc', page, per_page};
|
||||
let methodProps = {
|
||||
owner,
|
||||
repo,
|
||||
state,
|
||||
base,
|
||||
sort,
|
||||
direction: 'asc',
|
||||
page,
|
||||
per_page
|
||||
};
|
||||
let response = await octokit.pullRequests.list(methodProps);
|
||||
return response.data[0].number;
|
||||
};
|
||||
|
||||
const getRange = async () => {
|
||||
let methodProps = {owner, repo, state, base, sort, direction: 'asc', page, per_page};
|
||||
let methodProps = {
|
||||
owner,
|
||||
repo,
|
||||
state,
|
||||
base,
|
||||
sort,
|
||||
direction: 'asc',
|
||||
page,
|
||||
per_page
|
||||
};
|
||||
let response = await octokit.pullRequests.list(methodProps);
|
||||
const firstPR = response.data[0].number;
|
||||
methodProps = {owner, repo, state, base, sort, direction: 'desc', page, per_page};
|
||||
methodProps = {
|
||||
owner,
|
||||
repo,
|
||||
state,
|
||||
base,
|
||||
sort,
|
||||
direction: 'desc',
|
||||
page,
|
||||
per_page
|
||||
};
|
||||
response = await octokit.pullRequests.list(methodProps);
|
||||
const lastPR = response.data[0].number;
|
||||
return [firstPR, lastPR];
|
||||
|
@ -14,23 +14,32 @@ const octokit = require('@octokit/rest')(octokitConfig);
|
||||
const { getPRs, getUserInput } = require('../get-prs');
|
||||
const { addLabels, addComment } = require('../pr-tasks');
|
||||
const { rateLimiter, savePrData, ProcessingLog } = require('../utils');
|
||||
const { frontmatterCheck } = require('../validation/guide-folder-checks/frontmatter-check');
|
||||
const { createErrorMsg } = require('../validation/guide-folder-checks/create-error-msg');
|
||||
const {
|
||||
frontmatterCheck
|
||||
} = require('../validation/guide-folder-checks/frontmatter-check');
|
||||
const {
|
||||
createErrorMsg
|
||||
} = require('../validation/guide-folder-checks/create-error-msg');
|
||||
|
||||
const allowedLangDirNames = [
|
||||
"arabic",
|
||||
"chinese",
|
||||
"english",
|
||||
"portuguese",
|
||||
"russian",
|
||||
"spanish"
|
||||
'arabic',
|
||||
'chinese',
|
||||
'english',
|
||||
'portuguese',
|
||||
'russian',
|
||||
'spanish'
|
||||
];
|
||||
|
||||
octokit.authenticate(octokitAuth);
|
||||
|
||||
const log = new ProcessingLog('all-frontmatter-checks');
|
||||
|
||||
const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment) => {
|
||||
const labeler = async (
|
||||
number,
|
||||
prFiles,
|
||||
currentLabels,
|
||||
guideFolderErrorsComment
|
||||
) => {
|
||||
const labelsToAdd = {}; // holds potential labels to add based on file path
|
||||
if (guideFolderErrorsComment) {
|
||||
labelsToAdd['status: needs update'] = 1;
|
||||
@ -38,7 +47,9 @@ const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment)
|
||||
const existingLabels = currentLabels.map(({ name }) => name);
|
||||
|
||||
/* this next section only adds needed labels which are NOT currently on the PR. */
|
||||
const newLabels = Object.keys(labelsToAdd).filter(label => !existingLabels.includes(label));
|
||||
const newLabels = Object.keys(labelsToAdd).filter(
|
||||
label => !existingLabels.includes(label)
|
||||
);
|
||||
if (newLabels.length) {
|
||||
if (process.env.PRODUCTION_RUN === 'true') {
|
||||
addLabels(number, newLabels);
|
||||
@ -50,9 +61,14 @@ const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment)
|
||||
|
||||
const checkPath = (fullPath, fileContent) => {
|
||||
let errorMsgs = [];
|
||||
const remaining = fullPath.split("/");
|
||||
const isTranslation = allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english';
|
||||
const frontMatterErrMsgs = frontmatterCheck(fullPath, isTranslation, fileContent);
|
||||
const remaining = fullPath.split('/');
|
||||
const isTranslation =
|
||||
allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english';
|
||||
const frontMatterErrMsgs = frontmatterCheck(
|
||||
fullPath,
|
||||
isTranslation,
|
||||
fileContent
|
||||
);
|
||||
return errorMsgs.concat(frontMatterErrMsgs);
|
||||
};
|
||||
|
||||
@ -71,14 +87,13 @@ const guideFolderChecks = async (number, prFiles, user) => {
|
||||
}
|
||||
|
||||
if (prErrors.length) {
|
||||
const comment = createErrorMsg(prErrors, user)
|
||||
const comment = createErrorMsg(prErrors, user);
|
||||
if (process.env.PRODUCTION_RUN === 'true') {
|
||||
const result = await addComment(number, comment);
|
||||
}
|
||||
await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500);
|
||||
return comment;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@ -95,13 +110,32 @@ const guideFolderChecks = async (number, prFiles, user) => {
|
||||
log.start();
|
||||
console.log('Starting frontmatter checks process...');
|
||||
for (let count in openPRs) {
|
||||
let { number, labels: currentLabels, user: { login: username } } = openPRs[count];
|
||||
const { data: prFiles } = await octokit.pullRequests.listFiles({ owner, repo, number });
|
||||
let {
|
||||
number,
|
||||
labels: currentLabels,
|
||||
user: { login: username }
|
||||
} = openPRs[count];
|
||||
const { data: prFiles } = await octokit.pullRequests.listFiles({
|
||||
owner,
|
||||
repo,
|
||||
number
|
||||
});
|
||||
|
||||
const guideFolderErrorsComment = await guideFolderChecks(number, prFiles, username);
|
||||
const commentLogVal = guideFolderErrorsComment ? guideFolderErrorsComment : 'none';
|
||||
const guideFolderErrorsComment = await guideFolderChecks(
|
||||
number,
|
||||
prFiles,
|
||||
username
|
||||
);
|
||||
const commentLogVal = guideFolderErrorsComment
|
||||
? guideFolderErrorsComment
|
||||
: 'none';
|
||||
|
||||
const labelsAdded = await labeler(number, prFiles, currentLabels, guideFolderErrorsComment);
|
||||
const labelsAdded = await labeler(
|
||||
number,
|
||||
prFiles,
|
||||
currentLabels,
|
||||
guideFolderErrorsComment
|
||||
);
|
||||
const labelLogVal = labelsAdded.length ? labelsAdded : 'none added';
|
||||
|
||||
log.add(number, { number, comment: commentLogVal, labels: labelLogVal });
|
||||
@ -109,11 +143,11 @@ const guideFolderChecks = async (number, prFiles, user) => {
|
||||
}
|
||||
}
|
||||
})()
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('Successfully completed frontmatter checks');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err)
|
||||
})
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('Successfully completed frontmatter checks');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err);
|
||||
});
|
||||
|
@ -35,25 +35,26 @@ const log = new ProcessingLog('all-locally-tested-labels');
|
||||
}
|
||||
|
||||
/* this next section only adds needed labels which are NOT currently on the PR. */
|
||||
const newLabels = Object.keys(labelsToAdd).filter(label => !existingLabels.includes(label));
|
||||
const newLabels = Object.keys(labelsToAdd).filter(
|
||||
label => !existingLabels.includes(label)
|
||||
);
|
||||
if (newLabels.length) {
|
||||
log.add(number, { labels: newLabels });
|
||||
if (process.env.PRODUCTION_RUN === 'true') {
|
||||
addLabels(number, newLabels, log);
|
||||
await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
log.add(number, { number, labels: 'none added' });
|
||||
}
|
||||
}
|
||||
}
|
||||
})()
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('Successfully completed labeling');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err)
|
||||
})
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('Successfully completed labeling');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err);
|
||||
});
|
||||
|
@ -2,7 +2,7 @@ require('dotenv').config({ path: '../.env' });
|
||||
const { closeOpen } = require('../pr-tasks');
|
||||
|
||||
const getUserInput = async () => {
|
||||
let [ n, f, prNum ] = process.argv;
|
||||
let [n, f, prNum] = process.argv;
|
||||
|
||||
if (!Number(prNum)) {
|
||||
throw `Please specify a PR # to close and reopen.`;
|
||||
@ -14,9 +14,9 @@ const getUserInput = async () => {
|
||||
const { prNum } = await getUserInput();
|
||||
return prNum;
|
||||
})()
|
||||
.then((prNum) => {
|
||||
closeOpen(prNum);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
});
|
||||
.then(prNum => {
|
||||
closeOpen(prNum);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ const log = new ProcessingLog('prs-closed-reopened');
|
||||
|
||||
log.start();
|
||||
const getUserInput = async () => {
|
||||
let [ n, f, filename ] = process.argv;
|
||||
let [n, f, filename] = process.argv;
|
||||
|
||||
if (!filename) {
|
||||
throw `Please specify a file with PRs which needed to be closed and reopened.`;
|
||||
@ -15,7 +15,7 @@ const getUserInput = async () => {
|
||||
let fileObj = openJSONFile(filename);
|
||||
let { prs } = fileObj;
|
||||
if (!prs.length) {
|
||||
throw `Either no PRs found in file or there or an error occurred.`
|
||||
throw `Either no PRs found in file or there or an error occurred.`;
|
||||
}
|
||||
return { prs };
|
||||
};
|
||||
@ -24,27 +24,26 @@ const getUserInput = async () => {
|
||||
const { prs } = await getUserInput();
|
||||
return prs;
|
||||
})()
|
||||
.then(async (prs) => {
|
||||
const firstPR = prs[0].number;
|
||||
const lastPR = prs[prs.length - 1].number;
|
||||
for (let { number, errorDesc } of prs) {
|
||||
if (errorDesc !== "unknown error") {
|
||||
log.add(number, { number, closedOpened: true, errorDesc });
|
||||
if (process.env.PRODUCTION_RUN === 'true') {
|
||||
await closeOpen(number);
|
||||
await rateLimiter(90000);
|
||||
.then(async prs => {
|
||||
const firstPR = prs[0].number;
|
||||
const lastPR = prs[prs.length - 1].number;
|
||||
for (let { number, errorDesc } of prs) {
|
||||
if (errorDesc !== 'unknown error') {
|
||||
log.add(number, { number, closedOpened: true, errorDesc });
|
||||
if (process.env.PRODUCTION_RUN === 'true') {
|
||||
await closeOpen(number);
|
||||
await rateLimiter(90000);
|
||||
}
|
||||
} else {
|
||||
log.add(number, { number, closedOpened: false, errorDesc });
|
||||
}
|
||||
}
|
||||
else {
|
||||
log.add(number, { number, closedOpened: false, errorDesc });
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('closing/reopening of PRs complete');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err)
|
||||
})
|
||||
})
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('closing/reopening of PRs complete');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err);
|
||||
});
|
||||
|
@ -7,18 +7,22 @@ would be added (test) based on data stored in the specific JSON log file.
|
||||
|
||||
const { saveToFile, openJSONFile } = require('../utils');
|
||||
const path = require('path');
|
||||
const dedent = require("dedent");
|
||||
const dedent = require('dedent');
|
||||
|
||||
const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper_3-6_2018-11-23T003553.json`);
|
||||
const specificLogFile = path.resolve(
|
||||
__dirname,
|
||||
`../work-logs/production_sweeper_3-6_2018-11-23T003553.json`
|
||||
);
|
||||
|
||||
(() => {
|
||||
let fileObj = openJSONFile(specificLogFile);
|
||||
let { prs } = fileObj;
|
||||
|
||||
let count = 0;
|
||||
let prsWithComments = prs.reduce((text, { number, data: { comment, labels } }) => {
|
||||
if (comment !== 'none' || labels !== 'none added') {
|
||||
text += dedent`
|
||||
let prsWithComments = prs.reduce(
|
||||
(text, { number, data: { comment, labels } }) => {
|
||||
if (comment !== 'none' || labels !== 'none added') {
|
||||
text += dedent`
|
||||
|
||||
PR #${number}
|
||||
Comment: ${comment}
|
||||
@ -28,10 +32,12 @@ const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper
|
||||
*************************\n
|
||||
|
||||
`;
|
||||
count++;
|
||||
}
|
||||
return text;
|
||||
}, '');
|
||||
count++;
|
||||
}
|
||||
return text;
|
||||
},
|
||||
''
|
||||
);
|
||||
|
||||
prsWithComments = dedent`
|
||||
# of PRs with comments or labels added: ${count}
|
||||
@ -40,6 +46,9 @@ const specificLogFile = path.resolve(__dirname, `../work-logs/production_sweeper
|
||||
${prsWithComments}
|
||||
`;
|
||||
|
||||
saveToFile(path.resolve(__dirname, `../work-logs/guideErrorComments.txt`), prsWithComments);
|
||||
saveToFile(
|
||||
path.resolve(__dirname, `../work-logs/guideErrorComments.txt`),
|
||||
prsWithComments
|
||||
);
|
||||
console.log('guideErrorComments.txt created');
|
||||
})()
|
||||
})();
|
||||
|
@ -19,7 +19,10 @@ octokit.authenticate(octokitAuth);
|
||||
|
||||
const log = new ProcessingLog('find-failures-script');
|
||||
|
||||
const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToFind.json'));
|
||||
const errorsToFind = require(path.resolve(
|
||||
__dirname,
|
||||
'../input-files/failuresToFind.json'
|
||||
));
|
||||
|
||||
(async () => {
|
||||
const { totalPRs, firstPR, lastPR } = await getUserInput();
|
||||
@ -31,11 +34,23 @@ const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToF
|
||||
log.start();
|
||||
console.log('Starting error finding process...');
|
||||
for (let count in openPRs) {
|
||||
let { number, labels, head: { sha: ref } } = openPRs[count];
|
||||
let {
|
||||
number,
|
||||
labels,
|
||||
head: { sha: ref }
|
||||
} = openPRs[count];
|
||||
const existingLabels = labels.map(({ name }) => name);
|
||||
|
||||
if (!existingLabels.includes('status: merge conflict') && !existingLabels.includes('status: needs update') && !existingLabels.includes('status: discussing')) {
|
||||
const { data: statuses } = await octokit.repos.listStatusesForRef({ owner, repo, ref });
|
||||
if (
|
||||
!existingLabels.includes('status: merge conflict') &&
|
||||
!existingLabels.includes('status: needs update') &&
|
||||
!existingLabels.includes('status: discussing')
|
||||
) {
|
||||
const { data: statuses } = await octokit.repos.listStatusesForRef({
|
||||
owner,
|
||||
repo,
|
||||
ref
|
||||
});
|
||||
if (statuses.length) {
|
||||
const { state, target_url } = statuses[0]; // first element contain most recent status
|
||||
const hasProblem = state === 'failure' || state === 'error';
|
||||
@ -44,7 +59,7 @@ const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToF
|
||||
//const logNumber = 'need to use Travis api to access the full log for the buildNum above'
|
||||
const logNumber = ++buildNum;
|
||||
const travisLogUrl = `https://api.travis-ci.org/v3/job/${logNumber}/log.txt`;
|
||||
const response = await fetch(travisLogUrl)
|
||||
const response = await fetch(travisLogUrl);
|
||||
const logText = await response.text();
|
||||
let found = false;
|
||||
let error;
|
||||
@ -64,11 +79,11 @@ const errorsToFind = require(path.resolve(__dirname, '../input-files/failuresToF
|
||||
}
|
||||
}
|
||||
})()
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('Successfully finished finding all specified errors.');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err)
|
||||
})
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('Successfully finished finding all specified errors.');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err);
|
||||
});
|
||||
|
@ -31,38 +31,61 @@ const log = new ProcessingLog('pr-relations');
|
||||
log.start();
|
||||
const { indices: oldIndices, prs: oldPRs } = await getExistingData();
|
||||
if (!oldPRs.length) {
|
||||
console.log('No existing PRs data found, so it will take a while to download PRs/filenames data.');
|
||||
console.log(
|
||||
'No existing PRs data found, so it will take a while to download PRs/filenames data.'
|
||||
);
|
||||
}
|
||||
const prPropsToGet = ['number', 'user', 'title', 'updated_at', 'files'];
|
||||
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||
|
||||
if (openPRs.length) {
|
||||
const getFilesBar = new _cliProgress.Bar({
|
||||
format: `Update PRs [{bar}] {percentage}% | {value}/{total} | Time Elapsed: {duration_formatted} | ETA: {eta_formatted}`,
|
||||
etaBuffer: 50
|
||||
}, _cliProgress.Presets.shades_classic);
|
||||
const getFilesBar = new _cliProgress.Bar(
|
||||
{
|
||||
format: `Update PRs [{bar}] {percentage}% | {value}/{total} | Time Elapsed: {duration_formatted} | ETA: {eta_formatted}`,
|
||||
etaBuffer: 50
|
||||
},
|
||||
_cliProgress.Presets.shades_classic
|
||||
);
|
||||
getFilesBar.start(openPRs.length, 0);
|
||||
|
||||
let prsUpdated = '';
|
||||
for (let count in openPRs) {
|
||||
let { number, updated_at, title, user: { login: username } } = openPRs[count];
|
||||
let {
|
||||
number,
|
||||
updated_at,
|
||||
title,
|
||||
user: { login: username }
|
||||
} = openPRs[count];
|
||||
let oldUpdated_at;
|
||||
let oldPrData = oldPRs[oldIndices[number]];
|
||||
if (oldPrData) {
|
||||
oldUpdated_at = oldPrData.updated_at;
|
||||
}
|
||||
if (!oldIndices.hasOwnProperty(number) || updated_at > oldUpdated_at) {
|
||||
const { data: prFiles } = await octokit.pullRequests.listFiles({ owner, repo, number });
|
||||
const { data: prFiles } = await octokit.pullRequests.listFiles({
|
||||
owner,
|
||||
repo,
|
||||
number
|
||||
});
|
||||
const filenames = prFiles.map(({ filename }) => filename);
|
||||
log.add(number, { number, updated_at, title, username, filenames });
|
||||
if (+count > 3000 ) {
|
||||
if (+count > 3000) {
|
||||
await rateLimiter(1400);
|
||||
}
|
||||
prsUpdated += `PR #${number} added or updated\n`;
|
||||
}
|
||||
else {
|
||||
let { username: oldUsername, title: oldTitle, filenames: oldFilenames } = oldPrData;
|
||||
log.add(number, { number, updated_at: oldUpdated_at, username: oldUsername, title: oldTitle, filenames: oldFilenames });
|
||||
} else {
|
||||
let {
|
||||
username: oldUsername,
|
||||
title: oldTitle,
|
||||
filenames: oldFilenames
|
||||
} = oldPrData;
|
||||
log.add(number, {
|
||||
number,
|
||||
updated_at: oldUpdated_at,
|
||||
username: oldUsername,
|
||||
title: oldTitle,
|
||||
filenames: oldFilenames
|
||||
});
|
||||
}
|
||||
|
||||
if (+count % 10 === 0) {
|
||||
@ -72,29 +95,31 @@ const log = new ProcessingLog('pr-relations');
|
||||
getFilesBar.update(openPRs.length);
|
||||
getFilesBar.stop();
|
||||
console.log(prsUpdated);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw 'There were no open PRs received from Github';
|
||||
}
|
||||
})()
|
||||
.then(async () => {
|
||||
log.finish();
|
||||
console.log('Finished retrieving Dashboard data');
|
||||
.then(async () => {
|
||||
log.finish();
|
||||
console.log('Finished retrieving Dashboard data');
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', fs.createReadStream(log._logfile));
|
||||
const result = await fetch(`${HOST}/upload?password=${process.env.GLITCH_UPLOAD_SECRET}`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
const formData = new FormData();
|
||||
formData.append('file', fs.createReadStream(log._logfile));
|
||||
const result = await fetch(
|
||||
`${HOST}/upload?password=${process.env.GLITCH_UPLOAD_SECRET}`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: formData
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
console.log(`Finished uploading data for ${HOST}`);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
console.log(`Finished uploading data for ${HOST}`);
|
||||
})
|
||||
.catch((err) => {
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err);
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err)
|
||||
})
|
||||
|
@ -20,26 +20,32 @@ log.start();
|
||||
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||
if (openPRs.length) {
|
||||
for (let count in openPRs) {
|
||||
let { number, head: { repo: headRepo } } = openPRs[count];
|
||||
let {
|
||||
number,
|
||||
head: { repo: headRepo }
|
||||
} = openPRs[count];
|
||||
if (headRepo === null) {
|
||||
const { data: { mergeable, mergeable_state } } = await octokit.pullRequests.get({ owner, repo, number });
|
||||
const {
|
||||
data: { mergeable, mergeable_state }
|
||||
} = await octokit.pullRequests.get({ owner, repo, number });
|
||||
if (mergeable_state === 'dirty' || mergeable_state === 'unknown') {
|
||||
log.add(number, { number, mergeable_state });
|
||||
console.log(`[${number} (mergeable_state: ${mergeable_state})](https://github.com/freeCodeCamp/freeCodeCamp/pull/${number})`);
|
||||
console.log(
|
||||
`[${number} (mergeable_state: ${mergeable_state})](https://github.com/freeCodeCamp/freeCodeCamp/pull/${number})`
|
||||
);
|
||||
}
|
||||
await rateLimiter(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
throw 'There were no open PRs received from Github';
|
||||
}
|
||||
})()
|
||||
.then(async () => {
|
||||
log.finish();
|
||||
console.log('Finished finding unknown repo PRs with merge conflicts');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err)
|
||||
})
|
||||
.then(async () => {
|
||||
log.finish();
|
||||
console.log('Finished finding unknown repo PRs with merge conflicts');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err);
|
||||
});
|
||||
|
@ -5,11 +5,12 @@ const octokit = require('@octokit/rest')(octokitConfig);
|
||||
octokit.authenticate(octokitAuth);
|
||||
|
||||
const addComment = async (number, comment) => {
|
||||
const result = await octokit.issues.createComment({ owner, repo, number, body: comment })
|
||||
.catch((err) => {
|
||||
console.log(`PR #${number} had an error when trying to add a comment\n`);
|
||||
console.log(err);
|
||||
});
|
||||
const result = await octokit.issues
|
||||
.createComment({ owner, repo, number, body: comment })
|
||||
.catch(err => {
|
||||
console.log(`PR #${number} had an error when trying to add a comment\n`);
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
if (result) {
|
||||
console.log(`PR #${number} successfully added a comment\n`);
|
||||
|
@ -6,15 +6,20 @@ const octokit = require('@octokit/rest')(octokitConfig);
|
||||
octokit.authenticate(octokitAuth);
|
||||
|
||||
const addLabels = (number, labels, log) => {
|
||||
octokit.issues.addLabels({ owner, repo, number, labels })
|
||||
.then(() => {
|
||||
console.log(`PR #${number} added ${JSON.stringify(labels)}\n`);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(`PR #${number} had an error when trying to labels: ${JSON.stringify(labels)}\n`);
|
||||
console.log(err)
|
||||
log.finish()
|
||||
})
|
||||
octokit.issues
|
||||
.addLabels({ owner, repo, number, labels })
|
||||
.then(() => {
|
||||
console.log(`PR #${number} added ${JSON.stringify(labels)}\n`);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(
|
||||
`PR #${number} had an error when trying to labels: ${JSON.stringify(
|
||||
labels
|
||||
)}\n`
|
||||
);
|
||||
console.log(err);
|
||||
log.finish();
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { addLabels };
|
||||
|
@ -8,28 +8,45 @@ const octokit = require('@octokit/rest')(octokitConfig);
|
||||
octokit.authenticate(octokitAuth);
|
||||
|
||||
/* closes and reopens an open PR with applicable comment */
|
||||
const closeOpen = async (number) => {
|
||||
const result = await octokit.pullRequests.update({ owner, repo , number, state: 'closed', base: 'master' })
|
||||
.then(async () => {
|
||||
await rateLimiter(5000);
|
||||
return octokit.pullRequests.update({ owner, repo , number, state: 'open', base: 'master' })
|
||||
})
|
||||
.then(async () => {
|
||||
await rateLimiter(1000);
|
||||
await addComment(number, 'Closed and Reopened this PR to attempt to resolve a specific Travis build failure.');
|
||||
})
|
||||
.catch(async (err) => {
|
||||
const { errors } = JSON.parse(err.message); // Octokit stores message as a stringified object
|
||||
const errorMg = errors[0].message;
|
||||
if (errorMg === "state cannot be changed. The repository that submitted this pull request has been deleted.") {
|
||||
const closeOpen = async number => {
|
||||
const result = await octokit.pullRequests
|
||||
.update({ owner, repo, number, state: 'closed', base: 'master' })
|
||||
.then(async () => {
|
||||
await rateLimiter(5000);
|
||||
return octokit.pullRequests.update({
|
||||
owner,
|
||||
repo,
|
||||
number,
|
||||
state: 'open',
|
||||
base: 'master'
|
||||
});
|
||||
})
|
||||
.then(async () => {
|
||||
await rateLimiter(1000);
|
||||
await addComment(number, `This PR was closed because user's repo was deleted.`);
|
||||
console.log(`PR #${number} was closed because user's repo was deleted.`);
|
||||
}
|
||||
else {
|
||||
console.log(err);
|
||||
}
|
||||
})
|
||||
await addComment(
|
||||
number,
|
||||
'Closed and Reopened this PR to attempt to resolve a specific Travis build failure.'
|
||||
);
|
||||
})
|
||||
.catch(async err => {
|
||||
const { errors } = JSON.parse(err.message); // Octokit stores message as a stringified object
|
||||
const errorMg = errors[0].message;
|
||||
if (
|
||||
errorMg ===
|
||||
'state cannot be changed. The repository that submitted this pull request has been deleted.'
|
||||
) {
|
||||
await rateLimiter(1000);
|
||||
await addComment(
|
||||
number,
|
||||
`This PR was closed because user's repo was deleted.`
|
||||
);
|
||||
console.log(
|
||||
`PR #${number} was closed because user's repo was deleted.`
|
||||
);
|
||||
} else {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { closeOpen };
|
||||
|
@ -3,7 +3,12 @@ const { validLabels } = require('../validation');
|
||||
const { addLabels } = require('./add-labels');
|
||||
const { rateLimiter } = require('../utils');
|
||||
|
||||
const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment) => {
|
||||
const labeler = async (
|
||||
number,
|
||||
prFiles,
|
||||
currentLabels,
|
||||
guideFolderErrorsComment
|
||||
) => {
|
||||
const labelsToAdd = {}; // holds potential labels to add based on file path
|
||||
if (guideFolderErrorsComment) {
|
||||
labelsToAdd['status: needs update'] = 1;
|
||||
@ -11,22 +16,27 @@ const labeler = async (number, prFiles, currentLabels, guideFolderErrorsComment)
|
||||
const existingLabels = currentLabels.map(({ name }) => name);
|
||||
prFiles.forEach(({ filename }) => {
|
||||
/* remove '/challenges' from filename so language variable hold the language */
|
||||
const filenameReplacement = filename.replace(/^curriculum\/challenges\//, 'curriculum\/');
|
||||
const regex = /^(docs|curriculum|guide)(?:\/)(arabic|chinese|portuguese|russian|spanish)?\/?/
|
||||
const [ _, articleType, language ] = filenameReplacement.match(regex) || []; // need an array to pass to labelsAdder
|
||||
const filenameReplacement = filename.replace(
|
||||
/^curriculum\/challenges\//,
|
||||
'curriculum/'
|
||||
);
|
||||
const regex = /^(docs|curriculum|guide)(?:\/)(arabic|chinese|portuguese|russian|spanish)?\/?/;
|
||||
const [_, articleType, language] = filenameReplacement.match(regex) || []; // need an array to pass to labelsAdder
|
||||
if (articleType && validLabels[articleType]) {
|
||||
labelsToAdd[validLabels[articleType]] = 1
|
||||
labelsToAdd[validLabels[articleType]] = 1;
|
||||
}
|
||||
if (language && validLabels[language]) {
|
||||
labelsToAdd[validLabels[language]] = 1
|
||||
labelsToAdd[validLabels[language]] = 1;
|
||||
}
|
||||
if (articleType === 'curriculum') {
|
||||
labelsToAdd['status: need to test locally'] = 1;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
/* this next section only adds needed labels which are NOT currently on the PR. */
|
||||
const newLabels = Object.keys(labelsToAdd).filter(label => !existingLabels.includes(label));
|
||||
const newLabels = Object.keys(labelsToAdd).filter(
|
||||
label => !existingLabels.includes(label)
|
||||
);
|
||||
if (newLabels.length) {
|
||||
if (process.env.PRODUCTION_RUN === 'true') {
|
||||
addLabels(number, newLabels);
|
||||
|
@ -1,12 +1,13 @@
|
||||
/*
|
||||
This script was originally created to iterate over all open PRs to label and comment
|
||||
on specific PR errors (i.e. guide related filenmame syntax and frontmatter).
|
||||
This script was originally created to iterate over all open
|
||||
PRs to label and comment on specific PR errors (i.e. guide related
|
||||
filename syntax and front-matter).
|
||||
|
||||
Since the first run which covered over 10,000+ PRs, it is curently ran every couple of days
|
||||
for just the most recent PRs.
|
||||
Since the first run which covered over 10,000+ PRs, it is currently
|
||||
ran every couple of days for just the most recent PRs.
|
||||
|
||||
To run the script for a specific range (i.e. label and comment on guide errors),
|
||||
run `node sweeper.js range startingPrNumber endingPrNumber`
|
||||
To run the script for a specific range (i.e. label and comment on
|
||||
guide errors), run `node sweeper.js range startingPrNumber endingPrNumber`
|
||||
*/
|
||||
|
||||
const { owner, repo, octokitConfig, octokitAuth } = require('./constants');
|
||||
@ -14,8 +15,8 @@ const { owner, repo, octokitConfig, octokitAuth } = require('./constants');
|
||||
const octokit = require('@octokit/rest')(octokitConfig);
|
||||
|
||||
const { getPRs, getUserInput } = require('./get-prs');
|
||||
const { guideFolderChecks } = require('./validation');
|
||||
const { savePrData, ProcessingLog, rateLimiter } = require('./utils');
|
||||
const { guideFolderChecks } = require('./validation');
|
||||
const { ProcessingLog, rateLimiter } = require('./utils');
|
||||
const { labeler } = require('./pr-tasks');
|
||||
|
||||
octokit.authenticate(octokitAuth);
|
||||
@ -24,33 +25,55 @@ const log = new ProcessingLog('sweeper');
|
||||
|
||||
log.start();
|
||||
console.log('Sweeper started...');
|
||||
(async () => {
|
||||
(async() => {
|
||||
const { totalPRs, firstPR, lastPR } = await getUserInput();
|
||||
const prPropsToGet = ['number', 'labels', 'user'];
|
||||
const { openPRs } = await getPRs(totalPRs, firstPR, lastPR, prPropsToGet);
|
||||
|
||||
if (openPRs.length) {
|
||||
console.log('Processing PRs...');
|
||||
for (let count in openPRs) {
|
||||
let { number, labels: currentLabels, user: { login: username } } = openPRs[count];
|
||||
const { data: prFiles } = await octokit.pullRequests.listFiles({ owner, repo, number });
|
||||
console.log('Processing PRs...');
|
||||
for (let count in openPRs) {
|
||||
if (openPRs.length) {
|
||||
let {
|
||||
number,
|
||||
labels: currentLabels,
|
||||
user: { login: username }
|
||||
} = openPRs[count];
|
||||
|
||||
const guideFolderErrorsComment = await guideFolderChecks(number, prFiles, username);
|
||||
const commentLogVal = guideFolderErrorsComment ? guideFolderErrorsComment : 'none';
|
||||
const { data: prFiles } = await octokit.pullRequests.listFiles({
|
||||
owner,
|
||||
repo,
|
||||
number
|
||||
});
|
||||
|
||||
const guideFolderErrorsComment = await guideFolderChecks(
|
||||
number,
|
||||
prFiles,
|
||||
username
|
||||
);
|
||||
|
||||
const commentLogVal = guideFolderErrorsComment
|
||||
? guideFolderErrorsComment
|
||||
: 'none';
|
||||
|
||||
const labelsAdded = await labeler(
|
||||
number,
|
||||
prFiles,
|
||||
currentLabels,
|
||||
guideFolderErrorsComment
|
||||
);
|
||||
|
||||
const labelsAdded = await labeler(number, prFiles, currentLabels, guideFolderErrorsComment);
|
||||
const labelLogVal = labelsAdded.length ? labelsAdded : 'none added';
|
||||
|
||||
log.add(number, { number, comment: commentLogVal, labels: labelLogVal });
|
||||
await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500);
|
||||
await rateLimiter(+process.env.RATELIMIT_INTERVAL || 1500);
|
||||
}
|
||||
}
|
||||
})()
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('Sweeper complete');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err)
|
||||
})
|
||||
.then(() => {
|
||||
log.finish();
|
||||
console.log('Sweeper complete');
|
||||
})
|
||||
.catch(err => {
|
||||
log.finish();
|
||||
console.log(err);
|
||||
});
|
||||
|
@ -4,4 +4,10 @@ const { saveToFile } = require('./save-to-file');
|
||||
const { openJSONFile } = require('./open-json-file');
|
||||
const { ProcessingLog } = require('./processing-log');
|
||||
|
||||
module.exports = { rateLimiter, savePrData, saveToFile, openJSONFile, ProcessingLog };
|
||||
module.exports = {
|
||||
rateLimiter,
|
||||
savePrData,
|
||||
saveToFile,
|
||||
openJSONFile,
|
||||
ProcessingLog
|
||||
};
|
||||
|
@ -14,7 +14,10 @@ class ProcessingLog {
|
||||
this._prs = [];
|
||||
this._indicesObj = {};
|
||||
this._prCount = null;
|
||||
this._logfile = path.resolve(__dirname, `../work-logs/data-for_${this.getRunType()}_${this._script}.json`);
|
||||
this._logfile = path.resolve(
|
||||
__dirname,
|
||||
`../work-logs/data-for_${this.getRunType()}_${this._script}.json`
|
||||
);
|
||||
}
|
||||
|
||||
getRunType() {
|
||||
@ -32,18 +35,18 @@ class ProcessingLog {
|
||||
indices: this._indicesObj,
|
||||
prs: this._prs
|
||||
};
|
||||
saveToFile(this._logfile, JSON.stringify(log))
|
||||
saveToFile(this._logfile, JSON.stringify(log));
|
||||
}
|
||||
|
||||
add(prNum, props) {
|
||||
this._prs.push(props);
|
||||
this._indicesObj[prNum] = this._prs.length -1;
|
||||
this._indicesObj[prNum] = this._prs.length - 1;
|
||||
}
|
||||
|
||||
getPrRange() {
|
||||
if (this._prs.length) {
|
||||
const first = this._prs[0].number;
|
||||
const last = this._prs[this._prs.length -1].number;
|
||||
const last = this._prs[this._prs.length - 1].number;
|
||||
return [first, last];
|
||||
}
|
||||
console.log('Current log file does not contain any PRs');
|
||||
@ -58,8 +61,8 @@ class ProcessingLog {
|
||||
finish() {
|
||||
this._finishTime = new Date();
|
||||
const minutesElapsed = (this._finishTime - this._startTime) / 1000 / 60;
|
||||
this._elapsedTime = minutesElapsed.toFixed(2) + ' mins';
|
||||
let [ first, last ] = this.getPrRange();
|
||||
this._elapsedTime = minutesElapsed.toFixed(2) + ' mins';
|
||||
let [first, last] = this.getPrRange();
|
||||
this._firstPR = first;
|
||||
this._lastPR = last;
|
||||
this.export();
|
||||
@ -68,14 +71,19 @@ class ProcessingLog {
|
||||
|
||||
changeFilename() {
|
||||
const now = formatDate(new Date(), 'YYYY-MM-DDTHHmmss');
|
||||
const finalFilename = `${this.getRunType()}_${this._script}_${this._firstPR}-${this._lastPR}_${now}.json`;
|
||||
const newFilename = path.resolve(__dirname,`../work-logs/${finalFilename}`);
|
||||
const finalFilename = `${this.getRunType()}_${this._script}_${
|
||||
this._firstPR
|
||||
}-${this._lastPR}_${now}.json`;
|
||||
const newFilename = path.resolve(
|
||||
__dirname,
|
||||
`../work-logs/${finalFilename}`
|
||||
);
|
||||
fs.renameSync(this._logfile, newFilename);
|
||||
if (!fs.existsSync(newFilename)) {
|
||||
throw `File rename unsuccessful.`;
|
||||
}
|
||||
this._logfile = newFilename;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { ProcessingLog };
|
||||
|
@ -1,4 +1,4 @@
|
||||
const rateLimiter = (delay) => {
|
||||
const rateLimiter = 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 now = formatDate(new Date(), 'YYYY-MM-DDTHHmmss');
|
||||
const filename = path.resolve(__dirname, `../work-logs/data-for-openprs_${firstPR}-${lastPR}_${now}.json`);
|
||||
const filename = path.resolve(
|
||||
__dirname,
|
||||
`../work-logs/data-for-openprs_${firstPR}-${lastPR}_${now}.json`
|
||||
);
|
||||
console.log(`# of PRs Retrieved: ${openPRs.length}`);
|
||||
console.log(`PR Range: ${firstPR} - ${lastPR}`);
|
||||
saveToFile(filename, JSON.stringify(openPRs));
|
||||
|
@ -2,8 +2,10 @@ const fs = require('fs');
|
||||
|
||||
const saveToFile = (fileName, data) => {
|
||||
fs.writeFileSync(fileName, data, err => {
|
||||
if (err) { return console.log(err) }
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { saveToFile };
|
||||
|
@ -1,17 +1,17 @@
|
||||
const { frontmatterCheck } = require('./frontmatter-check');
|
||||
|
||||
const allowedLangDirNames = [
|
||||
"arabic",
|
||||
"chinese",
|
||||
"english",
|
||||
"portuguese",
|
||||
"russian",
|
||||
"spanish"
|
||||
'arabic',
|
||||
'chinese',
|
||||
'english',
|
||||
'portuguese',
|
||||
'russian',
|
||||
'spanish'
|
||||
];
|
||||
|
||||
const checkPath = (fullPath, fileContent) => {
|
||||
let errorMsgs = [];
|
||||
const remaining = fullPath.split("/");
|
||||
const remaining = fullPath.split('/');
|
||||
|
||||
if (!allowedLangDirNames.includes(remaining[1])) {
|
||||
errorMsgs.push({
|
||||
@ -20,20 +20,22 @@ const checkPath = (fullPath, fileContent) => {
|
||||
});
|
||||
}
|
||||
|
||||
if (remaining[remaining.length - 1] !== "index.md") {
|
||||
if (remaining[remaining.length - 1] !== 'index.md') {
|
||||
errorMsgs.push({
|
||||
msg: `\`${remaining[remaining.length - 1]}\` is not a valid file name, please use \`index.md\``,
|
||||
msg: `\`${
|
||||
remaining[remaining.length - 1]
|
||||
}\` is not a valid file name, please use \`index.md\``,
|
||||
fullPath
|
||||
});
|
||||
} else if (remaining[2] === "index.md") {
|
||||
} else if (remaining[2] === 'index.md') {
|
||||
errorMsgs.push({
|
||||
msg: `This file is not in its own sub-directory`,
|
||||
fullPath
|
||||
});
|
||||
}
|
||||
|
||||
const dirName = fullPath.replace("/index.md", "");
|
||||
if (dirName.replace(/(\s|\_)/, "") !== dirName) {
|
||||
const dirName = fullPath.replace('/index.md', '');
|
||||
if (dirName.replace(/(\s|\_)/, '') !== dirName) {
|
||||
errorMsgs.push({
|
||||
msg: `Invalid character found in a directory name, please use \`-\` as separators`,
|
||||
fullPath
|
||||
@ -47,8 +49,13 @@ const checkPath = (fullPath, fileContent) => {
|
||||
});
|
||||
}
|
||||
|
||||
const isTranslation = allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english';
|
||||
const frontMatterErrMsgs = frontmatterCheck(fullPath, isTranslation, fileContent);
|
||||
const isTranslation =
|
||||
allowedLangDirNames.includes(remaining[1]) && remaining[1] !== 'english';
|
||||
const frontMatterErrMsgs = frontmatterCheck(
|
||||
fullPath,
|
||||
isTranslation,
|
||||
fileContent
|
||||
);
|
||||
|
||||
return errorMsgs.concat(frontMatterErrMsgs);
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
const dedent = require("dedent");
|
||||
const dedent = require('dedent');
|
||||
|
||||
const createErrorMsg = (errors, user) => {
|
||||
let errorMsgHeader = dedent`
|
||||
@ -13,16 +13,18 @@ const createErrorMsg = (errors, user) => {
|
||||
`;
|
||||
|
||||
let errorMsgTable = errors.reduce((msgStr, { msg, fullPath }, idx) => {
|
||||
return (msgStr += "\n" + dedent`
|
||||
return (msgStr +=
|
||||
'\n' +
|
||||
dedent`
|
||||
| ${idx + 1} | ${msg} | ${fullPath} |
|
||||
`);
|
||||
}, "");
|
||||
}, '');
|
||||
|
||||
let errorMsgFooter = dedent`
|
||||
P.S: I am just friendly bot. Review our [Guidelines for Contributing](https://github.com/FreeCodeCamp/FreeCodeCamp/blob/master/CONTRIBUTING.md) and reach out to the [Contributors Chat room](https://gitter.im/FreeCodeCamp/Contributors) for more help.
|
||||
`;
|
||||
|
||||
return errorMsgHeader + errorMsgTable + "\n\n" + errorMsgFooter;
|
||||
return errorMsgHeader + errorMsgTable + '\n\n' + errorMsgFooter;
|
||||
};
|
||||
|
||||
module.exports = { createErrorMsg };
|
||||
|
@ -17,15 +17,13 @@ const frontmatterCheck = (fullPath, isTranslation, fileContent) => {
|
||||
fullPath
|
||||
});
|
||||
}
|
||||
}
|
||||
catch(err) {
|
||||
} catch (err) {
|
||||
errors.push({
|
||||
msg: `Unexpected syntax found in the front matter block. Review Travis CI build Details link for more details.`,
|
||||
fullPath
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
|
@ -22,14 +22,13 @@ const guideFolderChecks = async (number, prFiles, user) => {
|
||||
}
|
||||
|
||||
if (prErrors.length) {
|
||||
const comment = createErrorMsg(prErrors, user)
|
||||
const comment = createErrorMsg(prErrors, user);
|
||||
if (process.env.PRODUCTION_RUN === 'true') {
|
||||
const result = await addComment(number, comment);
|
||||
}
|
||||
await rateLimiter(+process.env.RATELIMIT_INTERVAL | 1500);
|
||||
return comment;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user