solutions-rocketPDF
Improved Creation of Handlebar PDF Documents.
The aim of PDF Rocket is to enable a engineer faster and more stable PDF document creation.
The main part of this is the cc-pdf-sass
framework that creates consistent PDF styling as well as CC Sass compilation. This enables the use of variable.sass
files instead of one massif style.css
file.
The Second part, just as important, is an easy way to receive data from the Journey Backend. We do this with cc-util-data
, creating a simplistic fast way of retrieving data from the backend. See the readme at https://github.com/journeyapps-solutions/cc-util-data
Lastly is to add a way of easily using all of this, this is why this module exists. By using simple commands, the majority of the work can be completed with data on the local machine.
Documentation
View Styling docs @ https://journeyapps-solutions.github.io/cc-pdf-sass/
Installation
Per machine
yarn global add @journeyapps-solutions/rocket-pdf --save
or
npm install @journeyapps-solutions/rocket-pdf -g
Note, if you are having issues related to the package not being found after install. Double check that the yarn bin or npm bin is added to your path environmental variables
- run
yarn global bin
to get the yarn path to use. - run
npm bin
to get the npm bin path.
Deploying
yarn version
Usage
To see the latest list of commands, run
rocketPDF --help
Commands:
- rocketPDF init
- rocketPDF compile
- rocketPDF serve
- rocketPDF template
- rocketPDF image encode
- rocketPDF file down
- rocketPDF file up
- rocketPDF file up
- rocketPDF smoegel init
- rocketPDF smoegel add
Initializing
I added this section as I think that it is important to understand what happens when running this command rocketPDF init
.
During this process, the following happens:
- The index.html gets created or a prompt to override appears
- The index.js gets created or a prompt to override appears
- The latest versions of the dependencies are retrieved and set in
package.json
-
devDependencies
andbabel
config is added - The Sass files
variables.scss
andcustom.scss
are created.
This whole process allows rocketPDF to have the correct construct. It is recommended to follow the way the template is laid out.
Local Development
You are able to run rocketPDF locally, with data, as long as you abide to the layout required to do so. You just run rocketPDF serve
.
RocketPDF requires a couple of things to be able to run the locally:
- DB credentials added to the .rocketPDF file, this is automated and will be asked when running
rocketPDF serve
-
generateData
function in theindex.js
file that gives back an json object with all the data required for the PDF
/**
* Generate the data required for the template
* N.B. This function is used in local running of PDF Rocket
* @param {string} objectID ID of the object being referenced
* @return {object} [description]
*/
export async function generateData(event) {
const _ccUtilData = new ccUtilData(this.backend);
let objectID;
if(this.source == CloudCode.EDITOR_TEST && CloudCode.task.env == 'testing') {
objectID = ''; // TODO implement test case
} else {
objectID = event.object.id; //TODO implement correct ID
}
//Declare all the data objects needed to pull through to the PDF
let _data = {};
//TODO Fetch Data
return _data;
}
-
generateHTML
function that is responsible to take the data and output the raw html
/**
* Generate the document via handlebars
* N.B. This function is used in local running of PDF Rocket
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export async function generateHTML(data) {
// Compile the variable.sass and custom.sass in runtime
const _compiledSass = await ccPdfSass.compileSass(__dirname);
// Add css to JSON payload
data.css = _compiledSass;
//Get the CSS and HTML Files
const _html = fs.readFileSync(`${__dirname}/index.html`, 'utf8');
// Generate HTML using handlebars
const _pdfTemplate = handlebars.compile(_html);
const _pdfHtml = _pdfTemplate(data);
return _pdfHtml;
}
Debugging rocketPDF serve
It is possible to step through and debug the code executed in the generateHTML
and generateData
functions. The NodeJS developer tools are now exposed when running rocketPDF serve
. There are multiple methods of interacting with the NodeJS developer tools. Only a few are mentioned below.
VS Code
There are multiple methods in VS Code to access the debug server. See the guide on debugging in VS Code.
The simplest method is to open a Javascript debugging terminal. This can be achieved by selecting Debug: JavaScript Debug Terminal
from the command pallet.
Inset breakpoints or debugger
statements in the generateHTML
or generateData
functions in index.js
. Run rocketPDF serve
from the VS Code terminal and observe that the IDE will gain access once the functions are triggered.
Node Inspect
Other tools such as Chrome Dev tools or the node-inspect npm package can connect to the exposed websocket. To expose a websocket run rocketPDF serve --inspect
from any terminal. Copy the debug url into your tool of choice. The code will continue once a debug client has connected.
Quick Start
- Create a new Cloud Code task (example: test_rocketPDF)
- Open the terminal and enter
rocketPDF file down
and follow the instructions. - Enter
rocketPDF init
. This will initialize the project with the latest dependencies. - Enter
rocketPDF serve
to preview the pdf. - While the above command is still running, update the heading in the
index.html
file. Save changes. Changes will auto reflect in preview. - Open
variable.sass
and update the color variable$white: #fff;
to$white: red;
. Save changes and check preview. - Cancel current running
serve
by entering CTR + C. - Enter
rocketPDF file up
to upload your files to the new Cloud Code task - Copy the
.npmrc
credentials over to your task. - Setup the
config.js
file for the task. - Run task to make sure everything compiles.
Example of use @ https://build-preprod.journeyapps.com/apps/5989b1c1c93305497aa71453-hello-world#/cloudcode/edit/example/index.js:
const config = require('./config');
const fs = require('fs');
const assert = require('assert');
const Rollbar = require('rollbar');
const handlebars = require('handlebars');
const pdf = require('@journeyapps/pdf-reports');
const ccPdfSass = require('@journeyapps-solutions/cc-pdf-sass');
const ccUtilData = require('@journeyapps/cc-util-data');
export async function run(event) {
const rollbar = new Rollbar({
accessToken: config.rollbar,
captureUncaught: true,
captureUnhandledRejections: true,
environment: CloudCode.task.env
});
//Setup Rollbar for cloud code events (excluding editor)
if(this.source !== CloudCode.EDITOR_TEST) {
this.on('beforeTimeout', () => {
rollbar.critical(`${CloudCode.task.name} - Script timeout imminent!`, { custom: { context: this, event } });
});
this.on('highMemory', () => {
rollbar.critical(`${CloudCode.task.name} - High memory usage!`, { custom: { context: this, event, memory: process.memoryUsage() } });
});
}
console.log("Event", JSON.stringify(event));
try {
let _data = await generateData.call(this, event);
let _pdfHtml = await generateHTML.call(this, _data);
// Setup authentication for PDF service
// This token is short-lived, so it must be set again on every run.
pdf.setApiToken(this.backend.token);
// Covert the HTML to PDF
const _generatedPdf = await pdf.generatePdf({
html: _pdfHtml
});
let _attachment = await _generatedPdf.toEmailAttachment(`${CloudCode.task.name}.pdf`);
await sendMail(_attachment);
} catch(error) {
if(this.source !== CloudCode.EDITOR_TEST) {
rollbar.error("Cloud Code - " + CloudCode.task.name + " - Unexpected Error", JSON.stringify({
error: error
}));
}
throw error;
}
}
/**
* Generate the data required for the template
* N.B. This function is used in local running of PDF Rocket
* @param {string} objectID ID of the object being referenced
* @return {object} [description]
*/
export async function generateData(event) {
const _ccUtilData = new ccUtilData(this.backend);
let objectID;
if(this.source == CloudCode.EDITOR_TEST && CloudCode.task.env == 'testing') {
let _booking = await DB.booking.first();
objectID = _booking.id; // TODO implement test case
} else {
objectID = event.object.id; //TODO implement correct ID
}
//Declare all the data objects needed to pull through to the PDF
const _dataJson = await _ccUtilData.fetchData(objectID, 'booking', ['room','room.hotel']);
//TODO Fetch Data
return _dataJson;
}
/**
* Generate the document via handlebars
* N.B. This function is used in local running of PDF Rocket
* @param {[type]} data [description]
* @return {[type]} [description]
*/
export async function generateHTML(data) {
// Compile the variable.sass and custom.sass in runtime
const _compiledSass = await ccPdfSass.compileSass(__dirname);
// Add css to JSON payload
data.css = _compiledSass;
//Get the CSS and HTML Files
const _html = fs.readFileSync(`${__dirname}/index.html`, 'utf8');
// Generate HTML using handlebars
const _pdfTemplate = handlebars.compile(_html);
const _pdfHtml = _pdfTemplate(data);
return _pdfHtml;
}
async function sendMail(attachment) {
const _email = {
from: 'johndoe@journeyapps.com',
to: 'johndoe@journeyapps.com',
subject: "Test email",
text: "Please find attached the generated mail",
attachments: [attachment]
};
// Create mailer instance for sending of mail.
const _mailer = new ccSendGrid({}, config.sendGrid, CloudCode.task.env, config.whiteList);
await _mailer.send(_email);
}
Smoegel
yes, smoegel seems to be a real term. anyways. The smoegel actions enables creating of mock pdf's a lot faster and easier. The whole process is basicly automated, from creation to upload to the CC task.
Getting started
- Create an Cloud Code task in your app. Example
smoegel
- Create an new directory we are going to work in. This can be anywhere.
- Open your
terminal
and navigate to this new directory - Run
rocketPDF smoegel init
initializing the smoegel. Follow the questions as the are prompt. - View the current set of code in your Cloud Code task in the platform editor. Things should look different.
- Lets add some resources (image/pdf) by running
rocketPDF smoegel add
and following the prompts. - If you want to upload your code (That is you said no too the prompt), then run
rocketPDF file up
- Copy the
.npmrc
credentials over to your task. This allows Cloud Code to access the solutions modules. - Click run, this should generate a PDF with your image and/or attachments added.
- Setup the
config.js
file for the task. - Run task to make sure everything compiles.
- Now that the basics are setup, you can start editing the Cloud Code task, however, remember that any changes made in the Cloud Code task can be overwritten if you upload work locally, so to keep things in sync, run
rocketPDF file down
Smoegel More info - Static PDF's
In your index.js you will find the EXTRA_ATTACHMENTS
field. This is where all static documents get reference to be used in the task.
/**
* List of Extra static pdf attachments to load
*/
const EXTRA_ATTACHMENTS = [
];
Attachments added to the above field, will be read by the below code. Here you can change the logic of which PDF's are added.
let _attachments = [];
let _generatedPdfAttachment = await _generatedPdf.toEmailAttachment(`${CloudCode.task.name}.pdf`);
_attachments.push(_generatedPdfAttachment);
for (let i in EXTRA_ATTACHMENTS) {
let _extraAttachment = await fs.readFile(`${process.cwd()}/${EXTRA_ATTACHMENTS[i]}`);
_attachments.push({
filename: EXTRA_ATTACHMENTS[i],
type: 'application/pdf',
content: _extraAttachment.toString('base64')
});
}
Config
This tool stores configuration settings in two places. A global config is stored in the .rocketPDFConfig/
folder in the machine user's home directory. The second config is stored in .rocketPDF
files stored in each project directory.
Global Config
The global config hosts settings which affect global operations. This includes the file API tokens for apps.
File API tokens are applicable on the JourneyApps application ID level. An SQLite DB is used to store all the tokens.
To update an application file API token run rocketPDF config set-token --appID=<appID>
.
Local
The local .rocketPDF
config files should not be committed to source repositories since they can contain secret credentials to the DB. The following commands are available to edit the data stored on the local config files.
-
rocketPDF config init
: Resets the local configuration -
rocketPDF config switchtask
: Updates the corresponding CloudCode task -
rocketPDF config switchdb
: Updates the DB credentials