Tony CHEMIT pushed to branch feature/issue_2755 at ultreiaio / ird-observe
Commits:
-
45538604
by Tony Chemit at 2023-11-16T18:49:02+01:00
7 changed files:
- client/datasource/actions/src/main/resources/report/html/default/report.html → client/datasource/actions/src/main/i18n/templates/reportHtmlExport_en_GB.ftl
- + client/datasource/actions/src/main/i18n/templates/reportHtmlExport_es_ES.ftl
- + client/datasource/actions/src/main/i18n/templates/reportHtmlExport_fr_FR.ftl
- client/datasource/actions/src/main/java/fr/ird/observe/client/datasource/actions/report/ResultJsonModel.java → client/datasource/actions/src/main/java/fr/ird/observe/client/datasource/actions/report/HtmlExportModel.java
- client/datasource/actions/src/main/java/fr/ird/observe/client/datasource/actions/report/ReportModel.java
- client/datasource/actions/src/main/java/fr/ird/observe/client/datasource/actions/report/ResultTableModel.java
- client/datasource/actions/src/main/java/fr/ird/observe/client/datasource/actions/report/actions/ExportToHtml.java
Changes:
| ... | ... | @@ -74,7 +74,7 @@ |
| 74 | 74 | }
|
| 75 | 75 | new gridjs.Grid(config).render(container);
|
| 76 | 76 | }
|
| 77 | - var json = ${json};
|
|
| 77 | + var json = ${data};
|
|
| 78 | 78 | </script>
|
| 79 | 79 | </head>
|
| 80 | 80 | <body>
|
| 1 | +<#--
|
|
| 2 | + #%L
|
|
| 3 | + ObServe Client :: DataSource :: Actions
|
|
| 4 | + %%
|
|
| 5 | + Copyright (C) 2008 - 2023 IRD, Ultreia.io
|
|
| 6 | + %%
|
|
| 7 | + This program is free software: you can redistribute it and/or modify
|
|
| 8 | + it under the terms of the GNU General Public License as
|
|
| 9 | + published by the Free Software Foundation, either version 3 of the
|
|
| 10 | + License, or (at your option) any later version.
|
|
| 11 | + |
|
| 12 | + This program is distributed in the hope that it will be useful,
|
|
| 13 | + but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 14 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 15 | + GNU General Public License for more details.
|
|
| 16 | + |
|
| 17 | + You should have received a copy of the GNU General Public
|
|
| 18 | + License along with this program. If not, see
|
|
| 19 | + <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 20 | + #L%
|
|
| 21 | +--> |
| 1 | +<#-- @ftlvariable name=".data_model" type="fr.ird.observe.client.datasource.actions.report.HtmlExportModel" -->
|
|
| 2 | +<!DOCTYPE html>
|
|
| 3 | +<!--
|
|
| 4 | + #%L
|
|
| 5 | + ObServe Client :: DataSource :: Actions
|
|
| 6 | + %%
|
|
| 7 | + Copyright (C) 2008 - 2023 IRD, Ultreia.io
|
|
| 8 | + %%
|
|
| 9 | + This program is free software: you can redistribute it and/or modify
|
|
| 10 | + it under the terms of the GNU General Public License as
|
|
| 11 | + published by the Free Software Foundation, either version 3 of the
|
|
| 12 | + License, or (at your option) any later version.
|
|
| 13 | + |
|
| 14 | + This program is distributed in the hope that it will be useful,
|
|
| 15 | + but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 16 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 17 | + GNU General Public License for more details.
|
|
| 18 | + |
|
| 19 | + You should have received a copy of the GNU General Public
|
|
| 20 | + License along with this program. If not, see
|
|
| 21 | + <http://www.gnu.org/licenses/gpl-3.0.html>.
|
|
| 22 | + #L%
|
|
| 23 | + -->
|
|
| 24 | + |
|
| 25 | +<html lang="en">
|
|
| 26 | +<head>
|
|
| 27 | + <link href="https://unpkg.com/gridjs/dist/theme/mermaid.min.css" rel="stylesheet"/>
|
|
| 28 | + <title>Report...</title>
|
|
| 29 | + <script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
|
|
| 30 | + <style>
|
|
| 31 | + .config-panel {
|
|
| 32 | + padding: 10px;
|
|
| 33 | + margin: 10px 0;
|
|
| 34 | + background: #fcfcfc;
|
|
| 35 | + border: 1px solid #e9e9e9;
|
|
| 36 | + display: inline-block;
|
|
| 37 | + }
|
|
| 38 | + |
|
| 39 | + .config-panel label {
|
|
| 40 | + margin-right: 10px;
|
|
| 41 | + }
|
|
| 42 | + |
|
| 43 | + td.gridjs-td {
|
|
| 44 | + background-color: transparent;
|
|
| 45 | + }
|
|
| 46 | + |
|
| 47 | + tr:nth-child(even) {
|
|
| 48 | + background-color: #dddddd;
|
|
| 49 | + }
|
|
| 50 | + </style>
|
|
| 51 | + <script type="application/javascript">
|
|
| 52 | + const config = {
|
|
| 53 | + language: {
|
|
| 54 | + 'search': {
|
|
| 55 | + 'placeholder': '🔍 Recherche...'
|
|
| 56 | + }
|
|
| 57 | + }
|
|
| 58 | + };
|
|
| 59 | + const json = ${.data_model.json};
|
|
| 60 | + </script>
|
|
| 61 | +</head>
|
|
| 62 | +<body>
|
|
| 63 | +<h1>Rapport ${.data_model.selectedReport.name}</h1>
|
|
| 64 | +<h2>Configuration</h2>
|
|
| 65 | +<h3>Données sélectionnées :</h3>
|
|
| 66 | +<ul>
|
|
| 67 | + <li>
|
|
| 68 | + <#assign selectDataModel = .data_model.selectDataModel.selectionDataModel />
|
|
| 69 | + <#assign selectDataCount = selectDataModel.getSelectedCount() />
|
|
| 70 | + |
|
| 71 | + <#if selectDataCount == 1> 1 marée <#else> ${selectDataCount} marées</#if>.
|
|
| 72 | + <ul>
|
|
| 73 | + <#list selectDataModel.getSelectedLabels() as key, value>
|
|
| 74 | + <li>
|
|
| 75 | + ${key}
|
|
| 76 | + <ul>
|
|
| 77 | + <#list value as trip>
|
|
| 78 | + <li>${trip}</li>
|
|
| 79 | + </#list>
|
|
| 80 | + </ul>
|
|
| 81 | + </li>
|
|
| 82 | + </#list>
|
|
| 83 | + </ul>
|
|
| 84 | + </li>
|
|
| 85 | +</ul>
|
|
| 86 | +<h3>Rapport sélectionné :</h3>
|
|
| 87 | + |
|
| 88 | +<#assign selectedReport = .data_model.selectedReport />
|
|
| 89 | +<p><b>${selectedReport.name}</b></p>
|
|
| 90 | +<#list selectedReport.getVariables() as variable>
|
|
| 91 | + <li>
|
|
| 92 | + Variable ${variable.name} : ${variable.selectedValue}.
|
|
| 93 | + </li>
|
|
| 94 | +</#list>
|
|
| 95 | +<h2>Résultat</h2>
|
|
| 96 | + |
|
| 97 | +<div class="config-panel">
|
|
| 98 | + <label><input id="search" type="checkbox" class="action" checked/> Search</label>
|
|
| 99 | + <label><input id="resizable" type="checkbox" class="action" checked/> Colonnes redimensionnables</label>
|
|
| 100 | + <label><input id="sort" type="checkbox" class="action" checked/> Sorting</label>
|
|
| 101 | + <label><input id="pagination" type="checkbox" class="action"/> Pagination</label>
|
|
| 102 | + <label>Page size<input id="paginationSize" type="number" disabled value="20"/></label>
|
|
| 103 | +</div>
|
|
| 104 | +<div id="wrapperParent">
|
|
| 105 | + <div id="wrapper"></div>
|
|
| 106 | +</div>
|
|
| 107 | +<script type="application/javascript">
|
|
| 108 | + const gridContainerParent = document.getElementById("wrapperParent");
|
|
| 109 | + const searchOption = document.getElementById("search");
|
|
| 110 | + const resizableOption = document.getElementById("resizable");
|
|
| 111 | + const paginationOption = document.getElementById("pagination");
|
|
| 112 | + const paginationSizeOption = document.getElementById("paginationSize");
|
|
| 113 | + const sortOption = document.getElementById("sort");
|
|
| 114 | + |
|
| 115 | + // FIXME Use options value from the very beginning :)
|
|
| 116 | + |
|
| 117 | + let grid = new gridjs.Grid({
|
|
| 118 | + search: true, resizable: true, sort: true,
|
|
| 119 | + language: config.language, data: []
|
|
| 120 | + });
|
|
| 121 | + |
|
| 122 | + function initGrid(json, container) {
|
|
| 123 | + let config = {data: json.data.data};
|
|
| 124 | + if (!!json.columnNames) {
|
|
| 125 | + config.columns = json.columnNames;
|
|
| 126 | + } else {
|
|
| 127 | + config.columns = null;
|
|
| 128 | + }
|
|
| 129 | + grid.updateConfig(config).render(container);
|
|
| 130 | + }
|
|
| 131 | + |
|
| 132 | + initGrid(json, document.getElementById("wrapper"));
|
|
| 133 | + |
|
| 134 | + function toggle(source) {
|
|
| 135 | + let newValue = source.checked;
|
|
| 136 | + let option = source.id;
|
|
| 137 | + switch (option) {
|
|
| 138 | + case "search":
|
|
| 139 | + toggleSearch(newValue);
|
|
| 140 | + break;
|
|
| 141 | + case "resizable":
|
|
| 142 | + toggleResizable(newValue);
|
|
| 143 | + break;
|
|
| 144 | + case "pagination":
|
|
| 145 | + togglePagination(newValue);
|
|
| 146 | + break;
|
|
| 147 | + case "paginationSize":
|
|
| 148 | + changePaginationSize(newValue);
|
|
| 149 | + break;
|
|
| 150 | + case "sort":
|
|
| 151 | + toggleSort(newValue);
|
|
| 152 | + break;
|
|
| 153 | + }
|
|
| 154 | + }
|
|
| 155 | + |
|
| 156 | + function toggleSearch(newValue) {
|
|
| 157 | + console.info("Toggle search to: " + newValue);
|
|
| 158 | + let newConfig = {
|
|
| 159 | + language: config.language,
|
|
| 160 | + data: grid.config.data,
|
|
| 161 | + columns: grid.config.columns,
|
|
| 162 | + resizable: resizableOption.checked,
|
|
| 163 | + pagination: paginationOption.checked ? {limit: paginationSizeOption.value} : false,
|
|
| 164 | + sort: sortOption.checked
|
|
| 165 | + };
|
|
| 166 | + if (newValue) {
|
|
| 167 | + newConfig.search = true;
|
|
| 168 | + }
|
|
| 169 | + updateGrid(newConfig);
|
|
| 170 | + }
|
|
| 171 | + |
|
| 172 | + function toggleResizable(newValue) {
|
|
| 173 | + console.info("Toggle resizable to: " + newValue);
|
|
| 174 | + let newConfig = {
|
|
| 175 | + language: config.language,
|
|
| 176 | + data: grid.config.data,
|
|
| 177 | + columns: grid.config.columns,
|
|
| 178 | + search: searchOption.checked,
|
|
| 179 | + pagination: paginationOption.checked ? {limit: paginationSizeOption.value} : false,
|
|
| 180 | + sort: sortOption.checked
|
|
| 181 | + };
|
|
| 182 | + if (newValue) {
|
|
| 183 | + newConfig.resizable = true;
|
|
| 184 | + }
|
|
| 185 | + updateGrid(newConfig);
|
|
| 186 | + }
|
|
| 187 | + |
|
| 188 | + function toggleSort(newValue) {
|
|
| 189 | + console.info("Toggle sort to: " + newValue);
|
|
| 190 | + let newConfig = {
|
|
| 191 | + language: config.language,
|
|
| 192 | + data: grid.config.data,
|
|
| 193 | + columns: grid.config.columns,
|
|
| 194 | + search: searchOption.checked,
|
|
| 195 | + pagination: paginationOption.checked ? {limit: paginationSizeOption.value} : false,
|
|
| 196 | + resizable: resizableOption.checked
|
|
| 197 | + };
|
|
| 198 | + if (newValue) {
|
|
| 199 | + newConfig.sort = true;
|
|
| 200 | + }
|
|
| 201 | + updateGrid(newConfig);
|
|
| 202 | + }
|
|
| 203 | + |
|
| 204 | + function togglePagination(newValue) {
|
|
| 205 | + console.info("Toggle pagination to: " + newValue);
|
|
| 206 | + let newConfig = {
|
|
| 207 | + language: config.language,
|
|
| 208 | + data: grid.config.data,
|
|
| 209 | + columns: grid.config.columns,
|
|
| 210 | + search: searchOption.checked,
|
|
| 211 | + resizable: resizableOption.checked,
|
|
| 212 | + sort: sortOption.checked,
|
|
| 213 | + };
|
|
| 214 | + if (newValue) {
|
|
| 215 | + newConfig.pagination = {
|
|
| 216 | + limit: paginationSizeOption.value
|
|
| 217 | + };
|
|
| 218 | + paginationSizeOption["disabled"] = null;
|
|
| 219 | + } else {
|
|
| 220 | + |
|
| 221 | + paginationSizeOption.disabled = true;
|
|
| 222 | + }
|
|
| 223 | + |
|
| 224 | + |
|
| 225 | + updateGrid(newConfig);
|
|
| 226 | + }
|
|
| 227 | + |
|
| 228 | + function changePaginationSize(newValue) {
|
|
| 229 | + console.info("Change pagination size to: " + newValue);
|
|
| 230 | + let newConfig = {
|
|
| 231 | + language: config.language,
|
|
| 232 | + data: grid.config.data,
|
|
| 233 | + columns: grid.config.columns,
|
|
| 234 | + search: searchOption.checked,
|
|
| 235 | + resizable: resizableOption.checked,
|
|
| 236 | + sort: sortOption.checked,
|
|
| 237 | + pagination: {
|
|
| 238 | + limit: paginationSizeOption.value
|
|
| 239 | + }
|
|
| 240 | + };
|
|
| 241 | + updateGrid(newConfig);
|
|
| 242 | + }
|
|
| 243 | + |
|
| 244 | + function updateGrid(newConfig) {
|
|
| 245 | + gridContainerParent.innerHTML = "<div id=\"wrapper\"></div>";
|
|
| 246 | + console.info(newConfig);
|
|
| 247 | + setTimeout(() => {
|
|
| 248 | + grid = new gridjs.Grid(newConfig).render(document.getElementById("wrapper"));
|
|
| 249 | + }, 200);
|
|
| 250 | + }
|
|
| 251 | + |
|
| 252 | + let actions = document.getElementsByClassName("action");
|
|
| 253 | + for (const action of document.getElementsByClassName("action")) {
|
|
| 254 | + action.addEventListener("click", function (event) {
|
|
| 255 | + toggle(this);
|
|
| 256 | + });
|
|
| 257 | + }
|
|
| 258 | + paginationSizeOption.addEventListener("change", function (event) {
|
|
| 259 | + toggle(this);
|
|
| 260 | + });
|
|
| 261 | +</script>
|
|
| 262 | +</body>
|
|
| 263 | +</html> |
| ... | ... | @@ -22,6 +22,10 @@ package fr.ird.observe.client.datasource.actions.report; |
| 22 | 22 | * #L%
|
| 23 | 23 | */
|
| 24 | 24 | |
| 25 | +import com.google.gson.Gson;
|
|
| 26 | +import fr.ird.observe.client.datasource.actions.config.SelectDataModel;
|
|
| 27 | +import fr.ird.observe.report.Report;
|
|
| 28 | +import io.ultreia.java4all.application.template.spi.GenerateTemplate;
|
|
| 25 | 29 | import io.ultreia.java4all.util.matrix.DataMatrix;
|
| 26 | 30 | |
| 27 | 31 | import java.util.List;
|
| ... | ... | @@ -34,24 +38,52 @@ import java.util.List; |
| 34 | 38 | * @author Tony Chemit - dev@tchemit.fr
|
| 35 | 39 | * @since 9.3.0
|
| 36 | 40 | */
|
| 37 | -public class ResultJsonModel {
|
|
| 41 | +@GenerateTemplate(template = "reportHtmlExport.ftl")
|
|
| 42 | +public class HtmlExportModel {
|
|
| 43 | + /**
|
|
| 44 | + * Selected report.
|
|
| 45 | + */
|
|
| 46 | + private final transient Report selectedReport;
|
|
| 47 | + /**
|
|
| 48 | + * Selected data model.
|
|
| 49 | + */
|
|
| 50 | + private final transient SelectDataModel selectDataModel;
|
|
| 38 | 51 | |
| 39 | 52 | private final List<String> columnNames;
|
| 40 | 53 | private final List<String> rowNames;
|
| 41 | 54 | private final DataMatrix data;
|
| 42 | 55 | private final boolean withColumnHeader;
|
| 43 | 56 | private final boolean withRowHeader;
|
| 57 | + private final transient String json;
|
|
| 44 | 58 | |
| 45 | - public ResultJsonModel(List<String> columnNames,
|
|
| 59 | + public HtmlExportModel(Gson gson,
|
|
| 60 | + Report selectedReport,
|
|
| 61 | + SelectDataModel selectDataModel,
|
|
| 62 | + List<String> columnNames,
|
|
| 46 | 63 | List<String> rowNames,
|
| 47 | 64 | DataMatrix data,
|
| 48 | 65 | boolean withColumnHeader,
|
| 49 | 66 | boolean withRowHeader) {
|
| 67 | + this.selectedReport = selectedReport;
|
|
| 68 | + this.selectDataModel = selectDataModel;
|
|
| 50 | 69 | this.columnNames = columnNames;
|
| 51 | 70 | this.rowNames = rowNames;
|
| 52 | 71 | this.data = data;
|
| 53 | 72 | this.withColumnHeader = withColumnHeader;
|
| 54 | 73 | this.withRowHeader = withRowHeader;
|
| 74 | + this.json = gson.toJson(this);
|
|
| 75 | + }
|
|
| 76 | + |
|
| 77 | + public String getJson() {
|
|
| 78 | + return json;
|
|
| 79 | + }
|
|
| 80 | + |
|
| 81 | + public Report getSelectedReport() {
|
|
| 82 | + return selectedReport;
|
|
| 83 | + }
|
|
| 84 | + |
|
| 85 | + public SelectDataModel getSelectDataModel() {
|
|
| 86 | + return selectDataModel;
|
|
| 55 | 87 | }
|
| 56 | 88 | |
| 57 | 89 | public List<String> getColumnNames() {
|
| ... | ... | @@ -601,6 +601,11 @@ public class ReportModel extends AdminActionModel { |
| 601 | 601 | }
|
| 602 | 602 | }
|
| 603 | 603 | |
| 604 | + public HtmlExportModel toJsonModel() {
|
|
| 605 | + |
|
| 606 | + return new HtmlExportModel(getClientConfig().getGsonSupplier().get(), getSelectedReport(), selectDataModel, getResultModel().getColumnNames(), getResultModel().getRowNames(), getResultModel().getData(), getResultModel().isWithColumnHeader(), getResultModel().isWithRowHeader());
|
|
| 607 | + }
|
|
| 608 | + |
|
| 604 | 609 | private Map<String, FilterableComboBox<?>> initVariableComponents(ReportUI ui, Report report) {
|
| 605 | 610 | Map<String, FilterableComboBox<?>> result = new TreeMap<>();
|
| 606 | 611 | JPanel variablesPanel = ui.getReportVariableSelectorPanel();
|
| ... | ... | @@ -236,6 +236,26 @@ public class ResultTableModel extends AbstractTableModel { |
| 236 | 236 | return data == null ? null : data.getValue(columnIndex, rowIndex);
|
| 237 | 237 | }
|
| 238 | 238 | |
| 239 | + public List<String> getColumnNames() {
|
|
| 240 | + return columnNames;
|
|
| 241 | + }
|
|
| 242 | + |
|
| 243 | + public List<String> getRowNames() {
|
|
| 244 | + return rowNames;
|
|
| 245 | + }
|
|
| 246 | + |
|
| 247 | + public DataMatrix getData() {
|
|
| 248 | + return data;
|
|
| 249 | + }
|
|
| 250 | + |
|
| 251 | + public boolean isWithColumnHeader() {
|
|
| 252 | + return withColumnHeader;
|
|
| 253 | + }
|
|
| 254 | + |
|
| 255 | + public boolean isWithRowHeader() {
|
|
| 256 | + return withRowHeader;
|
|
| 257 | + }
|
|
| 258 | + |
|
| 239 | 259 | public String getClipboardContent(boolean copyRowHeaders, boolean copyColumnHeaders) {
|
| 240 | 260 | return getDataContent(copyRowHeaders, copyColumnHeaders, true, '\t');
|
| 241 | 261 | }
|
| ... | ... | @@ -264,8 +284,4 @@ public class ResultTableModel extends AbstractTableModel { |
| 264 | 284 | result += data.getClipboardContent(copyRowHeaders || !withRowHeader, true, escapeCell, separator);
|
| 265 | 285 | return result;
|
| 266 | 286 | }
|
| 267 | - |
|
| 268 | - public ResultJsonModel toJsonModel() {
|
|
| 269 | - return new ResultJsonModel(columnNames, rowNames, data, withColumnHeader, withRowHeader);
|
|
| 270 | - }
|
|
| 271 | 287 | } |
| ... | ... | @@ -22,13 +22,12 @@ package fr.ird.observe.client.datasource.actions.report.actions; |
| 22 | 22 | * #L%
|
| 23 | 23 | */
|
| 24 | 24 | |
| 25 | -import com.google.common.base.Charsets;
|
|
| 26 | -import com.google.common.io.Resources;
|
|
| 27 | 25 | import fr.ird.observe.client.datasource.actions.ObserveKeyStrokesActions;
|
| 28 | 26 | import fr.ird.observe.client.datasource.actions.actions.AdminTabUIActionSupport;
|
| 27 | +import fr.ird.observe.client.datasource.actions.report.HtmlExportModel;
|
|
| 28 | +import fr.ird.observe.client.datasource.actions.report.HtmlExportModelTemplate;
|
|
| 29 | 29 | import fr.ird.observe.client.datasource.actions.report.ReportModel;
|
| 30 | 30 | import fr.ird.observe.client.datasource.actions.report.ReportUI;
|
| 31 | -import fr.ird.observe.client.datasource.actions.report.ResultJsonModel;
|
|
| 32 | 31 | import fr.ird.observe.client.util.UIHelper;
|
| 33 | 32 | import org.apache.logging.log4j.LogManager;
|
| 34 | 33 | import org.apache.logging.log4j.Logger;
|
| ... | ... | @@ -79,20 +78,14 @@ public class ExportToHtml extends AdminTabUIActionSupport<ReportUI> { |
| 79 | 78 | // generate data file
|
| 80 | 79 | Path dataFile = exportDirectory.resolve("data.json");
|
| 81 | 80 | log.info("Generate data file for report ({}) at {}", reportId, dataFile);
|
| 82 | - ResultJsonModel dataModel = stepModel.getResultModel().toJsonModel();
|
|
| 83 | - String dataContent = getClientConfig().getGsonSupplier().get().toJson(dataModel);
|
|
| 81 | + HtmlExportModel dataModel = stepModel.toJsonModel();
|
|
| 82 | + String dataContent = dataModel.getJson();
|
|
| 84 | 83 | Files.writeString(dataFile, dataContent);
|
| 85 | 84 | |
| 86 | -// // generate javascript file
|
|
| 87 | -// Path jsFile = exportDirectory.resolve("report.js");
|
|
| 88 | -// log.info("Generate js file for report ({}) at {}", reportId, jsFile);
|
|
| 89 | -// String jsContent = Resources.asCharSource(Resources.getResource("report/html/default/report.js"), Charsets.UTF_8).read();
|
|
| 90 | -// Files.writeString(jsFile, jsContent);
|
|
| 91 | - |
|
| 92 | 85 | // generate html file
|
| 93 | 86 | Path htmlFile = exportDirectory.resolve("report.html");
|
| 94 | 87 | log.info("Generate html file for report ({}) at {}", reportId, htmlFile);
|
| 95 | - String fileContent = Resources.asCharSource(Resources.getResource("report/html/default/report.html"), Charsets.UTF_8).read().replace("${json}", dataContent);
|
|
| 88 | + String fileContent = HtmlExportModelTemplate.generate(dataModel);
|
|
| 96 | 89 | Files.writeString(htmlFile, fileContent);
|
| 97 | 90 | |
| 98 | 91 | // open html file
|