Skip to content

Commit c930443

Browse files
authored
Merge pull request #89 from JohT/fix/visualization-timeout
Fix JavaScript Graph Visualization by running it after all CSV reports that write data into the Graph
2 parents 63aa4dd + 667522e commit c930443

File tree

6 files changed

+87
-31
lines changed

6 files changed

+87
-31
lines changed

.github/workflows/code-structure-analysis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ jobs:
141141
name: code-analysis-logs-java-${{ matrix.java }}-python-${{ matrix.python }}-mambaforge-${{ matrix.mambaforge }}
142142
path: |
143143
./temp/**/runtime/*
144+
./temp/**/reports/*
144145
retention-days: 5
145146

146147
# Upload successful results in case they are needed for troubleshooting

graph-visualization/renderVisualizations.js

Lines changed: 64 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ console.log(`renderVisualizations.js: dirname=${__dirname}`);
1313

1414
/**
1515
* Crops the image in the buffer so that there is no empty frame around it.
16-
* @param {Buffer} buffer
16+
* @param {Buffer} buffer
1717
* @returns Buffer
1818
*/
1919
const autoCropImageBuffer = async (buffer) => {
@@ -31,41 +31,72 @@ const autoCropImageBuffer = async (buffer) => {
3131
*/
3232
const camelToKebabCase = (str) => str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
3333

34+
/**
35+
* Take a screenshot after an error happened.
36+
*
37+
* @param {string} htmlFilename
38+
* @param {string} reason
39+
*/
40+
const makeScreenshotOfError = async (page, htmlFilename, reason) => {
41+
const reportName = basename(htmlFilename, ".html");
42+
const directoryName = camelToKebabCase(reportName);
43+
console.log(`Taking an error screenshot of report ${reportName}. Reason: ${reason}`);
44+
await page.screenshot({ path: `./${directoryName}/error-${reportName}-${reason}.png`, omitBackground: false });
45+
};
46+
47+
/**
48+
* Handle a catched error by taking a screenshot.
49+
*
50+
* @param {string} htmlFilename
51+
* @param {string} reason
52+
* @return
53+
*/
54+
const handleErrorWithScreenshot = (page, htmlFilename, reason) => async (error) => {
55+
await makeScreenshotOfError(page, htmlFilename, reason);
56+
throw new Error(error);
57+
};
3458
/**
3559
* Creates a new page and takes a screenshot of all canvas tags that are contained within a div.
3660
* Note: Don't run this in parallel. See https://github.com/puppeteer/puppeteer/issues/1479
3761
* @param {Browser} browser
3862
* @param {string} htmlFilename
3963
*/
4064
const takeCanvasScreenshots = async (browser, htmlFilename) => {
65+
const reportName = basename(htmlFilename, ".html");
66+
const directoryName = camelToKebabCase(reportName);
67+
if (!existsSync(directoryName)) {
68+
mkdirSync(directoryName);
69+
}
70+
4171
const page = await browser.newPage();
72+
page.on("requestfailed", (request) => console.log(`${request.failure().errorText} ${request.url()}`));
4273
await page.setViewport({ width: 1500, height: 1000, isMobile: false, isLandscape: true, hasTouch: false, deviceScaleFactor: 1 });
4374

4475
console.log(`Loading ${htmlFilename}`);
4576
await page.goto(`file://${htmlFilename}`);
4677

78+
if (!process.env.NEO4J_INITIAL_PASSWORD) {
79+
throw new Error("Missing environment variable NEO4J_INITIAL_PASSWORD");
80+
}
4781
// Login with Neo4j server password from the environment variable NEO4J_INITIAL_PASSWORD
4882
const loginButton = await page.waitForSelector("#neo4j-server-login");
4983
await page.type("#neo4j-server-password", process.env.NEO4J_INITIAL_PASSWORD);
5084
await loginButton.click();
5185

5286
// Wait for the graph visualization to be rendered onto a HTML5 canvas
5387
console.log(`Waiting for visualizations to be finished`);
54-
await page.waitForSelector(".visualization-finished", { timeout: 90_000 });
88+
await page
89+
.waitForSelector(".visualization-finished", { timeout: 90_000 })
90+
.catch(handleErrorWithScreenshot(page, htmlFilename, "visualization-did-not-finish"));
5591

5692
// Get all HTML canvas tag elements
5793
const canvasElements = await page.$$("canvas");
5894
if (canvasElements.length <= 0) {
59-
console.error(`No elements with CSS selector 'canvas' found in ${htmlFilename}`);
95+
await makeScreenshotOfError(page, htmlFilename, "no-canvas-found");
6096
}
6197
console.log(`Found ${canvasElements.length} visualizations`);
6298

6399
// Take a png screenshot of every canvas element and save them with increasing indices
64-
const reportName = basename(htmlFilename, ".html");
65-
const directoryName = camelToKebabCase(reportName);
66-
if (!existsSync(directoryName)) {
67-
mkdirSync(directoryName);
68-
}
69100
await Promise.all(
70101
Array.from(canvasElements).map(async (canvasElement, index) => {
71102
console.log(`Exporting image ${reportName}-${index}.png...`);
@@ -74,10 +105,8 @@ const takeCanvasScreenshots = async (browser, htmlFilename) => {
74105
}, canvasElement);
75106
let data = Buffer.from(dataUrl.split(",").pop(), "base64");
76107
console.log(`Cropping image ${reportName}-${index}.png...`);
77-
data = await autoCropImageBuffer(data);
108+
data = await autoCropImageBuffer(data).catch(handleErrorWithScreenshot(page, htmlFilename, `failed-to-crop-image-${index}`));
78109
writeFileSync(`./${directoryName}/${reportName}-${index}.png`, data);
79-
// console.log(`Taking screenshot ${reportName} of canvas ${index} in ${htmlFilename} of element...`);
80-
// await canvasElement.screenshot({ path: `./${directoryName}/${reportName}-${index}.png`, omitBackground: true });
81110
})
82111
);
83112
};
@@ -89,17 +118,34 @@ let browser;
89118
* and takes a screenshot of the canvas elements using {@link takeCanvasScreenshots}.
90119
*/
91120
(async () => {
92-
console.log('renderVisualizations.js: Starting headless browser...');
93-
browser = await puppeteer.launch({ headless: "new" }); // { headless: false } for testing
121+
console.log("renderVisualizations.js: Starting headless browser...");
122+
browser = await puppeteer.launch({
123+
headless: "new",
124+
dumpio: true,
125+
args: [
126+
"--enable-logging",
127+
"--disable-search-engine-choice-screen",
128+
"--ash-no-nudges",
129+
"--no-first-run",
130+
"--no-default-browser-check",
131+
"--hide-scrollbars",
132+
"--disable-features=Translate",
133+
"--disable-features=InterestFeedContentSuggestions",
134+
"--disable-extensions",
135+
"--disable-default-apps",
136+
"--disable-component-extensions-with-background-pages",
137+
"--disable-client-side-phishing-detection",
138+
"--use-gl=disabled",
139+
"--disable-features=Vulkan",
140+
],
141+
}); // { headless: false } for testing
94142

95143
// Get all *.html files in this (script) directory and its subdirectories
96-
// The separate filter is needed to ignore the "node_modules" directory.
144+
// The separate filter is needed to ignore the "node_modules" directory.
97145
// Glob's build-in filter doesn't seem to work on Windows.
98-
const htmlFiles = globSync(`${__dirname}/**/*.html`, { absolute: true }).filter(file => !file.includes('node_modules'));
146+
const htmlFiles = globSync(`${__dirname}/**/*.html`, { absolute: true }).filter((file) => !file.includes("node_modules"));
99147
for (const htmlFile of htmlFiles) {
100148
await takeCanvasScreenshots(browser, htmlFile);
101149
}
102150
console.log(`renderVisualizations.js: Successfully rendered ${htmlFiles.length} html file(s)`);
103-
})()
104-
.catch((err) => console.error(err))
105-
.finally(() => browser?.close());
151+
})().finally(() => browser?.close());

graph-visualization/visualization-pagination.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@ function paginatedGraphVisualization({
2626
/**
2727
* Marks the given element as finished when the visualization is completed.
2828
* @param {Element} indexedVisualizationElement
29+
* @param {string} logDescription
2930
*/
30-
function markVisualizationAsFinished(indexedVisualizationElement) {
31+
function markVisualizationAsFinished(indexedVisualizationElement, logDescription) {
3132
indexedVisualizationElement.classList.add(classOfFinishedVisualization);
3233
const unfinishedVisualizations = document.querySelectorAll(`.${classOfIndexedVisualizationElement}:not(.${classOfFinishedVisualization})`);
3334
if (unfinishedVisualizations.length === 0) {
35+
console.debug(`${logDescription}: Last visualization finished on element ${JSON.stringify(indexedVisualizationElement)}.`);
36+
console.debug(`${logDescription}: Mark whole visualization as finished on parent element ${JSON.stringify( indexedVisualizationElement.parentElement)}`);
3437
indexedVisualizationElement.parentElement.classList.add(classOfFinishedVisualization);
3538
}
3639
}
@@ -48,19 +51,23 @@ function paginatedGraphVisualization({
4851

4952
neoViz.registerOnEvent(NeoVis.NeoVisEvents.CompletionEvent, (event) => {
5053
if (event.recordCount == 0) {
54+
if (index=0) {
55+
log.error('No query results. Nothing to visualize. Check the query and if the nodes and properties have been written.')
56+
}
5157
indexedVisualizationContainer.remove(); // remove an empty canvas
52-
markVisualizationAsFinished(indexedVisualizationContainer);
58+
markVisualizationAsFinished(indexedVisualizationContainer, 'No query results (anymore)');
5359
} else {
5460
setTimeout(() => {
5561
neoViz.stabilize();
56-
markVisualizationAsFinished(indexedVisualizationContainer);
62+
markVisualizationAsFinished(indexedVisualizationContainer, 'Visualization stabilized');
5763
}, 5000);
5864
}
5965
});
6066
neoViz.registerOnEvent(NeoVis.NeoVisEvents.ErrorEvent, (event) => {
6167
indexedVisualizationContainer.classList.add(classOfFailedVisualization);
6268
indexedVisualizationContainer.textContent = event.error.message;
63-
markVisualizationAsFinished(indexedVisualizationContainer);
69+
console.error(`Visualization Error: ${JSON.stringify(event.error)}`)
70+
markVisualizationAsFinished(indexedVisualizationContainer, 'Error event');
6471
});
6572
const parameters = {
6673
blockSize: recordsPerVisualization,

scripts/reports/GraphVisualization.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,6 @@ if [ ! -d "${GRAPH_VISUALIZATION_DIRECTORY}/node_modules" ] ; then
3333
fi
3434

3535
# Execute the node.js script to render the graph visualizations as image files
36-
(cd "${REPORTS_DIRECTORY}" && exec node "${GRAPH_VISUALIZATION_DIRECTORY}/renderVisualizations.js")
36+
(cd "${REPORTS_DIRECTORY}" && exec node "${GRAPH_VISUALIZATION_DIRECTORY}/renderVisualizations.js")
37+
38+
echo "GraphVisualization: Successfully finished"

scripts/reports/compilations/AllReports.sh

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ set -o errexit -o pipefail
1515
REPORT_COMPILATIONS_SCRIPT_DIR=${REPORT_COMPILATIONS_SCRIPT_DIR:-$( CDPATH=. cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P )}
1616
echo "AllReports: REPORT_COMPILATIONS_SCRIPT_DIR=${REPORT_COMPILATIONS_SCRIPT_DIR}"
1717

18-
REPORTS_SCRIPT_DIR=${REPORTS_SCRIPT_DIR:-$(dirname -- "${REPORT_COMPILATIONS_SCRIPT_DIR}")}
19-
echo "AllReports: REPORTS_SCRIPT_DIR=${REPORTS_SCRIPT_DIR}"
20-
21-
# Run all report scripts
22-
for report_script_file in "${REPORTS_SCRIPT_DIR}"/*.sh; do
23-
echo "AllReports: Starting ${report_script_file}...";
24-
source "${report_script_file}"
25-
done
18+
# The reports will not be generically searched as files anymore
19+
# but will be processed in order. Especially the visualization
20+
# needs to be done as a last step to be able to use properties
21+
# and data written to the Graph in the CsvReports.
22+
source "${REPORT_COMPILATIONS_SCRIPT_DIR}/CsvReports.sh"
23+
source "${REPORT_COMPILATIONS_SCRIPT_DIR}/JupyterReports.sh"
24+
source "${REPORT_COMPILATIONS_SCRIPT_DIR}/VisualizationReports.sh"

scripts/reports/compilations/VisualizationReports.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
# Therefore these reports will take longer and require more ressources than just plain database queries/procedures.
77

88
# Requires reports/*.sh
9+
# Needs to run after reports/TopologySortCsv.sh that provides the property "topologicalSortIndex" to be queried.
910

1011
# Fail on any error ("-e" = exit on first error, "-o pipefail" exist on errors within piped commands)
1112
set -o errexit -o pipefail

0 commit comments

Comments
 (0)