Skip to content

Commit

Permalink
✨ Export report as HTML file (#48)
Browse files Browse the repository at this point in the history
Co-authored-by: BetaHuhn <schiller@mxis.ch>
  • Loading branch information
kaaax0815 and BetaHuhn committed Mar 10, 2021
1 parent 01ab2e3 commit d258cb6
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 83 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ src/config.json
report.json
report.xml
.env
test.html
report.html
24 changes: 24 additions & 0 deletions src/Runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,30 @@ class Runner {
}
}

async html(data) {
const { output } = this.args
const spinner = this.spinner

try {
if (output === undefined) throw new Error(' error: no output path specified')

spinner.text = 'Generating html...'
await Report.html(data, this.config.all, output)

return spinner.succeed(` Report saved to ${ output }`)
} catch (err) {
if (err.message) {
spinner.fail(` ${ err.message }`)
return undefined
}

spinner.fail(' error: see below for more details')
console.log(err)

return undefined
}
}

async json(data) {
const { output } = this.args
const spinner = this.spinner
Expand Down
18 changes: 18 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@ program
runner.json(data)
})

program
.command('html')
.description('Generate report and output it to a HTML file')
.option('-d, --domain <titles...>', 'specify domains by title')
.option('-i, --id <ids...>', 'specify domains by id')
.option('-o, --output <file>', 'path to output file', 'report.html')
.option('-r, --range <range>', 'specify data range', 'month')
.option('-l, --limit <number>', 'limit number of list items', 3)
.option('-e, --events [type]', 'get event data', false)
.action(async (args, program) => {
const runner = new Runner(args, program)

const data = await runner.getData()
if (!data) return

runner.html(data)
})

program
.command('rss')
.alias('xml')
Expand Down
85 changes: 4 additions & 81 deletions src/service/email.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const nodemailer = require('nodemailer')
const ejs = require('ejs')
const mjml = require('mjml')
const path = require('path')

const render = require('./helpers/renderHtml')

class Email {
constructor(host, port, username, password) {
Expand All @@ -18,81 +17,6 @@ class Email {
this.transporter = transporter
}

async render(data, endpoint, to) {
// Prepare list items
const listItems = [ 'pages', 'referrers', 'languages', 'browsers', 'devices', 'sizes', 'systems' ]
const domains = data.domains.map((domain) => {
// Filter domains
const result = listItems
.filter((item) => domain[item] === undefined || domain[item].length)
.map((item) => {
return {
title: item.charAt(0).toUpperCase() + item.slice(1),
data: domain[item]
}
})

// Split domains into chunks
const chunked = []
for (let i = 0; i < result.length; i += 2) {
chunked.push(result.slice(i, i + 2))
}

return {
id: domain.id,
title: domain.title,
viewsInRange: domain.viewsInRange,
viewsDay: domain.viewsDay,
viewsMonth: domain.viewsMonth,
viewsYear: domain.viewsYear,
viewsAvg: domain.viewsAvg,
durationAvg: domain.durationAvg,
rows: chunked
}
})

const events = []
if (data.events !== undefined && data.events.length > 0) {
const filteredEvents = data.events.filter((item) => item.data !== undefined && item.data.length > 0)

// Split events into chunks
if (filteredEvents.length > 0) {
for (let i = 0; i < filteredEvents.length; i += 2) {
events.push(filteredEvents.slice(i, i + 2))
}
}
}

// Render email with data
const html = await ejs.renderFile(
path.join(__dirname, `../templates/email.ejs`),
{
domains,
events,
viewsInRange: data.viewsInRange,
viewsYear: data.viewsYear,
viewsAvg: data.viewsAvg,
durationAvg: data.durationAvg,
names: data.names,
namesShort: data.namesShort,
generatedAt: new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''),
range: data.range,
endpoint,
to
}
)

// Generate final email
const htmlOutput = mjml(html, { validationLevel: 'skip' })

if (htmlOutput.errors.length > 0) {
console.log(htmlOutput.errors)
throw new Error(JSON.stringify(htmlOutput.errors))
}

return htmlOutput.html
}

send(from, to, subject, html) {
return new Promise((resolve, reject) => {
const mailOptions = {
Expand All @@ -118,12 +42,11 @@ const report = async function(data, config, to) {
return new Promise(async (resolve) => {

const { host, port, username, password, from } = config.email

const email = new Email(host, port, username, password)
const html = await email.render(data, config.ackee.server, to)

const subject = `Ackee report for ${ data.namesShort }`
const html = await render(data, config.ackee.server, to)

const subject = `Ackee report for ${ data.namesShort }`
email.send(from, to, subject, html).then((info) => {
resolve(info)
})
Expand Down
78 changes: 78 additions & 0 deletions src/service/helpers/renderHtml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
const ejs = require('ejs')
const mjml = require('mjml')
const path = require('path')

module.exports = async function(data, endpoint, to) {
// Prepare list items
const listItems = [ 'pages', 'referrers', 'languages', 'browsers', 'devices', 'sizes', 'systems' ]
const domains = data.domains.map((domain) => {
// Filter domains
const result = listItems
.filter((item) => domain[item] === undefined || domain[item].length)
.map((item) => {
return {
title: item.charAt(0).toUpperCase() + item.slice(1),
data: domain[item]
}
})

// Split domains into chunks
const chunked = []
for (let i = 0; i < result.length; i += 2) {
chunked.push(result.slice(i, i + 2))
}

return {
id: domain.id,
title: domain.title,
viewsInRange: domain.viewsInRange,
viewsDay: domain.viewsDay,
viewsMonth: domain.viewsMonth,
viewsYear: domain.viewsYear,
viewsAvg: domain.viewsAvg,
durationAvg: domain.durationAvg,
rows: chunked
}
})

const events = []
if (data.events !== undefined && data.events.length > 0) {
const filteredEvents = data.events.filter((item) => item.data !== undefined && item.data.length > 0)

// Split events into chunks
if (filteredEvents.length > 0) {
for (let i = 0; i < filteredEvents.length; i += 2) {
events.push(filteredEvents.slice(i, i + 2))
}
}
}

// Render HTML with data
const html = await ejs.renderFile(
path.join(__dirname, `../../templates/email.ejs`),
{
domains,
events,
viewsInRange: data.viewsInRange,
viewsYear: data.viewsYear,
viewsAvg: data.viewsAvg,
durationAvg: data.durationAvg,
names: data.names,
namesShort: data.namesShort,
generatedAt: new Date().toISOString().replace(/T/, ' ').replace(/\..+/, ''),
range: data.range,
endpoint,
to
}
)

// Generate final html
const htmlOutput = mjml(html, { validationLevel: 'skip' })

if (htmlOutput.errors.length > 0) {
console.log(htmlOutput.errors)
throw new Error(JSON.stringify(htmlOutput.errors))
}

return htmlOutput.html
}
17 changes: 17 additions & 0 deletions src/service/html.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const fs = require('fs')

const render = require('./helpers/renderHtml')

const report = async function(data, config, output) {
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
const html = await render(data, config.ackee.server, 'html')

fs.writeFile(output, html, (err) => {
if (err) reject(err)
resolve()
})
})
}

module.exports = report
4 changes: 3 additions & 1 deletion src/service/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const email = require('./email')
const json = require('./json')
const rss = require('./rss')
const html = require('./html')

module.exports.email = email
module.exports.json = json
module.exports.rss = rss
module.exports.rss = rss
module.exports.html = html

0 comments on commit d258cb6

Please sign in to comment.