Skip to content

Commit

Permalink
Widget compatible with DocumentGeneration 1.1 RC
Browse files Browse the repository at this point in the history
  • Loading branch information
mgroeneweg committed Aug 24, 2023
1 parent c326963 commit 0dd8c51
Show file tree
Hide file tree
Showing 26 changed files with 340 additions and 106 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "documentlayoutwidget",
"widgetName": "DocumentLayoutWidget",
"version": "1.0.0",
"version": "1.1.0",
"description": "Document layout widget",
"copyright": "2023 Aiden",
"author": "Marcel Groeneweg",
Expand Down
2 changes: 1 addition & 1 deletion src/package.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="DocumentLayoutWidget" version="1.0.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<clientModule name="DocumentLayoutWidget" version="1.1.0" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="DocumentLayoutWidget.xml"/>
</widgetFiles>
Expand Down
8 changes: 8 additions & 0 deletions src/ui/DocumentLayoutWidget.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
width: 100%;
}

.document-layout-widget .layout-table > thead {
break-inside: avoid;
}

.document-layout-widget .layout-table > tfoot {
break-inside: avoid;
}

.document-layout-widget .footer {
position: fixed;
bottom: 0;
Expand Down
Binary file modified test/TestDocLayoutWidget.mpr
Binary file not shown.
12 changes: 12 additions & 0 deletions test/javascriptsource/documentgeneration/service/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"tabWidth": 4,
"useTabs": false,
"overrides": [
{
"files": ["*.json", "*.prettierrc"],
"options": {
"tabWidth": 2
}
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,42 @@ async function waitForContent(page) {
});
}

async function emulateScreenMedia(page) {
await withTiming("Emulate screen media", async () => {
await page.emulateMediaType("screen");
}).catch((error) => {
throw new Error(`Failed to emulate screen media: ${error}`);
});
}

async function injectClassToBodyElement(page, className) {
await withTiming(
`Inject class '${className}' to body element`,
async () => {
const bodyHandle = await page.$("body");
if (bodyHandle !== undefined) {
await page.evaluate(
(body, className) => body.classList.add(className),
bodyHandle,
className
);
await bodyHandle.dispose();
}
}
).catch((error) => {
throw new Error(`Failed to inject class to body element: ${error}`);
});
}

async function emulateTimezone(page, timezone) {
await withTiming("Emulate timezone", async () => {
await page.emulateTimezone(timezone);
logMessage(`Emulating timezone: ${timezone}`);
}).catch((error) => {
throw new Error(`Failed to emulate timezone: ${error}`);
});
}

async function pageOrientationIsLandscape(page) {
if (page === null) throw new Error("Browser not initialized");

Expand Down Expand Up @@ -113,9 +149,19 @@ function getHeaderFooterOptions(enablePageNumbers) {
};
}

async function generateDocument(page, pageUrl, securityToken, requestAnalyzer) {
async function generateDocument(
page,
pageUrl,
securityToken,
timezone,
useScreenMediaType,
requestAnalyzer
) {
if (useScreenMediaType) await emulateScreenMedia(page);
await emulateTimezone(page, timezone ?? "GMT");
await navigateToPage(page, pageUrl, securityToken, requestAnalyzer);
await waitForContent(page);
await injectClassToBodyElement(page, "document-generation-body-injected");

const isLandscape = await pageOrientationIsLandscape(page);
const pageSize = await determinePageSize(page);
Expand Down Expand Up @@ -152,9 +198,21 @@ export function createDocumentGenerator(browser, requestAnalyzer) {
initialize: async () => {
page = await browser.openPage();
},
generateDocument: async (pageUrl, securityToken) =>
generateDocument: async (
pageUrl,
securityToken,
timezone,
useScreenMediaType
) =>
withTiming("Generate document", async () =>
generateDocument(page, pageUrl, securityToken, requestAnalyzer)
generateDocument(
page,
pageUrl,
securityToken,
timezone,
useScreenMediaType,
requestAnalyzer
)
),
getPageMetrics: async () => getPageMetrics(page),
getRequestStatistics: () => getRequestStatistics(requestAnalyzer),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import axios from "axios";
import { withTiming } from "./logging.js";

async function sendResult(resultUrl, document, securityToken) {
async function sendResult(resultUrl, document, securityToken, maxBodyLength) {
await withTiming("Send result back", () =>
axios({
method: "POST",
url: resultUrl,
data: document,
maxBodyLength,
headers: {
"Content-Type": "application/pdf",
"X-Security-Token": securityToken,
Expand All @@ -17,8 +18,12 @@ async function sendResult(resultUrl, document, securityToken) {
});
}

export function createModuleConnector() {
export function createModuleConnector(maxBodyLength) {
if (maxBodyLength === undefined || Math.sign(maxBodyLength) !== 1)
throw new Error(`Invalid value '${maxBodyLength}' for maxBodyLength`);

return {
sendResult,
sendResult: async (resultUrl, document, securityToken) =>
sendResult(resultUrl, document, securityToken, maxBodyLength),
};
}
21 changes: 18 additions & 3 deletions test/javascriptsource/documentgeneration/service/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import parseArguments from "command-line-args";
import formatSize from "pretty-bytes";

import { createBrowser } from "./components/browser.js";
import { createRequestAnalyzer } from "./components/request-analyzer.js";
Expand All @@ -13,7 +14,9 @@ const options = parseArguments([
{ name: "result-path" },
{ name: "request-id" },
{ name: "security-token" },
{ name: "enable-metrics", type: Boolean }
{ name: "timezone" },
{ name: "use-screen-media", type: Boolean },
{ name: "enable-metrics", type: Boolean },
]);

const {
Expand All @@ -23,11 +26,15 @@ const {
"result-path": resultPath,
"request-id": requestId,
"security-token": securityToken,
timezone: timezone,
"use-screen-media": useScreenMediaType,
"enable-metrics": enableMetrics,
} = options;

const maxDocumentSize = 25000000; // 25 MB

const requestAnalyzer = enableMetrics ? createRequestAnalyzer() : undefined;
const moduleConnector = createModuleConnector();
const moduleConnector = createModuleConnector(maxDocumentSize);

await withBrowser(async (browser) => {
const documentGenerator = createDocumentGenerator(browser, requestAnalyzer);
Expand All @@ -38,9 +45,17 @@ await withBrowser(async (browser) => {

const document = await documentGenerator.generateDocument(
pageUrl,
securityToken
securityToken,
timezone,
useScreenMediaType
);

const documentSize = formatSize(document.length, {
minimumFractionDigits: 3,
});

logMessage(`Document size: ${documentSize}`);

if (enableMetrics) {
const metrics = await documentGenerator.getPageMetrics();
logMessage(`Page metrics: ${JSON.stringify(metrics)}`);
Expand Down
21 changes: 19 additions & 2 deletions test/javascriptsource/documentgeneration/service/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions test/javascriptsource/documentgeneration/service/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mx-docgen-local",
"version": "0.1.0",
"version": "1.1.0",
"type": "module",
"description": "Puppeteer-based script to support local document generation",
"main": "index.js",
Expand All @@ -10,8 +10,9 @@
"author": "Mendix Technology B.V.",
"license": "Apache-2.0",
"dependencies": {
"axios": "^0.27.2",
"command-line-args": "^5.2.1",
"puppeteer-core": "^14.2.0",
"axios": "^0.27.2"
"pretty-bytes": "^6.1.0",
"puppeteer-core": "^14.2.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import com.mendix.core.Core;
import com.mendix.core.CoreException;
import com.mendix.core.conf.RuntimeVersion;
import com.mendix.externalinterface.connector.RequestHandler;
import com.mendix.logging.ILogNode;
import com.mendix.m2ee.api.IMxRuntimeRequest;
Expand Down Expand Up @@ -99,23 +102,22 @@ private void generateDocument(IMxRuntimeRequest request, IMxRuntimeResponse resp
documentRequest.setDocumentRequest_Session(Session.initialize(Core.createSystemContext(), session.getMendixObject()));
documentRequest.commit();

response.addCookie("XASSESSIONID", session.getId().toString(), "/", "", -1);
response.addCookie("XASID", "0." + Core.getXASId(), "/", "", -1);
addCookies(response, session);
}

response.getHttpServletResponse().sendRedirect(DocumentGenerator.getApplicationURL() + "/p/generate-document/"
+ documentRequest.getMendixObject().getId().toLong());
+ documentRequest.getMendixObject().getId().toLong() + "?profile=Responsive");
}

private void processResult(IMxRuntimeRequest request, IMxRuntimeResponse response, ISession session,
DocumentRequest documentRequest) throws CoreException {

IContext userContext = session.createContext();
FileDocument outputDocument = this.getFileDocument(userContext, documentRequest);
IContext sudoContext = session.createContext().createSudoClone();
FileDocument outputDocument = DocGenRequestHandler.getFileDocument(sudoContext, documentRequest);

if (outputDocument != null) {
try (InputStream is = request.getInputStream()) {
Core.storeFileDocumentContent(userContext, outputDocument.getMendixObject(), is);
Core.storeFileDocumentContent(sudoContext, outputDocument.getMendixObject(), is);
} catch (IOException e) {
logging.error("Could not write to file: " + e.getMessage());
return;
Expand All @@ -126,9 +128,9 @@ private void processResult(IMxRuntimeRequest request, IMxRuntimeResponse respons
}
}

private FileDocument getFileDocument(IContext context, DocumentRequest documentRequest) {
private static FileDocument getFileDocument(IContext context, DocumentRequest documentRequest) {
try {
FileDocument document = documentRequest.getDocumentRequest_FileDocument();
FileDocument document = documentRequest.getDocumentRequest_FileDocument(context);

if (document != null) {
logging.trace("Using existing FileDocument object");
Expand All @@ -142,10 +144,33 @@ private FileDocument getFileDocument(IContext context, DocumentRequest documentR
return DocumentGenerator.generateFileDocument(context, documentRequest.getResultEntity(),
documentRequest.getFileName());
}

private static void addCookies(IMxRuntimeResponse response, ISession session) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
String[] runtimeVersion = RuntimeVersion.getInstance().toString().split("\\.");

if ((Integer.parseInt(runtimeVersion[0]) == 9 && Integer.parseInt(runtimeVersion[1]) >= 20) || Integer.parseInt(runtimeVersion[0]) >= 10) {
// Use reflection to call the addCookie method with the 7th parameter for 'isHostOnly', which was added in 9.20
// Based on the SessionHandler implementation of the Deeplink module (https://github.com/mendix/DeeplinkModule)
@SuppressWarnings("rawtypes")
Class[] methodSignature = {String.class, String.class, String.class, String.class, int.class, boolean.class, boolean.class};
Method addCookie = response.getClass().getMethod("addCookie", methodSignature);

// For Mx 9.20 and above:
// addCookie(String key, String value, String path, String domain, int expiry, boolean isHttpOnly, boolean isHostOnly)
addCookie.invoke(response, Core.getConfiguration().getSessionIdCookieName(), session.getId().toString(), "/", "", -1, true, true);
addCookie.invoke(response, XAS_ID, "0." + Core.getXASId(),"/", "", -1, true, true);
} else {
// For Mx 9.19 and below:
// addCookie(String key, String value, String path, String domain, int expiry, boolean isHttpOnly)
response.addCookie(Core.getConfiguration().getSessionIdCookieName(), session.getId().toString(), "/", "", -1, true);
response.addCookie(XAS_ID, "0." + Core.getXASId(),"/", "", -1, true);
}
}

private static final ILogNode logging = Logging.logNode;
public static final String ENDPOINT = "docgen/";
public static final String GENERATE_PATH = "generate";
public static final String RESULT_PATH = "result";
public static final String VERIFY_PATH = "verify";
private static final String XAS_ID = "XASID";
}
Loading

0 comments on commit 0dd8c51

Please sign in to comment.