Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 88 additions & 20 deletions generatePDFHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@

async function generatePDFFromJSON(req, res) {
try {
let attachment = req.body.attachment ?? req.body[0];

// console.log("PDF Request:",req);
// console.log("PDF Request body:",req.body);
let attachment = req.body.attachment ?? req.body[0];

// Validate attachment is present in incoming message
if (!attachment) {
Expand All @@ -23,11 +20,10 @@

const savedJsonString = Buffer.from(attachment, 'base64').toString('utf-8');
let savedJson;
// console.log("Saved JSON String:",savedJsonString);

try {

savedJson = JSON.parse(savedJsonString);
// console.log("Saved Parsed JSON:",savedJson);
savedJson = JSON.parse(savedJsonString);
const { valid, errors } = validateJson(savedJson);

if (valid) {
Expand Down Expand Up @@ -61,8 +57,7 @@

// Generate PDF buffer
const pdfBuffer = await generatePDF(savedJsonString); //get the pdf from the savedJson
const pdfBase64 = Buffer.from(pdfBuffer).toString('base64');
// console.log("PDF Base64:",pdfBase64);
const pdfBase64 = Buffer.from(pdfBuffer).toString('base64');

res.status(200).json({
errorCode: 0,
Expand All @@ -83,10 +78,9 @@
async function generatePDF(savedJson) {

const id = await storeData(savedJson);
const endPointForPDF = process.env.GENERATE_KILN_URL + "?jsonId=" + id;
// console.log("PDF Endpoint:",endPointForPDF);
const pdfBufferFromURL = await getPDFFromURL(endPointForPDF);
deleteData(id);
const endPointForPDF = process.env.GENERATE_KILN_URL + "?jsonId=" + id;
const pdfBufferFromURL = await getPDFFromURL(endPointForPDF);
await deleteData(id);
return pdfBufferFromURL;

}
Expand Down Expand Up @@ -146,8 +140,7 @@

async function generatePDFFromURL(req, res) {
const { path } = req.body;
try {

try {
const pdfBuffer = await getPDFFromURL(path);

// Send PDF back to client
Expand All @@ -171,9 +164,7 @@
const CLICK_WINDOW_MS = Number(process.env.PDF_CLICK_WINDOW_MS ?? 3000);
const CLICK_RETRY_EVERY_MS = Number(process.env.PDF_CLICK_INTERVAL_MS ?? 200);
const POST_CLICK_MS = Number(process.env.PDF_POST_CLICK_MS ?? 2500);

// console.log("Puppeteer path:", process.env.PUPPETEER_EXECUTABLE_PATH);
// console.log("Puppeteer URL:", url);

const browser = await puppeteer.launch({
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || "/usr/bin/chromium",
args: [
Expand All @@ -193,7 +184,7 @@
await page.setViewport({ width: 1280, height: 800 });

// Set the HTML content of the page
await page.goto(url, { waitUntil: 'networkidle2', timeout: 150000 });
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 150000 });
Comment thread Dismissed

// Let app hydrate
await sleep(HYDRATE_MS);
Expand Down Expand Up @@ -242,7 +233,83 @@

// Give it time to assemble the printable view
await sleep(POST_CLICK_MS);

console.log("Waiting for print button...");

let clickedPrint = false;
const PRINT_DEADLINE = Date.now() + 20000; // up to 20 sec
while (!clickedPrint && Date.now() < PRINT_DEADLINE) {
// Try main document
try {
await page.waitForSelector("#print", { visible: true, timeout: 1000 });
await page.click("#print");
clickedPrint = true;
console.log("Clicked print button on main page");
break;
} catch {}

// Try every frame
for (const frame of page.frames()) {
try {
await frame.waitForSelector("#print", { visible: true, timeout: 1000 });
await frame.click("#print");
clickedPrint = true;
console.log("Clicked print button inside iframe");
break;
} catch {}
}

await sleep(250); // avoid hot-looping
}
//await sleep(POST_CLICK_MS);
if (clickedPrint) {

console.log("Waiting for printable layout to finish…");

// Wait for data-form-id attribute
try {
await page.waitForFunction(
() => document.documentElement.getAttribute("data-form-id"),
{ timeout: 20000 }
);
console.log("Printable layout ready (data-form-id detected)");
} catch {
console.log("Timeout waiting for data-form-id");
}

// Svelte stabilization
console.log("Waiting for Svelte DOM stability…");
await page.waitForFunction(() => {
const now = performance.now();
if (!window.__lastMutationTime) window.__lastMutationTime = now;

if (!window.__mutationObserverInstalled) {
const observer = new MutationObserver(() => {
window.__lastMutationTime = performance.now();
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
characterData: true
});
window.__mutationObserverInstalled = true;
}

return performance.now() - window.__lastMutationTime > 600;
}, { timeout: 20000 });
console.log("Svelte DOM stabilized");
} else {
console.log("Could not click on print button");
}

await page.evaluate((t) => {
document.title = t;
const titleTag = document.querySelector("title") ||
document.head.appendChild(document.createElement("title"));
titleTag.textContent = t;
}, "Untitled");

// Generate PDF with print CSS applied
await page.emulateMediaType('print');
const pdfBuffer = await page.pdf({
Expand All @@ -255,6 +322,7 @@


await browser.close();
console.log("Returning PDF");
return pdfBuffer;

} catch (error) {
Expand Down Expand Up @@ -296,4 +364,4 @@



module.exports = { generatePDFFromHTML, generatePDFFromURL, generatePDFFromJSON, loadSavedJson };
module.exports = { generatePDFFromHTML, generatePDFFromURL, generatePDFFromJSON, loadSavedJson ,generatePDF };
6 changes: 4 additions & 2 deletions helm/templates/hpa.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{{- if contains "prod" .Release.Namespace }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
Expand All @@ -15,10 +16,11 @@ spec:
name: cpu
target:
type: Utilization
averageUtilization: 75
averageUtilization: 80
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 75
averageUtilization: 80
{{- end }}
4 changes: 2 additions & 2 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ configMapName: commlayer

resources:
requests:
memory: "128Mi"
cpu: "50m"
memory: "256Mi"
cpu: "20m"
limits:
memory: "512Mi"
cpu: "250m"
Expand Down
3 changes: 2 additions & 1 deletion routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const axios = require("axios");
const xmlparser = require("express-xml-bodyparser");
const { keycloakForSiebel, keycloakForFormRepo } = require("./keycloak.js");
const { generateTemplate,generateNewTemplate } = require("./generateHandler");
const { saveICMdata, loadICMdata, clearICMLockedFlag } = require("./saveICMdataHandler");
const { saveICMdata, loadICMdata, clearICMLockedFlag, loadICMdataAsPDF } = require("./saveICMdataHandler");
const { getUsername } = require("./usernameHandler.js");
const renderRouter = require("./renderHandler");
const appCfg = require('./appConfig.js');
Expand Down Expand Up @@ -205,5 +205,6 @@ router.post("/loadPortalForm", loadPortalIntegratedForm);
router.use("/interface", interface);
router.post('/submitForPortalAction', submitForPortalAction);
router.post('/cancelForPortalAction', cancelForPortalAction);
router.post("/loadICMDataAsPDF", loadICMdataAsPDF);

module.exports = router;
101 changes: 98 additions & 3 deletions saveICMdataHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
const { toICMFormat } = require("./dateConverter.js");
const { formExceptions } = require("./dictionary/jsonXmlConversion.js");
const { propertyExists, propertyNotEmpty, keyExists } = require("./dictionary/dictionaryUtils.js");
const {generatePDF }= require("./generatePDFHandler.js");

const SIEBEL_ICM_API_FORMS_ENDPOINT = process.env.SIEBEL_ICM_API_FORMS_ENDPOINT;

Expand Down Expand Up @@ -152,8 +153,7 @@
saveJson["DocFileExt"] = "json";
saveJson["Doc Attachment Id"] = Buffer.from(savedFormParam).toString('base64');//savedForm is saved as attachment
let saveData = JSON.parse(savedFormParam)["data"];// This is the data part of the savedJson



/**
* Apply Kiln Version
* Kiln V1 uses data: { items: []}
Expand Down Expand Up @@ -799,7 +799,102 @@
return truncatedKeysSaveData;
}

async function loadICMdataAsPDF(req,res) {

//load the savedJson form ICM.
let params = req.body;
const rawHost = (req.get("X-Original-Server") || req.hostname);
const configOpt = appCfg[rawHost];
params = { ...params,...configOpt };
const attachment_id = params["attachmentId"];
const office_name = params["OfficeName"];
console.log("attachment_id>>", attachment_id);
if (!attachment_id) {
return res
.status(400)
.send({ error: getErrorMessage("ATTACHMENT_ID_REQUIRED") });
}
let username = null;

if (params["token"]) {
username = await getUsername(params["token"], params["employeeEndpoint"]);
} else if (params["username"]) {
const valid = await isUsernameValid(params["username"], params["employeeEndpoint"]);
username = valid ? params["username"] : null;
}

if (!username || !isNaN(username)) {
return res
.status(401)
.send({ error: getErrorMessage("INVALID_USER") });
}

// Acquire token once and reuse for all ICM calls
let authHeaders;
try {
const grant = await keycloakForSiebel.grantManager.obtainFromClientCredentials();
authHeaders = {
Authorization: `Bearer ${grant.id_token.token}`,
"X-ICM-TrustedUsername": username,
};
} catch (error) {
console.error("Failed to acquire Keycloak token:", error);
return res.status(500).send({ error: getErrorMessage("GENERIC_ERROR_MSG") });
}

let icm_metadata = await getICMAttachmentStatus(attachment_id, username, params, authHeaders);
let icm_status = icm_metadata["Status"];
if (!icm_status || icm_status == "") {
console.log("Error fetching Form Instance Thin data for ", attachment_id);
return res
.status(400)
.send({ error: getErrorMessage("FORM_STATUS_NOT_FOUND") });
}
//let url = buildUrlWithParams('SIEBEL_ICM_API_HOST', 'fwd/v1.0/data/DT FormFoundry Upsert/DT Form Instance Orbeon Revise/'+attachment_id+'/','');
let url = buildUrlWithParams(params["apiHost"], params["saveEndpoint"] + attachment_id + '/', params);
try {
let response;
const query = {
viewMode: "Catalog",
inlineattachment: true
}
if (params.icmWorkspace) {
query.workspace = params.icmWorkspace;
}
response = await axios.get(url, { params: query, headers: authHeaders });
Comment thread Dismissed
let return_data = Buffer.from(response.data["Doc Attachment Id"], 'base64').toString('utf-8');
//validate the returned data to be of the expected format
const valid = isJsonStringValid(return_data);
if (!valid) {
console.log('JSON is not valid ');
return res
.status(400)
.send({ error: getErrorMessage("FORM_NOT_VALID") });
}
//validate ends here. Once validated continue to modify json based on status and send back.
if (icm_status == "Complete") {
let new_data = JSON.parse(return_data);
new_data.form_definition.readOnly = true;
return_data = JSON.stringify(new_data);
}

const pdfBuffer = await generatePDF(return_data); //get the pdf from the savedJson

const pdfBase64 = Buffer.from(pdfBuffer).toString('base64');
res.status(200).json({
pdf_base64: pdfBase64
});
}
catch (error) {
console.error(`Error loading data from ICM for PDF:`, error);
return res
.status(400)
.send({ error: getErrorMessage("GENERIC_ERROR_MSG") });
}
}

module.exports.saveICMdata = saveICMdata;
module.exports.loadICMdata = loadICMdata;
module.exports.clearICMLockedFlag = clearICMLockedFlag;
module.exports.getICMAttachmentStatus = getICMAttachmentStatus;
module.exports.getICMAttachmentStatus = getICMAttachmentStatus;
module.exports.loadICMdataAsPDF = loadICMdataAsPDF;
Loading