Bauke/tildes-issue-log
Bauke
/
tildes-issue-log
Archived
1
Fork 0

fix: fix all linting errors

This commit is contained in:
Bauke 2019-07-05 23:51:11 +02:00
parent 9b7e616ab6
commit 23f0cff306
Signed by: Bauke
GPG Key ID: C1C0F29952BCF558
9 changed files with 362 additions and 301 deletions

View File

@ -1,21 +1,20 @@
// Require dependencies // Require dependencies
const const path = require('path');
cheerio = require('cheerio'), const cheerio = require('cheerio');
df = require('date-format'), const df = require('date-format');
{Feed} = require('feed'), const {Feed} = require('feed');
fs = require('fs-extra'), const fs = require('fs-extra');
gitlab = require('gitlab/dist/es5').default, const GitLab = require('gitlab/dist/es5').default;
gulp = require('gulp'), const gulp = require('gulp');
htmlclean = require('gulp-htmlclean'), const htmlclean = require('gulp-htmlclean');
klaw = require('klaw-sync'), const klaw = require('klaw-sync');
log = require('fancy-log'), const log = require('fancy-log');
merge2 = require('merge2'), const merge2 = require('merge2');
path = require('path'), const scss = require('gulp-sass');
scss = require('gulp-sass'), const sync = require('browser-sync');
sync = require('browser-sync')
// Require statistic functions // Require statistic functions
const { avgTime, freqUsers, labelsAlphabet, changedLines, uniqueContributors } = require('./statistics') const {avgTime, freqUsers, labelsAlphabet, changedLines, uniqueContributors} = require('./statistics');
// Define paths that are gonna be used commonly // Define paths that are gonna be used commonly
const paths = { const paths = {
@ -24,35 +23,35 @@ const paths = {
issues: { issues: {
open: path.join(__dirname, 'data/issues/open/'), open: path.join(__dirname, 'data/issues/open/'),
closed: path.join(__dirname, 'data/issues/closed/'), closed: path.join(__dirname, 'data/issues/closed/'),
out: path.join(__dirname, 'data/issues/out/'), out: path.join(__dirname, 'data/issues/out/')
}, }
}, },
extra: path.join(__dirname, 'src/favicons/**'), extra: path.join(__dirname, 'src/favicons/**'),
html: { html: {
index: path.join(__dirname, 'src/index.html'), index: path.join(__dirname, 'src/index.html'),
posts: path.join(__dirname, 'src/posts/*.html'), posts: path.join(__dirname, 'src/posts/*.html')
}, },
out: path.join(__dirname, 'public/'), out: path.join(__dirname, 'public/'),
scss: path.join(__dirname, 'src/scss/*.scss'), scss: path.join(__dirname, 'src/scss/*.scss')
} };
// Define options for Node Sass and Browser Sync // Define options for Node Sass and Browser Sync
const opts = { const opts = {
scss: { scss: {
outputStyle: 'compressed', outputStyle: 'compressed'
}, },
sync: { sync: {
server: { server: {
baseDir: paths.out, baseDir: paths.out
}, }
}, }
} };
// The data to download from specified month, months are zero-based zo January would be 0 // The data to download from specified month, months are zero-based zo January would be 0
// Make sure both of these are **numbers**, if they are strings it won't work properly! // Make sure both of these are **numbers**, if they are strings it won't work properly!
const wantedMonth = new Date().getMonth() const wantedMonth = new Date().getMonth();
// Since we've passed from 2018 into 2019 we also have to start checking for year now // Since we've passed from 2018 into 2019 we also have to start checking for year now
const wantedYear = new Date().getFullYear() const wantedYear = new Date().getFullYear();
// Init the months array, probably a way to do this with Dates but this works too // Init the months array, probably a way to do this with Dates but this works too
const months = [ const months = [
@ -67,31 +66,31 @@ const months = [
'September', 'September',
'October', 'October',
'November', 'November',
'December', 'December'
] ];
// Add the year and month to the open/closed/out path so they're easy to identify // Add the year and month to the open/closed/out path so they're easy to identify
const commitsPath = `${paths.data.commits}${wantedYear}/${months[wantedMonth]}/` const commitsPath = `${paths.data.commits}${wantedYear}/${months[wantedMonth]}/`;
const openIssuesPath = `${paths.data.issues.open}${wantedYear}/${months[wantedMonth]}/` // folder const openIssuesPath = `${paths.data.issues.open}${wantedYear}/${months[wantedMonth]}/`; // Folder
const closedIssuesPath = `${paths.data.issues.closed}${wantedYear}/${months[wantedMonth]}/` // folder const closedIssuesPath = `${paths.data.issues.closed}${wantedYear}/${months[wantedMonth]}/`; // Folder
const outIssuesPath = `${paths.data.issues.out}${months[wantedMonth]}${wantedYear}` // will become table and statistics files const outIssuesPath = `${paths.data.issues.out}${months[wantedMonth]}${wantedYear}`; // Will become table and statistics files
// Make the directories using fs-extra's "mkdir -p" equivalent // Make the directories using fs-extra's "mkdir -p" equivalent
// It will make any directory that doesn't yet exist in the path // It will make any directory that doesn't yet exist in the path
fs.mkdirpSync(commitsPath) fs.mkdirpSync(commitsPath);
fs.mkdirpSync(openIssuesPath) fs.mkdirpSync(openIssuesPath);
fs.mkdirpSync(closedIssuesPath) fs.mkdirpSync(closedIssuesPath);
fs.mkdirpSync(paths.data.issues.out) fs.mkdirpSync(paths.data.issues.out);
// Create the browser sync server, it only starts when using `gulp watch` however // Create the browser sync server, it only starts when using `gulp watch` however
const server = sync.create() const server = sync.create();
// Copy over the HTML, using merge2 to use Gulp's async completion and multiple src's // Copy over the HTML, using merge2 to use Gulp's async completion and multiple src's
function buildHTML() { function buildHTML() {
return merge2([ return merge2([
gulp.src(paths.html.index).pipe(htmlclean()).pipe(gulp.dest(paths.out)), gulp.src(paths.html.index).pipe(htmlclean()).pipe(gulp.dest(paths.out)),
gulp.src(paths.html.posts).pipe(htmlclean()).pipe(gulp.dest(paths.out + 'posts/')), gulp.src(paths.html.posts).pipe(htmlclean()).pipe(gulp.dest(paths.out + 'posts/'))
]) ]);
} }
// Build the CSS // Build the CSS
@ -99,33 +98,33 @@ function buildCSS() {
return gulp return gulp
.src(paths.scss) .src(paths.scss)
.pipe(scss(opts.scss)) .pipe(scss(opts.scss))
.pipe(gulp.dest(paths.out + 'css/')) .pipe(gulp.dest(paths.out + 'css/'));
} }
// Build the extra stuff, for now only the favicons // Build the extra stuff, for now only the favicons
function buildExtra() { function buildExtra() {
return gulp return gulp
.src(paths.extra) .src(paths.extra)
.pipe(gulp.dest(paths.out)) .pipe(gulp.dest(paths.out));
} }
// Start the Browser Sync server and watch individual file types with appropriate build functions // Start the Browser Sync server and watch individual file types with appropriate build functions
function watch() { function watch() {
server.init(opts.sync) server.init(opts.sync);
gulp.watch([ paths.html.index, paths.html.posts ], gulp.series(buildHTML, createFeeds, reload)) gulp.watch([paths.html.index, paths.html.posts], gulp.series(buildHTML, createFeeds, reload));
gulp.watch(paths.scss, gulp.series(buildCSS, reload)) gulp.watch(paths.scss, gulp.series(buildCSS, reload));
gulp.watch(paths.extra, gulp.series(buildExtra, reload)) gulp.watch(paths.extra, gulp.series(buildExtra, reload));
} }
// To use Gulp's async completion system this has to be done, it's ugly but can't do without it // To use Gulp's async completion system this has to be done, it's ugly but can't do without it
function reload(callback) { function reload(callback) {
server.reload() server.reload();
callback() callback();
} }
function download() { function download() {
// Create the API with the token // Create the API with the token
const api = new gitlab({ token: require('./config.json').token }) const api = new GitLab({token: require('./config.json').token});
// Return a new Promise so we can take advantage of Gulp's async completion system // Return a new Promise so we can take advantage of Gulp's async completion system
// We'll reject whenever there is an error and resolve when everything is completed // We'll reject whenever there is an error and resolve when everything is completed
@ -133,240 +132,253 @@ function download() {
// The Node GitLab API is a bit weird, first we have to find the project Tildes/Tildes // The Node GitLab API is a bit weird, first we have to find the project Tildes/Tildes
api.Projects api.Projects
.show('tildes/tildes') .show('tildes/tildes')
.catch((error) => reject(new Error('There was an error fetching the project:', error))) .catch(error => reject(new Error('There was an error fetching the project:', error)))
.then((project) => { .then(project => {
log('Found project, downloading issues...') log('Found project, downloading issues...');
// Then once we find the project we can use it and its ID to download the issues // Then once we find the project we can use it and its ID to download the issues
api.Issues api.Issues
.all({ projectId: project.id }) .all({projectId: project.id})
.catch((error) => reject(new Error('There was an error downloading the issues:', error))) .catch(error => reject(new Error('There was an error downloading the issues:', error)))
.then((issues) => { .then(issues => {
// And then once we've downloaded all the issues we can write them to file appropriately // And then once we've downloaded all the issues we can write them to file appropriately
log(`Downloaded issues, saving opened and closed issues from ${months[wantedMonth]} ${wantedYear} to file...`) log(`Downloaded issues, saving opened and closed issues from ${months[wantedMonth]} ${wantedYear} to file...`);
for (const issue of issues) { for (const issue of issues) {
const createdDate = new Date(issue.created_at) const createdDate = new Date(issue.created_at);
if (createdDate.getFullYear() === wantedYear && if (createdDate.getFullYear() === wantedYear &&
createdDate.getMonth() === wantedMonth) { createdDate.getMonth() === wantedMonth) {
fs.writeFileSync(openIssuesPath + `${issue.iid}.json`, JSON.stringify(issue, null, 2)) fs.writeFileSync(openIssuesPath + `${issue.iid}.json`, JSON.stringify(issue, null, 2));
} }
const closedDate = new Date(issue.closed_at) const closedDate = new Date(issue.closed_at);
if (issue.closed_at !== null && if (issue.closed_at !== null &&
closedDate.getFullYear() === wantedYear && closedDate.getFullYear() === wantedYear &&
closedDate.getMonth() === wantedMonth) { closedDate.getMonth() === wantedMonth) {
fs.writeFileSync(closedIssuesPath + `${issue.iid}.json`, JSON.stringify(issue, null, 2)) fs.writeFileSync(closedIssuesPath + `${issue.iid}.json`, JSON.stringify(issue, null, 2));
} }
} }
log('Finished writing issues to file.')
log('Downloading commits...') log('Finished writing issues to file.');
log('Downloading commits...');
}) })
.then(() => { .then(() => {
api.Commits.all(project.id, { ref_name: 'master', with_stats: true }) api.Commits.all(project.id, {ref_name: 'master', with_stats: true})
.catch((error) => reject(new Error('There was an error downloading the commits:', error))) .catch(error => reject(new Error('There was an error downloading the commits:', error)))
.then((commits) => { .then(commits => {
log(`Downloaded commits, saving commits from ${months[wantedMonth]} ${wantedYear} to file...`) log(`Downloaded commits, saving commits from ${months[wantedMonth]} ${wantedYear} to file...`);
for (const commit of commits) { for (const commit of commits) {
const authoredDate = new Date(commit.authored_date) const authoredDate = new Date(commit.authored_date);
if (authoredDate.getFullYear() === wantedYear && if (authoredDate.getFullYear() === wantedYear &&
authoredDate.getMonth() === wantedMonth) { authoredDate.getMonth() === wantedMonth) {
fs.writeFileSync(commitsPath + `${commit.short_id}.json`, JSON.stringify(commit, null, 2)) fs.writeFileSync(commitsPath + `${commit.short_id}.json`, JSON.stringify(commit, null, 2));
} }
} }
log('Finished writing commits to file.')
resolve() log('Finished writing commits to file.');
}) resolve();
}) });
}) });
}) });
});
} }
function createIssueTable() { function createIssueTable() {
// Using a Promise again for Gulp's async completion // Using a Promise again for Gulp's async completion
return new Promise((resolve) => { return new Promise(resolve => {
// Klaw returns all files in a directory recursively so we're getting all opened and closed issue files // Klaw returns all files in a directory recursively so we're getting all opened and closed issue files
let opened = klaw(openIssuesPath) const opened = klaw(openIssuesPath);
let closed = klaw(closedIssuesPath) const closed = klaw(closedIssuesPath);
// Then we want to sort all of these issue files in their arrays // Then we want to sort all of these issue files in their arrays
opened.sort(function(a, b) { opened.sort((a, b) => {
const aFile = require(a.path) const aFile = require(a.path);
const bFile = require(b.path) const bFile = require(b.path);
return (aFile.iid > bFile.iid) ? 1 : ((bFile.iid > aFile.iid) ? -1 : 0) return (aFile.iid > bFile.iid) ? 1 : ((bFile.iid > aFile.iid) ? -1 : 0);
}) });
closed.sort(function(a, b) { closed.sort((a, b) => {
const aFile = require(a.path) const aFile = require(a.path);
const bFile = require(b.path) const bFile = require(b.path);
return (aFile.iid > bFile.iid) ? 1 : ((bFile.iid > aFile.iid) ? -1 : 0) return (aFile.iid > bFile.iid) ? 1 : ((bFile.iid > aFile.iid) ? -1 : 0);
}) });
// And then generate the Issue Table HTML, which is kind of a mess to do // And then generate the Issue Table HTML, which is kind of a mess to do
let table = '<article id="issue-table">\n' let table = '<article id="issue-table">\n';
table += ' <h2>Issue Table</h2>\n' table += ' <h2>Issue Table</h2>\n';
table += ' <h3 id="opened">Opened</h3>\n' table += ' <h3 id="opened">Opened</h3>\n';
table += ' <table>\n' table += ' <table>\n';
table += ' <thead>\n' table += ' <thead>\n';
table += ' <tr>\n' table += ' <tr>\n';
table += ' <td>Issue</td>\n' table += ' <td>Issue</td>\n';
table += ' <td>Title</td>\n' table += ' <td>Title</td>\n';
table += ' <td>Author</td>\n' table += ' <td>Author</td>\n';
table += ' <td>Opened</td>\n' table += ' <td>Opened</td>\n';
table += ' <td>Closed</td>\n' table += ' <td>Closed</td>\n';
table += ' </tr>\n' table += ' </tr>\n';
table += ' </thead>\n' table += ' </thead>\n';
table += ' <tbody>\n' table += ' <tbody>\n';
for (const file of opened) { for (const file of opened) {
const issue = require(file.path) const issue = require(file.path);
table += ' <tr>\n' table += ' <tr>\n';
table += ` <td><a href="${issue.web_url}">${issue.iid}</a></td>\n` table += ` <td><a href="${issue.web_url}">${issue.iid}</a></td>\n`;
let title let title;
if (issue.title.length >= 50) { if (issue.title.length >= 50) {
// We're going to be replacing all instances of <> signs to make sure nobody can add // We're going to be replacing all instances of <> signs to make sure nobody can add
// <script></script> in their issue title and run JS on the site or mess up the layout or something // <script></script> in their issue title and run JS on the site or mess up the layout or something
// I do check myself before I commit and push anything but I'd rather be completely sure. // I do check myself before I commit and push anything but I'd rather be completely sure.
title = issue.title.substring(0, 47).replace(/[<>]/g, '') + '...' title = issue.title.substring(0, 47).replace(/[<>]/g, '') + '...';
} else { } else {
title = issue.title.replace(/[<>]/g, '') title = issue.title.replace(/[<>]/g, '');
} }
table += ` <td>${title}</td>\n` table += ` <td>${title}</td>\n`;
table += ` <td><a href="${issue.author.web_url}">${issue.author.username}</a></td>\n` table += ` <td><a href="${issue.author.web_url}">${issue.author.username}</a></td>\n`;
table += ` <td>${df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.created_at))}</td>\n` table += ` <td>${df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.created_at))}</td>\n`;
let closedAt let closedAt;
if (issue.closed_at === null) { if (issue.closed_at === null) {
closedAt = '' closedAt = '';
} else { } else {
closedAt = df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.closed_at)) closedAt = df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.closed_at));
} }
table += ` <td>${closedAt}</td>\n` table += ` <td>${closedAt}</td>\n`;
table += ' </tr>\n' table += ' </tr>\n';
} }
table += ' </tbody>\n' table += ' </tbody>\n';
table += ' </table>\n\n' table += ' </table>\n\n';
table += ' <h3 id="closed">Closed</h3>\n' table += ' <h3 id="closed">Closed</h3>\n';
table += ' <table>\n' table += ' <table>\n';
table += ' <thead>\n' table += ' <thead>\n';
table += ' <tr>\n' table += ' <tr>\n';
table += ' <td>Issue</td>\n' table += ' <td>Issue</td>\n';
table += ' <td>Title</td>\n' table += ' <td>Title</td>\n';
table += ' <td>Author</td>\n' table += ' <td>Author</td>\n';
table += ' <td>Opened</td>\n' table += ' <td>Opened</td>\n';
table += ' <td>Closed</td>\n' table += ' <td>Closed</td>\n';
table += ' </tr>\n' table += ' </tr>\n';
table += ' <thead>\n' table += ' <thead>\n';
table += ' <tbody>\n' table += ' <tbody>\n';
for (const file of closed) { for (const file of closed) {
const issue = require(file.path) const issue = require(file.path);
table += ' <tr>\n' table += ' <tr>\n';
table += ` <td><a href="${issue.web_url}">${issue.iid}</a></td>\n` table += ` <td><a href="${issue.web_url}">${issue.iid}</a></td>\n`;
let title let title;
if (issue.title.length >= 50) { if (issue.title.length >= 50) {
title = issue.title.substring(0, 47).replace(/[<>]/g, '') + '...' title = issue.title.substring(0, 47).replace(/[<>]/g, '') + '...';
} else { } else {
title = issue.title.replace(/[<>]/g, '') title = issue.title.replace(/[<>]/g, '');
} }
table += ` <td>${title}\n` table += ` <td>${title}\n`;
table += ` <td><a href="${issue.author.web_url}">${issue.author.username}</a>\n` table += ` <td><a href="${issue.author.web_url}">${issue.author.username}</a>\n`;
table += ` <td>${df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.created_at))}</td>\n` table += ` <td>${df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.created_at))}</td>\n`;
let closedAt let closedAt;
if (issue.closed_at === null) { if (issue.closed_at === null) {
closedAt = '' closedAt = '';
} else { } else {
closedAt = df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.closed_at)) closedAt = df.asString('yyyy/MM/dd hh:mm:ss', new Date(issue.closed_at));
} }
table += ` <td>${closedAt}</td>\n` table += ` <td>${closedAt}</td>\n`;
table += ' </tr>\n' table += ' </tr>\n';
} }
table += ' </tbody>\n' table += ' </tbody>\n';
table += ' </table>\n' table += ' </table>\n';
table += '</article>\n' table += '</article>\n';
// And finally when the HTML is done generating we can write it and resolve that Promise we made // And finally when the HTML is done generating we can write it and resolve that Promise we made
fs.writeFileSync(outIssuesPath + '_table.html', table, { encoding: 'UTF-8' }) fs.writeFileSync(outIssuesPath + '_table.html', table, {encoding: 'UTF-8'});
resolve() resolve();
}) });
} }
function createStatistics() { function createStatistics() {
return new Promise((resolve) => { return new Promise(resolve => {
// Same process as the Issue Table generation // Same process as the Issue Table generation
let commits = klaw(commitsPath) const commits = klaw(commitsPath);
let opened = klaw(openIssuesPath) const opened = klaw(openIssuesPath);
let closed = klaw(closedIssuesPath) const closed = klaw(closedIssuesPath);
let statistics = '<article id="statistics">\n' let statistics = '<article id="statistics">\n';
statistics += ' <h2>Statistics</h2>\n' statistics += ' <h2>Statistics</h2>\n';
const commitStats = changedLines(commits) const commitStats = changedLines(commits);
const contributors = uniqueContributors(commits) const contributors = uniqueContributors(commits);
statistics += ` <p>In the month of ${months[wantedMonth]}, ` statistics += ` <p>In the month of ${months[wantedMonth]}, `;
statistics += `${commits.length} commits were made by ${contributors.length} contributors, ` statistics += `${commits.length} commits were made by ${contributors.length} contributors, `;
statistics += `changing a total of ${Math.abs(commitStats.total)} (+${commitStats.added}|-${commitStats.deleted}) lines. ` statistics += `changing a total of ${Math.abs(commitStats.total)} (+${commitStats.added}|-${commitStats.deleted}) lines. `;
statistics += `${opened.length} issues were opened and ` statistics += `${opened.length} issues were opened and `;
statistics += `${closed.length} issues were closed.</p>\n` statistics += `${closed.length} issues were closed.</p>\n`;
statistics += ` <p>An average of ${(opened.length / 30).toFixed(2)} issues were opened ` statistics += ` <p>An average of ${(opened.length / 30).toFixed(2)} issues were opened `;
statistics += `and ${(closed.length / 30).toFixed(2)} issues were closed each day.</p>\n` statistics += `and ${(closed.length / 30).toFixed(2)} issues were closed each day.</p>\n`;
statistics += ` <p>The average time to close issues was ${avgTime(closed, 'days')} days ` statistics += ` <p>The average time to close issues was ${avgTime(closed, 'days')} days `;
statistics += `or ${avgTime(closed, 'hours')} hours.</p>\n` statistics += `or ${avgTime(closed, 'hours')} hours.</p>\n`;
const topUsers = freqUsers(opened, 3) const topUsers = freqUsers(opened, 3);
statistics += ' <p>Top 3 issue creators:</p>\n' statistics += ' <p>Top 3 issue creators:</p>\n';
statistics += ' <ol>\n' statistics += ' <ol>\n';
for (const user in topUsers) { for (const user in topUsers) {
statistics += ' <li>\n' statistics += ' <li>\n';
statistics += ` <a href="https://gitlab.com/${user}">${user}</a>` statistics += ` <a href="https://gitlab.com/${user}">${user}</a>`;
statistics += ' with ' statistics += ' with ';
statistics += `<a href="https://gitlab.com/tildes/tildes/issues?state=all&author_username=${user}">${topUsers[user]} issues created</a>.\n` statistics += `<a href="https://gitlab.com/tildes/tildes/issues?state=all&author_username=${user}">${topUsers[user]} issues created</a>.\n`;
statistics += ' </li>\n' statistics += ' </li>\n';
} }
statistics += ' </ol>\n'
let labels = labelsAlphabet(opened, true) statistics += ' </ol>\n';
statistics += ' <p>Amount of labels assigned to currently open issues:</p>\n'
statistics += ' <ul>\n' let labels = labelsAlphabet(opened, true);
statistics += ' <p>Amount of labels assigned to currently open issues:</p>\n';
statistics += ' <ul>\n';
for (const label in labels) { for (const label in labels) {
statistics += ' <li>\n' statistics += ' <li>\n';
statistics += ` <a href="https://gitlab.com/tildes/tildes/issues?state=opened&label_name%5B%5D=${label.replace(' ', '+')}")>${label}</a>:` statistics += ` <a href="https://gitlab.com/tildes/tildes/issues?state=opened&label_name%5B%5D=${label.replace(' ', '+')}")>${label}</a>:`;
statistics += `${labels[label]} ` statistics += `${labels[label]} `;
if (labels[label] === 1) statistics += 'time.\n' if (labels[label] === 1) {
else statistics += 'times.\n' statistics += 'time.\n';
statistics += ' </li>\n' } else {
} statistics += 'times.\n';
statistics += ' </ul>\n' }
labels = labelsAlphabet(closed, false) statistics += ' </li>\n';
statistics += ' <p>Amount of labels assigned to closed issues:</p>\n' }
statistics += ' <ul>\n'
statistics += ' </ul>\n';
labels = labelsAlphabet(closed, false);
statistics += ' <p>Amount of labels assigned to closed issues:</p>\n';
statistics += ' <ul>\n';
for (const label in labels) { for (const label in labels) {
statistics += ' <li>\n' statistics += ' <li>\n';
statistics += ` <a href="https://gitlab.com/tildes/tildes/issues?state=opened&label_name%5B%5D=${label.replace(' ', '+')}")>${label}</a>:` statistics += ` <a href="https://gitlab.com/tildes/tildes/issues?state=opened&label_name%5B%5D=${label.replace(' ', '+')}")>${label}</a>:`;
statistics += `${labels[label]} ` statistics += `${labels[label]} `;
if (labels[label] === 1) statistics += 'time.\n' if (labels[label] === 1) {
else statistics += 'times.\n' statistics += 'time.\n';
statistics += ' </li>\n' } else {
} statistics += 'times.\n';
statistics += ' </ul>\n' }
statistics += '</article>\n'
fs.writeFileSync(outIssuesPath + '_statistics.html', statistics, { encoding: 'UTF-8' }) statistics += ' </li>\n';
resolve() }
})
statistics += ' </ul>\n';
statistics += '</article>\n';
fs.writeFileSync(outIssuesPath + '_statistics.html', statistics, {encoding: 'UTF-8'});
resolve();
});
} }
function createFeeds() { function createFeeds() {
@ -383,47 +395,47 @@ function createFeeds() {
feedLinks: { feedLinks: {
atom: 'https://til.bauke.xyz/feed.atom', atom: 'https://til.bauke.xyz/feed.atom',
json: 'https://til.bauke.xyz/feed.json', json: 'https://til.bauke.xyz/feed.json',
rss: 'https://til.bauke.xyz/feed.rss', rss: 'https://til.bauke.xyz/feed.rss'
}, },
author: { author: {
name: 'Bauke', name: 'Bauke',
email: 'me@bauke.xyz', email: 'me@bauke.xyz',
link: 'https://bauke.xyz', link: 'https://bauke.xyz'
}, }
}) });
const posts = fs.readdirSync(path.join(paths.out, 'posts')) const posts = fs.readdirSync(path.join(paths.out, 'posts'));
// Sort the posts descending year and month // Sort the posts descending year and month
posts.sort((a, b) => { posts.sort((a, b) => {
const yearA = Number(a.replace(/\D/g, '')) const yearA = Number(a.replace(/\D/g, ''));
const yearB = Number(b.replace(/\D/g, '')) const yearB = Number(b.replace(/\D/g, ''));
if (yearA === yearB) { if (yearA === yearB) {
const monthA = months.join(',').toLowerCase().split(',').indexOf(a.substring(0, a.indexOf('-'))) + 1 const monthA = months.join(',').toLowerCase().split(',').indexOf(a.substring(0, a.indexOf('-'))) + 1;
const monthB = months.join(',').toLowerCase().split(',').indexOf(b.substring(0, b.indexOf('-'))) + 1 const monthB = months.join(',').toLowerCase().split(',').indexOf(b.substring(0, b.indexOf('-'))) + 1;
return monthB - monthA return monthB - monthA;
} }
return yearB - yearA return yearB - yearA;
}) });
for (const post of posts) { for (const post of posts) {
// Skip the template, that doesn't need to be included // Skip the template, that doesn't need to be included
if (post.includes('template')) { if (post.includes('template')) {
continue continue;
} }
const html = fs.readFileSync(path.join(paths.out, 'posts', post), 'UTF8') const html = fs.readFileSync(path.join(paths.out, 'posts', post), 'UTF8');
const $ = cheerio.load(html) const $ = cheerio.load(html);
const title = $('#wrapper>h1').text() const title = $('#wrapper>h1').text();
const id = `https://til.bauke.xyz/posts/${post}` const id = `https://til.bauke.xyz/posts/${post}`;
const date = new Date(Date.UTC( const date = new Date(Date.UTC(
Number(post.replace(/\D/g, '')), Number(post.replace(/\D/g, '')),
// Add one to the month since UTC months are 0 based and since we set the // Add one to the month since UTC months are 0 based and since we set the
// day as 0 we'll get the Date back as the last day of the previous month // day as 0 we'll get the Date back as the last day of the previous month
months.join(',').toLowerCase().split(',').indexOf(post.substring(0, post.indexOf('-'))) + 1, months.join(',').toLowerCase().split(',').indexOf(post.substring(0, post.indexOf('-'))) + 1,
0, 23, 59, 59 0, 23, 59, 59
)) ));
const content = $('#post') const content = $('#post')
.html() .html()
.replace(/<article id="toc">.+?<\/article>/g, '') // Remove the TOC .replace(/<article id="toc">.+?<\/article>/g, ''); // Remove the TOC
feed.addItem({ feed.addItem({
title, title,
id, id,
@ -432,16 +444,17 @@ function createFeeds() {
published: date, published: date,
description: `${title}'s Issue Log`, description: `${title}'s Issue Log`,
content, content,
image: 'https://til.bauke.xyz/android-chrome-192x192.png', image: 'https://til.bauke.xyz/android-chrome-192x192.png'
}) });
} }
fs.writeFileSync(path.join(paths.out, 'feed.atom'), feed.atom1())
fs.writeFileSync(path.join(paths.out, 'feed.json'), feed.json1()) fs.writeFileSync(path.join(paths.out, 'feed.atom'), feed.atom1());
fs.writeFileSync(path.join(paths.out, 'feed.rss'), feed.rss2()) fs.writeFileSync(path.join(paths.out, 'feed.json'), feed.json1());
return Promise.resolve() fs.writeFileSync(path.join(paths.out, 'feed.rss'), feed.rss2());
return Promise.resolve();
} }
exports.build = gulp.series(gulp.parallel(buildHTML, buildCSS, buildExtra), createFeeds) exports.build = gulp.series(gulp.parallel(buildHTML, buildCSS, buildExtra), createFeeds);
exports.download = gulp.series(download, gulp.parallel(createIssueTable, createStatistics)) exports.download = gulp.series(download, gulp.parallel(createIssueTable, createStatistics));
exports.no_download = gulp.parallel(createIssueTable, createStatistics) exports.no_download = gulp.parallel(createIssueTable, createStatistics);
exports.watch = gulp.series(gulp.parallel(buildHTML, buildCSS, buildExtra), createFeeds, watch) exports.watch = gulp.series(gulp.parallel(buildHTML, buildCSS, buildExtra), createFeeds, watch);

View File

@ -40,6 +40,10 @@
} }
}, },
"xo": { "xo": {
"space": true "space": true,
"rules": {
"camelcase": "off",
"guard-for-in": "off"
}
} }
} }

View File

@ -1,5 +1,3 @@
@import 'colors';
a { a {
color: $cyan; color: $cyan;
text-decoration: none; text-decoration: none;

View File

@ -3,7 +3,7 @@ $background: #282a36;
$selection: #44475a; $selection: #44475a;
$comment: #6272a4; $comment: #6272a4;
$red: #ff5555; $red: #f55;
$orange: #ffb86c; $orange: #ffb86c;
$yellow: #f1fa8c; $yellow: #f1fa8c;
$green: #50fa7b; $green: #50fa7b;

View File

@ -3,9 +3,9 @@
width: 95vw; width: 95vw;
} }
} }
@media screen and (max-width: 500px) { @media screen and (max-width: 500px) {
#post > #toc { #post > #toc {
// stylelint-disable-next-line property-blacklist
float: none; float: none;
margin: 0; margin: 0;
width: auto; width: auto;

View File

@ -1,8 +1,17 @@
@import 'anchor';
@import 'colors'; @import 'colors';
@import 'anchor';
html, body, p, ul, ol, li, html,
h1, h2, h3, h4, h5 { body,
p,
ul,
ol,
li,
h1,
h2,
h3,
h4,
h5 {
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@ -30,8 +39,8 @@ body {
> h3 { > h3 {
display: inline-block; display: inline-block;
margin: 0 10px 4px 10px; margin: 0 10px 4px;
padding: 0 4px 4px 4px; padding: 0 4px 4px;
border-bottom: 4px solid; border-bottom: 4px solid;
&:nth-child(7n + 1) { &:nth-child(7n + 1) {

View File

@ -1,5 +1,5 @@
@import 'anchor';
@import 'colors'; @import 'colors';
@import 'anchor';
#posts { #posts {
background-color: rgba(0, 0, 0, 0.25); background-color: rgba(0, 0, 0, 0.25);

View File

@ -1,5 +1,5 @@
@import 'anchor';
@import 'colors'; @import 'colors';
@import 'anchor';
#post { #post {
> #toc { > #toc {
@ -8,6 +8,7 @@
width: fit-content; width: fit-content;
border: 4px solid $yellow; border: 4px solid $yellow;
border-top: none; border-top: none;
// stylelint-disable-next-line property-blacklist
float: right; float: right;
background-color: $background; background-color: $background;
@ -25,7 +26,7 @@
font-size: 1.25em; font-size: 1.25em;
} }
&:before { &::before {
content: '>'; content: '>';
padding-right: 0.5em; padding-right: 0.5em;
} }
@ -40,7 +41,7 @@
font-size: 1.25em; font-size: 1.25em;
} }
&:before { &::before {
content: '>>'; content: '>>';
padding-right: 0.5em; padding-right: 0.5em;
padding-left: 0.5em; padding-left: 0.5em;
@ -52,10 +53,12 @@
} }
> *:not(#toc) { > *:not(#toc) {
padding: 1em 1em; padding: 1em;
background-color: rgba(0, 0, 0, 0.25); background-color: rgba(0, 0, 0, 0.25);
p, ol, ul { p,
ol,
ul {
padding: 0.2em 0; padding: 0.2em 0;
} }
@ -80,7 +83,7 @@
h3 { h3 {
font-size: 1.3em; font-size: 1.3em;
padding: 2px 0; padding: 2px 0;
margin: 1em 0 0.4em 0; margin: 1em 0 0.4em;
border-bottom: 4px solid $red; border-bottom: 4px solid $red;
width: 25%; width: 25%;
} }
@ -89,7 +92,8 @@
font-size: 1.1em; font-size: 1.1em;
} }
ol, ul { ol,
ul {
font-size: 1.1em; font-size: 1.1em;
margin-bottom: 0.2em; margin-bottom: 0.2em;
@ -105,7 +109,7 @@
ul { ul {
list-style-type: none; list-style-type: none;
> li:before { > li::before {
content: '>'; content: '>';
padding-left: 0.5em; padding-left: 0.5em;
padding-right: 0.5em; padding-right: 0.5em;

View File

@ -7,20 +7,28 @@
*/ */
function avgTime(data, time) { function avgTime(data, time) {
if (time !== 'hours' && time !== 'days') return Error('avgTime(data, time): time should be "hours" or "days"') if (time !== 'hours' && time !== 'days') {
let avg return new Error('avgTime(data, time): time should be "hours" or "days"');
for (const file of data) {
const issue = require(file.path)
const openDate = new Date(issue.created_at)
const closeDate = new Date(issue.closed_at)
let diff
if (time === 'days') diff = (closeDate - openDate) / (1000 * 60 * 60 * 24)
else if (time === 'hours') diff = (closeDate - openDate) / (1000 * 60 * 60)
avg = (typeof avg === 'undefined')
? avg = diff
: avg += diff
} }
return (avg / data.length).toFixed(2)
let avg;
for (const file of data) {
const issue = require(file.path);
const openDate = new Date(issue.created_at);
const closeDate = new Date(issue.closed_at);
let diff;
if (time === 'days') {
diff = (closeDate - openDate) / (1000 * 60 * 60 * 24);
} else if (time === 'hours') {
diff = (closeDate - openDate) / (1000 * 60 * 60);
}
avg = (typeof avg === 'undefined') ?
avg = diff :
avg += diff;
}
return (avg / data.length).toFixed(2);
} }
/** /**
@ -32,20 +40,31 @@ function avgTime(data, time) {
*/ */
function freqUsers(data, maxUsers) { function freqUsers(data, maxUsers) {
if (typeof maxUsers === 'undefined') maxUsers = 3 if (typeof maxUsers === 'undefined') {
let userCounts = {} maxUsers = 3;
}
const userCounts = {};
for (const file of data) { for (const file of data) {
const issue = require(file.path) const issue = require(file.path);
if (typeof userCounts[issue.author.username] === 'undefined') userCounts[issue.author.username] = 1 if (typeof userCounts[issue.author.username] === 'undefined') {
else userCounts[issue.author.username]++ userCounts[issue.author.username] = 1;
} else {
userCounts[issue.author.username]++;
}
} }
const sortedArray = Object.keys(userCounts).sort((a, b) => userCounts[b] - userCounts[a])
const sortedObject = {} const sortedArray = Object.keys(userCounts).sort((a, b) => userCounts[b] - userCounts[a]);
const sortedObject = {};
for (let i = 0; i < maxUsers; i++) { for (let i = 0; i < maxUsers; i++) {
if (typeof sortedArray[i] === 'undefined') break if (typeof sortedArray[i] === 'undefined') {
sortedObject[sortedArray[i]] = userCounts[sortedArray[i]] break;
}
sortedObject[sortedArray[i]] = userCounts[sortedArray[i]];
} }
return sortedObject
return sortedObject;
} }
/** /**
@ -57,61 +76,75 @@ function freqUsers(data, maxUsers) {
*/ */
function labelsAlphabet(data, checkNull) { function labelsAlphabet(data, checkNull) {
if (typeof checkNull === 'undefined') checkNull = false if (typeof checkNull === 'undefined') {
const labels = {} checkNull = false;
}
const labels = {};
for (const file of data) { for (const file of data) {
const issue = require(file.path) const issue = require(file.path);
if (checkNull && issue.closed_at !== null) continue if (checkNull && issue.closed_at !== null) {
continue;
}
for (const label of issue.labels) { for (const label of issue.labels) {
if (typeof labels[label] === 'undefined') labels[label] = 1 if (typeof labels[label] === 'undefined') {
else labels[label]++ labels[label] = 1;
} else {
labels[label]++;
}
} }
} }
const labelsOrdered = {}
Object.keys(labels).sort().forEach(label => labelsOrdered[label] = labels[label]) const labelsOrdered = {};
return labelsOrdered Object.keys(labels).sort().forEach(label => {
labelsOrdered[label] = labels[label];
});
return labelsOrdered;
} }
/** /**
* @function changedLines * @function changedLines
* @description Returns the number of added, deleted and total lines changed * @description Returns the number of added, deleted and total lines changed
* @param {Array} data Array with paths leading to GitLab Commit .json files (with stats) * @param {Array} data Array with paths leading to GitLab Commit .json files (with stats)
* @returns {Object} * @returns {Object} Object with added/deleted/total lines changed
*/ */
function changedLines(data) { function changedLines(data) {
const stats = { const stats = {
added: 0, added: 0,
deleted: 0, deleted: 0,
total: 0, total: 0
} };
for (const file of data) { for (const file of data) {
const commit = require(file.path) const commit = require(file.path);
stats.added += commit.stats.additions stats.added += commit.stats.additions;
stats.deleted += commit.stats.deletions stats.deleted += commit.stats.deletions;
stats.total += commit.stats.additions - commit.stats.deletions stats.total += commit.stats.additions - commit.stats.deletions;
} }
return stats
return stats;
} }
/** /**
* @function uniqueContributors * @function uniqueContributors
* @description Returns the names of all contributors * @description Returns the names of all contributors
* @param {Array} data Array with paths leading to GitLab Commit .json files (with stats) * @param {Array} data Array with paths leading to GitLab Commit .json files (with stats)
* @returns {Array} * @returns {Array} Array with names of all contributors
*/ */
function uniqueContributors(data) { function uniqueContributors(data) {
const contributors = [] const contributors = [];
for (const file of data) { for (const file of data) {
const commit = require(file.path) const commit = require(file.path);
if (!contributors.includes(commit.author_name)) { if (!contributors.includes(commit.author_name)) {
contributors.push(commit.author_name) contributors.push(commit.author_name);
} }
} }
return contributors
return contributors;
} }
exports.avgTime = avgTime exports.avgTime = avgTime;
exports.freqUsers = freqUsers exports.freqUsers = freqUsers;
exports.labelsAlphabet = labelsAlphabet exports.labelsAlphabet = labelsAlphabet;
exports.changedLines = changedLines exports.changedLines = changedLines;
exports.uniqueContributors = uniqueContributors exports.uniqueContributors = uniqueContributors;