From 0086bcfa19ea2914063753f10fba75c8b5d58b4c Mon Sep 17 00:00:00 2001 From: Rupal Mahajan Date: Tue, 24 Jan 2023 17:22:34 +0000 Subject: [PATCH 1/7] Integrated new email template in cli Signed-off-by: Rupal Mahajan --- src/arguments.js | 14 +- src/constants.js | 4 +- src/download-helpers.js | 15 +- src/email-helpers.js | 57 +++-- src/index.js | 6 +- src/views/index.hbs | 446 +++++++++++++++++++++++++++++++++++++++- 6 files changed, 489 insertions(+), 53 deletions(-) diff --git a/src/arguments.js b/src/arguments.js index e153cba9..e3ed3d77 100644 --- a/src/arguments.js +++ b/src/arguments.js @@ -7,7 +7,7 @@ import { program, Option } from 'commander'; import { exit } from 'process'; import ora from 'ora'; -import { AUTH, CLI_COMMAND_NAME, DEFAULT_AUTH, DEFAULT_FILENAME, DEFAULT_FORMAT, DEFAULT_MIN_HEIGHT, DEFAULT_TENANT, DEFAULT_WIDTH, ENV_VAR, FORMAT, TRANSPORT_TYPE, DEFAULT_EMAIL_SUBJECT } from './constants.js'; +import { AUTH, CLI_COMMAND_NAME, DEFAULT_AUTH, DEFAULT_FILENAME, DEFAULT_FORMAT, DEFAULT_MIN_HEIGHT, DEFAULT_TENANT, DEFAULT_WIDTH, ENV_VAR, FORMAT, TRANSPORT_TYPE, DEFAULT_EMAIL_SUBJECT, DEFAULT_EMAIL_NOTE } from './constants.js'; import dotenv from "dotenv"; dotenv.config(); @@ -56,9 +56,12 @@ export async function getCommandArguments() { .env(ENV_VAR.SMTP_USERNAME)) .addOption(new Option('--smtppassword ', 'smtp password') .env(ENV_VAR.SMTP_PASSWORD)) - .addOption(new Option('--subject ', 'email Subject') + .addOption(new Option('--subject ', 'email subject') .default(DEFAULT_EMAIL_SUBJECT) .env(ENV_VAR.EMAIL_SUBJECT)) + .addOption(new Option('--note ', 'email note') + .default(DEFAULT_EMAIL_NOTE) + .env(ENV_VAR.EMAIL_NOTE)) program.addHelpText('after', ` Note: The tenant in the url has the higher priority than tenant value provided as command option.`); @@ -90,6 +93,7 @@ function getOptions(options) { smtppassword: null, subject: null, time: null, + note: null } // Set url. @@ -145,8 +149,7 @@ function getOptions(options) { commandOptions.filename = options.filename || process.env[ENV_VAR.FILENAME]; commandOptions.filename = options.filename === DEFAULT_FILENAME ? `${commandOptions.filename}-${commandOptions.time.toISOString()}.${commandOptions.format}` - : `${commandOptions.filename}.${commandOptions.format}` - + : `${commandOptions.filename}.${commandOptions.format}`; // Set width and height of the window commandOptions.width = Number(options.width); @@ -169,6 +172,9 @@ function getOptions(options) { // Set email subject. commandOptions.subject = options.subject || process.env[ENV_VAR.EMAIL_SUBJECT]; + // Set email note. + commandOptions.note = options.note || process.env[ENV_VAR.EMAIL_NOTE]; + spinner.succeed('Fetched argument values') return commandOptions; } \ No newline at end of file diff --git a/src/constants.js b/src/constants.js index eb2ddeba..a6910783 100644 --- a/src/constants.js +++ b/src/constants.js @@ -11,6 +11,7 @@ export const DEFAULT_WIDTH = '1680'; export const DEFAULT_MIN_HEIGHT = '600'; export const DEFAULT_FILENAME = 'opensearch-report'; export const DEFAULT_EMAIL_SUBJECT = 'This is an email containing your opensearch dashboard report'; +export const DEFAULT_EMAIL_NOTE = 'Hi,
Here is the latest report!'; export const REPORT_TYPE = { DASHBOARD: 'Dashboard', @@ -60,7 +61,8 @@ export const ENV_VAR = { SMTP_SECURE: 'OPENSEARCH_SMTP_SECURE', SMTP_USERNAME: 'OPENSEARCH_SMTP_USERNAME', SMTP_PASSWORD: 'OPENSEARCH_SMTP_PASSWORD', - EMAIL_SUBJECT: 'OPENSEARCH_EMAIL_SUBJECT' + EMAIL_SUBJECT: 'OPENSEARCH_EMAIL_SUBJECT', + EMAIL_NOTE: 'OPENSEARCH_EMAIL_NOTE', } export const TRANSPORT_TYPE = { diff --git a/src/download-helpers.js b/src/download-helpers.js index d3fc4501..91719584 100644 --- a/src/download-helpers.js +++ b/src/download-helpers.js @@ -11,7 +11,7 @@ import ora from 'ora'; const spinner = ora(); -export async function downloadReport(url, format, width, height, filename, authType, username, password, tenant, time) { +export async function downloadReport(url, format, width, height, filename, authType, username, password, tenant, time, transport) { spinner.start('Connecting to url ' + url); try { const browser = await puppeteer.launch({ @@ -132,12 +132,19 @@ export async function downloadReport(url, format, width, height, filename, authT } } - await browser.close(); - const timeCreated = time.valueOf(); const data = { timeCreated, dataUrl: buffer.toString('base64'), }; - await readStreamToFile(data.dataUrl, filename, format); + + if (transport !== undefined) { + let emailTemplateImageBuffer = await page.screenshot({ + fullPage: true, + }); + const data = { timeCreated, dataUrl: emailTemplateImageBuffer.toString('base64'), }; + await readStreamToFile(data.dataUrl, 'email_body.png', FORMAT.PNG); + } + + await browser.close(); spinner.succeed('The report is downloaded'); } catch (e) { spinner.fail('Downloading report failed. ' + e); diff --git a/src/email-helpers.js b/src/email-helpers.js index 7fedf2a3..0fe776a2 100644 --- a/src/email-helpers.js +++ b/src/email-helpers.js @@ -6,7 +6,7 @@ import nodemailer from "nodemailer"; import hbs from "nodemailer-express-handlebars"; import ora from 'ora'; -import { FORMAT } from './constants.js'; +import fs from 'fs'; import AWS from "aws-sdk"; import path from 'path'; import { fileURLToPath } from 'url'; @@ -22,7 +22,7 @@ try { // Do not set AWS_SDK_LOAD_CONFIG if aws config file is missing. } -export async function sendEmail(filename, format, sender, recipient, transport, smtphost, smtpport, smtpsecure, smtpusername, smtppassword, subject) { +export async function sendEmail(filename, url, sender, recipient, transport, smtphost, smtpport, smtpsecure, smtpusername, smtppassword, subject, note) { if (transport !== undefined && (transport === 'smtp' || ses !== undefined) && sender !== undefined && recipient !== undefined) { spinner.start('Sending email...'); } else { @@ -39,7 +39,7 @@ export async function sendEmail(filename, format, sender, recipient, transport, return; } - let mailOptions = getmailOptions(format, sender, recipient, filename, subject); + let mailOptions = getmailOptions(url, sender, recipient, filename, subject, note); let transporter = getTransporter(transport, smtphost, smtpport, smtpsecure, smtpusername, smtppassword); @@ -59,6 +59,7 @@ export async function sendEmail(filename, format, sender, recipient, transport, } else { spinner.succeed('Email sent successfully'); } + fs.unlinkSync('email_body.png'); }); } @@ -81,33 +82,27 @@ const getTransporter = (transport, smtphost, smtpport, smtpsecure, smtpusername, return transporter; } -const getmailOptions = (format, sender, recipient, file, emailSubject, mailOptions = {}) => { - if (format === FORMAT.PNG) { - mailOptions = { - from: sender, - subject: emailSubject, - to: recipient, - attachments: [ - { - filename: file, - path: file, - cid: 'report' - }], - template: 'index' - }; - } else { - mailOptions = { - from: sender, - subject: emailSubject, - to: recipient, - attachments: [ - { - filename: file, - path: file, - contentType: 'application/pdf' - }], - template: 'index' - }; - } +const getmailOptions = (url, sender, recipient, file, emailSubject, note, mailOptions = {}) => { + mailOptions = { + from: sender, + subject: emailSubject, + to: recipient, + attachments: [ + { + filename: 'email_body.png', + path: 'email_body.png', + cid: 'email_body' + }, + { + filename: file, + path: file + }], + template: 'index', + context: { + REPORT_TITLE: file, + DASHBOARD_URL: url, + NOTE: note + } + }; return mailOptions; } \ No newline at end of file diff --git a/src/index.js b/src/index.js index 3b5c38d8..bca4abbe 100755 --- a/src/index.js +++ b/src/index.js @@ -22,12 +22,13 @@ await downloadReport( options.username, options.password, options.tenant, - options.time + options.time, + options.transport ); await sendEmail( options.filename, - options.format, + options.url, options.sender, options.recipient, options.transport, @@ -37,4 +38,5 @@ await sendEmail( options.smtpusername, options.smtppassword, options.subject, + options.note ); diff --git a/src/views/index.hbs b/src/views/index.hbs index 09227bec..fe48419e 100644 --- a/src/views/index.hbs +++ b/src/views/index.hbs @@ -1,11 +1,435 @@ - - - - - - - - - - - \ No newline at end of file + + + + + + OpenSearch Dashboards Report: {{{REPORT_TITLE}}} + + + + + A new OpenSearch report is available + + + + + + + + + + + + \ No newline at end of file From 14a0dbbd8375bfbcbba3364593045f75ecb18345 Mon Sep 17 00:00:00 2001 From: Rupal Mahajan Date: Tue, 24 Jan 2023 17:37:19 +0000 Subject: [PATCH 2/7] Update help test Signed-off-by: Rupal Mahajan --- test/help.test.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/help.test.js b/test/help.test.js index 710cd13d..cba6f07b 100644 --- a/test/help.test.js +++ b/test/help.test.js @@ -30,7 +30,8 @@ Options: --smtpsecure use TLS when connecting to server (env: OPENSEARCH_SMTP_SECURE) --smtpusername smtp username (env: OPENSEARCH_SMTP_USERNAME) --smtppassword smtp password (env: OPENSEARCH_SMTP_PASSWORD) - --subject email Subject (default: "This is an email containing your opensearch dashboard report", env: OPENSEARCH_EMAIL_SUBJECT) + --subject email subject (default: "This is an email containing your opensearch dashboard report", env: OPENSEARCH_EMAIL_SUBJECT) + --note email note (default: "Hi,
Here is the latest report!", env: OPENSEARCH_EMAIL_NOTE) -h, --help display help for command Note: The tenant in the url has the higher priority than tenant value provided as command option. From cfc7aff53f66e6fdfbe3b9dd2df1b9caadfe7daa Mon Sep 17 00:00:00 2001 From: Rupal Mahajan Date: Tue, 24 Jan 2023 18:10:10 +0000 Subject: [PATCH 3/7] Delete temp img in for error case Signed-off-by: Rupal Mahajan --- src/email-helpers.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/email-helpers.js b/src/email-helpers.js index 0fe776a2..d9c8f9d9 100644 --- a/src/email-helpers.js +++ b/src/email-helpers.js @@ -27,6 +27,7 @@ export async function sendEmail(filename, url, sender, recipient, transport, smt spinner.start('Sending email...'); } else { if (transport === undefined && sender === undefined && recipient === undefined) { + deleteTemporaryImage(); return; } else if (transport === undefined) { spinner.warn('Transport value is missing'); @@ -36,6 +37,7 @@ export async function sendEmail(filename, url, sender, recipient, transport, smt spinner.warn('Sender/Recipient value is missing'); } spinner.fail('Skipped sending email'); + deleteTemporaryImage(); return; } @@ -59,7 +61,7 @@ export async function sendEmail(filename, url, sender, recipient, transport, smt } else { spinner.succeed('Email sent successfully'); } - fs.unlinkSync('email_body.png'); + deleteTemporaryImage(); }); } @@ -105,4 +107,11 @@ const getmailOptions = (url, sender, recipient, file, emailSubject, note, mailOp } }; return mailOptions; +} + +function deleteTemporaryImage() { + // Delete temporary image created for email body + if (fs.existsSync('email_body.png')) { + fs.unlinkSync('email_body.png'); + } } \ No newline at end of file From 991ae6f9415594fcb84d58de9574aa28517012af Mon Sep 17 00:00:00 2001 From: Rupal Mahajan Date: Tue, 24 Jan 2023 21:47:29 +0000 Subject: [PATCH 4/7] use const for emailTemplateImageBuffer Signed-off-by: Rupal Mahajan --- src/download-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/download-helpers.js b/src/download-helpers.js index 91719584..ed796d3b 100644 --- a/src/download-helpers.js +++ b/src/download-helpers.js @@ -137,7 +137,7 @@ export async function downloadReport(url, format, width, height, filename, authT await readStreamToFile(data.dataUrl, filename, format); if (transport !== undefined) { - let emailTemplateImageBuffer = await page.screenshot({ + const emailTemplateImageBuffer = await page.screenshot({ fullPage: true, }); const data = { timeCreated, dataUrl: emailTemplateImageBuffer.toString('base64'), }; From 6594bb5a0940a8eccd8464ec5cff9ba7bf88327b Mon Sep 17 00:00:00 2001 From: Rupal Mahajan Date: Wed, 25 Jan 2023 22:26:37 +0000 Subject: [PATCH 5/7] embed opensearch logo Signed-off-by: Rupal Mahajan --- src/email-helpers.js | 5 +++++ src/views/index.hbs | 2 +- src/views/opensearch_logo_darkmode.png | Bin 0 -> 34461 bytes 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/views/opensearch_logo_darkmode.png diff --git a/src/email-helpers.js b/src/email-helpers.js index d9c8f9d9..9e9a881d 100644 --- a/src/email-helpers.js +++ b/src/email-helpers.js @@ -95,6 +95,11 @@ const getmailOptions = (url, sender, recipient, file, emailSubject, note, mailOp path: 'email_body.png', cid: 'email_body' }, + { + filename: 'opensearch_logo_darkmode.png', + path: path.join(__dirname, './views/opensearch_logo_darkmode.png'), + cid: 'opensearch_logo_darkmode' + }, { filename: file, path: file diff --git a/src/views/index.hbs b/src/views/index.hbs index fe48419e..9909d079 100644 --- a/src/views/index.hbs +++ b/src/views/index.hbs @@ -379,7 +379,7 @@ diff --git a/src/views/opensearch_logo_darkmode.png b/src/views/opensearch_logo_darkmode.png new file mode 100644 index 0000000000000000000000000000000000000000..ffd60e07fd2aeb8d91339645257bf06cddbd50c2 GIT binary patch literal 34461 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 U)=2IL|CUifRz;>t$|U6f1IaYq4*&oF literal 0 HcmV?d00001 From 0695340c6620c76e0915749b90a804fd46835309 Mon Sep 17 00:00:00 2001 From: Rupal Mahajan Date: Thu, 26 Jan 2023 06:23:15 +0000 Subject: [PATCH 6/7] Accept text file for email body and add logic to convert text to html Signed-off-by: Rupal Mahajan --- src/arguments.js | 19 ++++++++++++++++++- src/constants.js | 2 +- src/email-helpers.js | 2 +- test/help.test.js | 2 +- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/arguments.js b/src/arguments.js index e3ed3d77..fed030fc 100644 --- a/src/arguments.js +++ b/src/arguments.js @@ -7,6 +7,7 @@ import { program, Option } from 'commander'; import { exit } from 'process'; import ora from 'ora'; +import fs from 'fs'; import { AUTH, CLI_COMMAND_NAME, DEFAULT_AUTH, DEFAULT_FILENAME, DEFAULT_FORMAT, DEFAULT_MIN_HEIGHT, DEFAULT_TENANT, DEFAULT_WIDTH, ENV_VAR, FORMAT, TRANSPORT_TYPE, DEFAULT_EMAIL_SUBJECT, DEFAULT_EMAIL_NOTE } from './constants.js'; import dotenv from "dotenv"; dotenv.config(); @@ -59,7 +60,7 @@ export async function getCommandArguments() { .addOption(new Option('--subject ', 'email subject') .default(DEFAULT_EMAIL_SUBJECT) .env(ENV_VAR.EMAIL_SUBJECT)) - .addOption(new Option('--note ', 'email note') + .addOption(new Option('--note ', 'email body (string or path to text file)') .default(DEFAULT_EMAIL_NOTE) .env(ENV_VAR.EMAIL_NOTE)) @@ -174,7 +175,23 @@ function getOptions(options) { // Set email note. commandOptions.note = options.note || process.env[ENV_VAR.EMAIL_NOTE]; + if (commandOptions.note !== DEFAULT_EMAIL_NOTE && fs.existsSync(commandOptions.note)) { + commandOptions.note = fs.readFileSync(commandOptions.note, "utf8"); + } + commandOptions.note = getHtml(commandOptions.note); spinner.succeed('Fetched argument values') return commandOptions; +} + +// Convert text to html +function getHtml(text) { + text = (text || ""); + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/\t/g, " ") + .replace(/ /g, "​ ​") + .replace(/\r\n|\r|\n/g, "
"); } \ No newline at end of file diff --git a/src/constants.js b/src/constants.js index a6910783..47046544 100644 --- a/src/constants.js +++ b/src/constants.js @@ -11,7 +11,7 @@ export const DEFAULT_WIDTH = '1680'; export const DEFAULT_MIN_HEIGHT = '600'; export const DEFAULT_FILENAME = 'opensearch-report'; export const DEFAULT_EMAIL_SUBJECT = 'This is an email containing your opensearch dashboard report'; -export const DEFAULT_EMAIL_NOTE = 'Hi,
Here is the latest report!'; +export const DEFAULT_EMAIL_NOTE = 'Hi,\nHere is the latest report!'; export const REPORT_TYPE = { DASHBOARD: 'Dashboard', diff --git a/src/email-helpers.js b/src/email-helpers.js index 9e9a881d..f2e0b85f 100644 --- a/src/email-helpers.js +++ b/src/email-helpers.js @@ -114,8 +114,8 @@ const getmailOptions = (url, sender, recipient, file, emailSubject, note, mailOp return mailOptions; } +// Delete temporary image created for email body function deleteTemporaryImage() { - // Delete temporary image created for email body if (fs.existsSync('email_body.png')) { fs.unlinkSync('email_body.png'); } diff --git a/test/help.test.js b/test/help.test.js index cba6f07b..54c920b4 100644 --- a/test/help.test.js +++ b/test/help.test.js @@ -31,7 +31,7 @@ Options: --smtpusername smtp username (env: OPENSEARCH_SMTP_USERNAME) --smtppassword smtp password (env: OPENSEARCH_SMTP_PASSWORD) --subject email subject (default: "This is an email containing your opensearch dashboard report", env: OPENSEARCH_EMAIL_SUBJECT) - --note email note (default: "Hi,
Here is the latest report!", env: OPENSEARCH_EMAIL_NOTE) + --note email body (string or path to text file) (default: "Hi,\\nHere is the latest report!", env: OPENSEARCH_EMAIL_NOTE) -h, --help display help for command Note: The tenant in the url has the higher priority than tenant value provided as command option. From 546cffda0d07251fcad10baa67144dc0b3f8babe Mon Sep 17 00:00:00 2001 From: Rupal Mahajan Date: Thu, 26 Jan 2023 06:43:32 +0000 Subject: [PATCH 7/7] Increase wait time after dom modification Signed-off-by: Rupal Mahajan --- src/download-helpers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/download-helpers.js b/src/download-helpers.js index ed796d3b..aa596a99 100644 --- a/src/download-helpers.js +++ b/src/download-helpers.js @@ -93,7 +93,7 @@ export async function downloadReport(url, format, width, height, filename, authT } // force wait for any resize to load after the above DOM modification. - await new Promise(resolve => setTimeout(resolve, 1000)); + await new Promise(resolve => setTimeout(resolve, 2000)); await waitForDynamicContent(page); let buffer; spinner.text = `Downloading Report...`;
- +

Report