Tony CHEMIT pushed to branch develop at ultreiaio / ird-observe Commits: ba11dbb3 by Tony Chemit at 2023-12-05T11:39:23+01:00 Improve report html export - final touch - Closes #2811 Add DataMatrix gson adapter (to reduce format) introduce ColumnRendererConsumer and ColumnRendererParameters API (add gson adapter) - - - - - - 22 changed files: - 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/HtmlExportModel.java - + client/datasource/actions/src/main/resources/fr/ird/observe/client/datasource/actions/report/HtmlExportModel.js - core/persistence/report/src/main/resources/observe-reports.properties - core/services/test/src/main/java/fr/ird/observe/services/service/ReportServiceFixtures.java - + src/site/markdown/report/embedded-column-renderers.md - src/site/markdown/report/syntax.md - toolkit/api-report/pom.xml - toolkit/api-report/src/main/java/fr/ird/observe/report/ColumnRendererConsumer.java - toolkit/api-report/src/main/java/fr/ird/observe/report/ColumnRendererConsumers.java - toolkit/api-report/src/main/java/fr/ird/observe/report/ReportColumnRenderersParameters.java - + toolkit/api-report/src/main/java/fr/ird/observe/report/json/ReportColumnRenderersParametersAdapter.java - toolkit/api-report/src/main/java/fr/ird/observe/report/renderers/HighlightIfAbsoluteDeltaIsPositive.java - toolkit/api-report/src/main/java/fr/ird/observe/report/renderers/HighlightIfNotI18nReferentialValue.java → toolkit/api-report/src/main/java/fr/ird/observe/report/renderers/HighlightIfEquals18nReferentialValue.java - toolkit/api-report/src/main/java/fr/ird/observe/report/renderers/HighlightIfNumericalValueIsPositive.java - toolkit/api-report/src/main/resources/fr/ird/observe/report/renderers/HighlightIfAbsoluteDeltaIsPositive.js - toolkit/api-report/src/main/resources/fr/ird/observe/report/renderers/HighlightIfNotI18nReferentialValue.js → toolkit/api-report/src/main/resources/fr/ird/observe/report/renderers/HighlightIfEquals18nReferentialValue.js - toolkit/api-report/src/main/resources/fr/ird/observe/report/renderers/HighlightIfNumericalValueIsPositive.js - toolkit/api/src/main/java/fr/ird/observe/dto/ObserveUtil.java - + toolkit/api/src/main/java/fr/ird/observe/spi/json/java4all/DataMatrixAdapter.java Changes: ===================================== client/datasource/actions/src/main/i18n/templates/reportHtmlExport_en_GB.ftl ===================================== @@ -199,95 +199,11 @@ } </style> <script type="application/javascript"> - - function searchValue() { - return searchOption.checked; - } - - function resizableValue() { - return resizableOption.checked; - } - - function sortValue() { - return sortOption.checked; - } - - function paginationValue() { - return paginationOption.checked ? {limit: paginationSizeOption.value} : false; - } - - function toggleSearch(config, source) { - let newValue = source.checked; - // console.info("Toggle search to: " + newValue); - updateGrid(config); - } - - function toggleResizable(config, source) { - let newValue = source.checked; - // console.info("Toggle resizable to: " + newValue); - updateGrid(config); - } - - function toggleSort(config, source) { - let newValue = source.checked; - // console.info("Toggle sort to: " + newValue); - updateGrid(config); - } - - function togglePagination(config, source) { - let newValue = source.checked; - // console.info("Toggle pagination to: " + newValue); - if (newValue) { - paginationSizeOption["disabled"] = null; - } else { - paginationSizeOption.disabled = true; - } - updateGrid(config); - } - - function changePaginationSize(config, source) { - let newValue = source.value; - // console.info("Change pagination size to: " + newValue); - updateGrid(config); - } - - function updateGrid(config) { - let searchValue1 = searchValue(); - let sortValue1 = sortValue(); - let resizableValue1 = resizableValue(); - // FIXME Need to update config.columns otherwise we will keep previous options (sort, resizable...) - let newConfig = { - language: config.language, - data: config.data, - columns: config.columns, - search: searchValue1, - resizable: resizableValue1, - sort: sortValue1, - pagination: paginationValue() - }; - gridContainerParent.innerHTML = '<div id="wrapper"></div>'; - // console.info(newConfig); - setTimeout(() => { - <#--noinspection JSUnresolvedReference--> - grid = new gridjs.Grid(newConfig).render(document.getElementById("wrapper")); - }, 50); - } + ${.data_model.script} <#list .data_model.columnRendererFunctions as key> -${key}</#list> - - function createColumns(json) { - let result = !!json["columnNames"] ? json["columnNames"] : []; - if (result.length === 0) { - return result; - } - let renderers = json["columnRendererDefinitions"]; - let i = 0; - let data = json.data; - <#list .data_model.columnRendererInitCode as value> - ${value}</#list> - return result; - } + ${key} + </#list> </script> </head> <body> @@ -330,11 +246,12 @@ ${key}</#list> <div class="widget-list" id="tab-result"> <div class="config-panel"> - <label><input id="search" type="checkbox" checked/> Search</label>| - <label><input id="resizable" type="checkbox" checked/> Resizable columns</label>| - <label><input id="sort" type="checkbox" checked/> Sort</label>| + <label><input id="search" type="checkbox"/> Search</label>| + <label><input id="resizable" type="checkbox"/> Resizable columns</label>| + <label><input id="sort" type="checkbox"/> Sort</label>| <label><input id="pagination" type="checkbox"/> Pagination</label>| - <label>Page size <input id="paginationSize" type="number" disabled value="20"/></label> + <label>Page size <input id="paginationSize" type="number"/></label>| + <label>Result count: <b><span id="rowCount" style="font-style: italic"></span></b></label> </div> <div id="wrapperParent"> <div id="wrapper"></div> @@ -347,61 +264,20 @@ ${key}</#list> </div> <script type="application/javascript"> - const json = ${.data_model.json}; - - const gridContainerParent = document.getElementById("wrapperParent"); - const searchOption = document.getElementById("search"); - const resizableOption = document.getElementById("resizable"); - const paginationOption = document.getElementById("pagination"); - const paginationSizeOption = document.getElementById("paginationSize"); - const sortOption = document.getElementById("sort"); - - let grid = new gridjs.Grid({ - search: searchValue(), - resizable: resizableValue(), - sort: sortValue(), - pagination: paginationValue(), - <#--language: {--> - <#-- 'search': {--> - <#-- 'placeholder': '🔍 Recherche...'--> - <#-- },--> - <#-- sort: {--> - <#-- sortAsc: 'Tri ascendant',--> - <#-- sortDesc: 'Tri descendant',--> - <#-- },--> - <#-- pagination: {--> - <#-- previous: 'Précédent',--> - <#-- next: 'Suivant',--> - <#-- navigate: (page, pages) => `Page ${r"${page}"} sur ${r"${pages}"}`,--> - <#-- page: (page) => `Page ${r"${page}"}`,--> - <#-- showing: 'Affichage des lignes de',--> - <#-- of: 'sur',--> - <#-- to: 'à',--> - <#-- results: 'lignes.',--> - <#-- },--> - <#-- loading: 'Chargement...'--> - <#--},--> - columns: createColumns(json), - data: json.data.data - }); - updateGrid(grid.config); - - searchOption.addEventListener("change", function () { - toggleSearch(grid.config, this); - }); - resizableOption.addEventListener("change", function () { - toggleResizable(grid.config, this); - }); - sortOption.addEventListener("change", function () { - toggleSort(grid.config, this); - }); - paginationOption.addEventListener("change", function () { - togglePagination(grid.config, this); - }); - - paginationSizeOption.addEventListener("change", function () { - changePaginationSize(grid.config, this); - }); + new GridHandler( + document, + {}, + function (json) { + let result = !!json["columnNames"] ? json["columnNames"] : []; + if (result.length === 0) { + return result; + } + let data = json.data; + <#list .data_model.columnRendererInitCode as value> + ${value}</#list> + return result; + }, + ${.data_model.json}).init(); </script> </body> </html> ===================================== client/datasource/actions/src/main/i18n/templates/reportHtmlExport_es_ES.ftl ===================================== @@ -199,95 +199,11 @@ } </style> <script type="application/javascript"> - - function searchValue() { - return searchOption.checked; - } - - function resizableValue() { - return resizableOption.checked; - } - - function sortValue() { - return sortOption.checked; - } - - function paginationValue() { - return paginationOption.checked ? {limit: paginationSizeOption.value} : false; - } - - function toggleSearch(config, source) { - let newValue = source.checked; - // console.info("Toggle search to: " + newValue); - updateGrid(config); - } - - function toggleResizable(config, source) { - let newValue = source.checked; - // console.info("Toggle resizable to: " + newValue); - updateGrid(config); - } - - function toggleSort(config, source) { - let newValue = source.checked; - // console.info("Toggle sort to: " + newValue); - updateGrid(config); - } - - function togglePagination(config, source) { - let newValue = source.checked; - // console.info("Toggle pagination to: " + newValue); - if (newValue) { - paginationSizeOption["disabled"] = null; - } else { - paginationSizeOption.disabled = true; - } - updateGrid(config); - } - - function changePaginationSize(config, source) { - let newValue = source.value; - // console.info("Change pagination size to: " + newValue); - updateGrid(config); - } - - function updateGrid(config) { - let searchValue1 = searchValue(); - let sortValue1 = sortValue(); - let resizableValue1 = resizableValue(); - // FIXME Need to update config.columns otherwise we will keep previous options (sort, resizable...) - let newConfig = { - language: config.language, - data: config.data, - columns: config.columns, - search: searchValue1, - resizable: resizableValue1, - sort: sortValue1, - pagination: paginationValue() - }; - gridContainerParent.innerHTML = '<div id="wrapper"></div>'; - // console.info(newConfig); - setTimeout(() => { - <#--noinspection JSUnresolvedReference--> - grid = new gridjs.Grid(newConfig).render(document.getElementById("wrapper")); - }, 50); - } + ${.data_model.script} <#list .data_model.columnRendererFunctions as key> -${key}</#list> - - function createColumns(json) { - let result = !!json["columnNames"] ? json["columnNames"] : []; - if (result.length === 0) { - return result; - } - let renderers = json["columnRendererDefinitions"]; - let i = 0; - let data = json.data; - <#list .data_model.columnRendererInitCode as value> - ${value}</#list> - return result; - } + ${key} + </#list> </script> </head> <body> @@ -329,11 +245,12 @@ ${key}</#list> </div> <div class="widget-list" id="tab-result"> <div class="config-panel"> - <label><input id="search" type="checkbox" checked/> Search</label>| - <label><input id="resizable" type="checkbox" checked/> Resizable columns</label>| - <label><input id="sort" type="checkbox" checked/> Sort</label>| + <label><input id="search" type="checkbox"/> Search</label>| + <label><input id="resizable" type="checkbox"/> Resizable columns</label>| + <label><input id="sort" type="checkbox"/> Sort</label>| <label><input id="pagination" type="checkbox"/> Pagination</label>| - <label>Page size <input id="paginationSize" type="number" disabled value="20"/></label> + <label>Page size <input id="paginationSize" type="number"/></label>| + <label>Result count: <b><span id="rowCount" style="font-style: italic"></span></b></label> </div> <div id="wrapperParent"> <div id="wrapper"></div> @@ -346,61 +263,20 @@ ${key}</#list> </div> <script type="application/javascript"> - const json = ${.data_model.json}; - - const gridContainerParent = document.getElementById("wrapperParent"); - const searchOption = document.getElementById("search"); - const resizableOption = document.getElementById("resizable"); - const paginationOption = document.getElementById("pagination"); - const paginationSizeOption = document.getElementById("paginationSize"); - const sortOption = document.getElementById("sort"); - - let grid = new gridjs.Grid({ - search: searchValue(), - resizable: resizableValue(), - sort: sortValue(), - pagination: paginationValue(), - <#--language: {--> - <#-- 'search': {--> - <#-- 'placeholder': '🔍 Recherche...'--> - <#-- },--> - <#-- sort: {--> - <#-- sortAsc: 'Tri ascendant',--> - <#-- sortDesc: 'Tri descendant',--> - <#-- },--> - <#-- pagination: {--> - <#-- previous: 'Précédent',--> - <#-- next: 'Suivant',--> - <#-- navigate: (page, pages) => `Page ${r"${page}"} sur ${r"${pages}"}`,--> - <#-- page: (page) => `Page ${r"${page}"}`,--> - <#-- showing: 'Affichage des lignes de',--> - <#-- of: 'sur',--> - <#-- to: 'à',--> - <#-- results: 'lignes.',--> - <#-- },--> - <#-- loading: 'Chargement...'--> - <#--},--> - columns: createColumns(json), - data: json.data.data - }); - updateGrid(grid.config); - - searchOption.addEventListener("change", function () { - toggleSearch(grid.config, this); - }); - resizableOption.addEventListener("change", function () { - toggleResizable(grid.config, this); - }); - sortOption.addEventListener("change", function () { - toggleSort(grid.config, this); - }); - paginationOption.addEventListener("change", function () { - togglePagination(grid.config, this); - }); - - paginationSizeOption.addEventListener("change", function () { - changePaginationSize(grid.config, this); - }); + new GridHandler( + document, + {}, + function (json) { + let result = !!json["columnNames"] ? json["columnNames"] : []; + if (result.length === 0) { + return result; + } + let data = json.data; + <#list .data_model.columnRendererInitCode as value> + ${value}</#list> + return result; + }, + ${.data_model.json}).init(); </script> </body> </html> ===================================== client/datasource/actions/src/main/i18n/templates/reportHtmlExport_fr_FR.ftl ===================================== @@ -200,95 +200,11 @@ } </style> <script type="application/javascript"> - - function searchValue() { - return searchOption.checked; - } - - function resizableValue() { - return resizableOption.checked; - } - - function sortValue() { - return sortOption.checked; - } - - function paginationValue() { - return paginationOption.checked ? {limit: paginationSizeOption.value} : false; - } - - function toggleSearch(config, source) { - let newValue = source.checked; - // console.info("Toggle search to: " + newValue); - updateGrid(config); - } - - function toggleResizable(config, source) { - let newValue = source.checked; - // console.info("Toggle resizable to: " + newValue); - updateGrid(config); - } - - function toggleSort(config, source) { - let newValue = source.checked; - // console.info("Toggle sort to: " + newValue); - updateGrid(config); - } - - function togglePagination(config, source) { - let newValue = source.checked; - // console.info("Toggle pagination to: " + newValue); - if (newValue) { - paginationSizeOption["disabled"] = null; - } else { - paginationSizeOption.disabled = true; - } - updateGrid(config); - } - - function changePaginationSize(config, source) { - let newValue = source.value; - // console.info("Change pagination size to: " + newValue); - updateGrid(config); - } - - function updateGrid(config) { - let searchValue1 = searchValue(); - let sortValue1 = sortValue(); - let resizableValue1 = resizableValue(); - // FIXME Need to update config.columns otherwise we will keep previous options (sort, resizable...) - let newConfig = { - language: config.language, - data: config.data, - columns: config.columns, - search: searchValue1, - resizable: resizableValue1, - sort: sortValue1, - pagination: paginationValue() - }; - gridContainerParent.innerHTML = '<div id="wrapper"></div>'; - // console.info(newConfig); - setTimeout(() => { - <#--noinspection JSUnresolvedReference--> - grid = new gridjs.Grid(newConfig).render(document.getElementById("wrapper")); - }, 50); - } + ${.data_model.script} <#list .data_model.columnRendererFunctions as key> -${key}</#list> - - function createColumns(json) { - let result = !!json["columnNames"] ? json["columnNames"] : []; - if (result.length === 0) { - return result; - } - let renderers = json["columnRendererDefinitions"]; - let i = 0; - let data = json.data; - <#list .data_model.columnRendererInitCode as value> - ${value}</#list> - return result; - } + ${key} + </#list> </script> </head> <body> @@ -330,11 +246,13 @@ ${key}</#list> </div> <div class="widget-list" id="tab-result"> <div class="config-panel"> - <label><input id="search" type="checkbox" checked/> Recherche</label>| - <label><input id="resizable" type="checkbox" checked/> Colonnes redimensionnables</label>| - <label><input id="sort" type="checkbox" checked/> Tri des colonnes</label>| + <label><input id="search" type="checkbox"/> Recherche</label>| + <label><input id="resizable" type="checkbox"/> Colonnes redimensionnables</label>| + <label><input id="sort" type="checkbox"/> Tri des colonnes</label>| <label><input id="pagination" type="checkbox"/> Pagination</label>| - <label>Nombre de lignes par page <input id="paginationSize" type="number" disabled value="20"/></label> + <label>Nombre de lignes par page <input id="paginationSize" type="number"/></label>| + <label>Nombre de résultat(s) : <b><span id="rowCount" style="font-style: italic"></span></b></label> + </div> <div id="wrapperParent"> <div id="wrapper"></div> @@ -347,21 +265,9 @@ ${key}</#list> </div> <script type="application/javascript"> - const json = ${.data_model.json}; - - const gridContainerParent = document.getElementById("wrapperParent"); - const searchOption = document.getElementById("search"); - const resizableOption = document.getElementById("resizable"); - const paginationOption = document.getElementById("pagination"); - const paginationSizeOption = document.getElementById("paginationSize"); - const sortOption = document.getElementById("sort"); - - let grid = new gridjs.Grid({ - search: searchValue(), - resizable: resizableValue(), - sort: sortValue(), - pagination: paginationValue(), - language: { + new GridHandler( + document, + { 'search': { 'placeholder': '🔍 Recherche...' }, @@ -381,27 +287,17 @@ ${key}</#list> }, loading: 'Chargement...' }, - columns: createColumns(json), - data: json.data.data - }); - updateGrid(grid.config); - - searchOption.addEventListener("change", function () { - toggleSearch(grid.config, this); - }); - resizableOption.addEventListener("change", function () { - toggleResizable(grid.config, this); - }); - sortOption.addEventListener("change", function () { - toggleSort(grid.config, this); - }); - paginationOption.addEventListener("change", function () { - togglePagination(grid.config, this); - }); - - paginationSizeOption.addEventListener("change", function () { - changePaginationSize(grid.config, this); - }); + function (json) { + let result = !!json["columnNames"] ? json["columnNames"] : []; + if (result.length === 0) { + return result; + } + let data = json.data; + <#list .data_model.columnRendererInitCode as value> + ${value}</#list> + return result; + }, + ${.data_model.json}).init(); </script> </body> </html> ===================================== client/datasource/actions/src/main/java/fr/ird/observe/client/datasource/actions/report/HtmlExportModel.java ===================================== @@ -24,14 +24,17 @@ package fr.ird.observe.client.datasource.actions.report; import com.google.gson.Gson; import fr.ird.observe.client.datasource.actions.config.SelectDataModel; +import fr.ird.observe.dto.ObserveUtil; import fr.ird.observe.report.ColumnRendererConsumers; import fr.ird.observe.report.Report; import fr.ird.observe.report.ReportColumnRenderersParameters; -import fr.ird.observe.report.definition.ColumnRendererDefinition; import io.ultreia.java4all.application.template.spi.GenerateTemplate; +import io.ultreia.java4all.util.SingletonSupplier; import io.ultreia.java4all.util.matrix.DataMatrix; +import java.net.URL; import java.util.List; +import java.util.Objects; import java.util.Set; /** @@ -44,6 +47,7 @@ import java.util.Set; */ @GenerateTemplate(template = "reportHtmlExport.ftl") public class HtmlExportModel { + private static SingletonSupplier<String> SCRIPT_CONTENT; /** * Selected report. */ @@ -52,16 +56,14 @@ public class HtmlExportModel { * Selected data model. */ private final transient SelectDataModel selectDataModel; - + private final transient Set<String> columnRendererFunctions; + private final transient List<String> columnRendererInitCode; private final List<String> columnNames; private final List<String> rowNames; private final DataMatrix data; private final boolean withColumnHeader; private final boolean withRowHeader; private final transient String json; - private final List<Object> columnRendererDefinitions; - private final transient Set<String> columnRendererFunctions; - private final transient List<String> columnRendererInitCode; public HtmlExportModel(Gson gson, Report selectedReport, @@ -80,24 +82,26 @@ public class HtmlExportModel { this.withColumnHeader = withColumnHeader; this.withRowHeader = withRowHeader; if (reportColumnRenderersParameters != null) { - this.columnRendererDefinitions = reportColumnRenderersParameters.consumeColumnRenderersHtml(); - ColumnRendererDefinition[] columnRenderers = selectedReport.definition().getColumnRenderers(); - this.columnRendererFunctions = ColumnRendererConsumers.htmlFunctions(columnRenderers); - this.columnRendererInitCode = ColumnRendererConsumers.htmlInitCode(columnRenderers); + this.columnRendererFunctions = ColumnRendererConsumers.htmlFunctions(reportColumnRenderersParameters); + this.columnRendererInitCode = ColumnRendererConsumers.htmlInitCode(reportColumnRenderersParameters); } else { - this.columnRendererDefinitions = List.of(); this.columnRendererFunctions = Set.of(); this.columnRendererInitCode = List.of(); } this.json = gson.toJson(this); } - public String getJson() { - return json; + public String getScript() { + if (SCRIPT_CONTENT == null) { + String resourceName = getClass().getSimpleName() + ".js"; + URL url = Objects.requireNonNull(getClass().getResource(resourceName), "Could not find resource: " + resourceName); + SCRIPT_CONTENT = ObserveUtil.loadResourceContentSupplier(url, ObserveUtil.removeJavaLicense()); + } + return SCRIPT_CONTENT.get(); } - public List<Object> getColumnRendererDefinitions() { - return columnRendererDefinitions; + public String getJson() { + return json; } public Set<String> getColumnRendererFunctions() { ===================================== client/datasource/actions/src/main/resources/fr/ird/observe/client/datasource/actions/report/HtmlExportModel.js ===================================== @@ -0,0 +1,192 @@ +/*- + * #%L + * ObServe Client :: DataSource :: Actions + * %% + * Copyright (C) 2008 - 2023 IRD, Ultreia.io + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +class GridHandler { + /** + * Where to store configuration of the grid. + */ + config; + rowCount; + searchOption; + resizableOption; + paginationOption; + paginationSizeOption; + sortOption; + gridContainerParent; + + constructor(document, language, columnsSupplier, json) { + this.gridContainerParent = document.getElementById("wrapperParent"); + this.searchOption = document.getElementById("search"); + this.resizableOption = document.getElementById("resizable"); + this.paginationOption = document.getElementById("pagination"); + this.paginationSizeOption = document.getElementById("paginationSize"); + this.sortOption = document.getElementById("sort"); + json.data.data = this.deserializeJson(json.data); + + this.rowCount = json.data['height']; + this.searchOption['checked'] = true; + document.getElementById("rowCount").innerHTML = this.rowCount; + this.resizableOption['checked'] = true; + this.sortOption['checked'] = false; + this.paginationSizeOption.value = 20; + if (this.rowCount > 200) { + this.paginationOption['checked'] = true; + } else { + this.paginationSizeOption.disabled = true; + } + this.config = { + language: language, + columns: columnsSupplier(json), + data: json.data.data, + search: this.searchValue(), + resizable: this.resizableValue(), + sort: this.sortValue(), + pagination: this.paginationValue() + } + } + + updateGrid() { + this.gridContainerParent.innerHTML = '<div id="wrapper"></div>'; + setTimeout(() => { + // noinspection JSUnresolvedReference + new gridjs.Grid(this.toGridConfig()).render(document.getElementById("wrapper")); + }, 50); + } + + toGridConfig() { + return { + language: this.config.language, + data: this.config.data, + columns: this.deepCopyColumns(this.config.columns), + search: this.config.search, + resizable: this.config.resizable, + sort: this.config.sort, + pagination: this.config.pagination, + } + } + + deepCopyColumns(columns) { + let result = []; + let index = 0; + for (const column of columns) { + if (column instanceof Object) { + let newColumn = {}; + newColumn['name'] = column['name']; + if (!!!column['formatter'] != null) { + newColumn['formatter'] = column['formatter']; + } + if (column['attributes'] != null) { + newColumn['attributes'] = column['attributes']; + } + result[index++] = newColumn; + } else { + result[index++] = column; + } + } + return result; + } + + deserializeJson(json) { + let height = json.height; + let width = json.width; + let data = json.rows; + let result = new Array(height); + for (let row = 0; row < height; row++) { + let cells = data[row].split('||'); + let realRow = new Array(width); + result[row] = realRow; + for (let column = 0; column < width; column++) { + let rowElement = cells[column]; + realRow[column] = rowElement === '$' ? null : rowElement; + } + } + return result; + } + + searchValue() { + return this.searchOption.checked; + } + + resizableValue() { + return this.resizableOption.checked; + } + + sortValue() { + return this.sortOption.checked; + } + + paginationValue() { + return this.paginationOption.checked ? {limit: this.paginationSizeOption.value} : false; + } + + toggleSearch(source) { + this.config.search = this.searchValue(); + this.updateGrid(); + } + + toggleResizable(source) { + this.config.resizable = this.resizableValue(); + this.updateGrid(); + } + + toggleSort(source) { + this.config.sort = this.sortValue(); + this.updateGrid(); + } + + togglePagination(source) { + let newValue = source.checked; + if (newValue) { + this.paginationSizeOption["disabled"] = null; + } else { + this.paginationSizeOption.disabled = true; + } + this.config.pagination = this.paginationValue(); + this.updateGrid(); + } + + changePaginationSize(source) { + this.config.pagination = this.paginationValue(); + this.updateGrid(); + } + + init() { + + this.updateGrid(); + let that = this; + this.searchOption.addEventListener("change", function () { + that.toggleSearch(this); + }); + this.resizableOption.addEventListener("change", function () { + that.toggleResizable(this); + }); + this.sortOption.addEventListener("change", function () { + that.toggleSort(this); + }); + this.paginationOption.addEventListener("change", function () { + that.togglePagination(this); + }); + + this.paginationSizeOption.addEventListener("change", function () { + that.changePaginationSize(this); + }); + } +} ===================================== core/persistence/report/src/main/resources/observe-reports.properties ===================================== @@ -888,8 +888,8 @@ Left Join t.localMarketSurveySamplingAcquisitionStatus localMarketSurveySampling Left Join t.advancedSamplingAcquisitionStatus advancedSamplingAcquisitionStatus \ Where t.id In :tripId \ Order By vessel.code,t.startDate,t.endDate -report.psLogbookTrip.columnRenderers.1.type=HighlightIfNotI18nReferentialValue -report.psLogbookTrip.columnRenderers.1.parameters=14,15,16,17,18,19,20,21|fr.ird.referential.ps.common.AcquisitionStatus#1464000000000#001 +report.psLogbookTrip.columnRenderers.1.type=HighlightIfEquals18nReferentialValue +report.psLogbookTrip.columnRenderers.1.parameters=14,15,16,17,18,19,20,21|fr.ird.referential.ps.common.AcquisitionStatus#1464000000000#999 report.psLogbookTrip.columnRenderers.2.type=HighlightIfAbsoluteDeltaIsPositive report.psLogbookTrip.columnRenderers.2.parameters=10|11|0.0001|0.5 report.psLogbookTrip.columnRenderers.3.type=HighlightIfAbsoluteDeltaIsPositive ===================================== core/services/test/src/main/java/fr/ird/observe/services/service/ReportServiceFixtures.java ===================================== @@ -127,11 +127,9 @@ public class ReportServiceFixtures extends GeneratedReportServiceFixtures { if (definition.needInitColumnRendererParameters()) { ReportColumnRenderersParameters renderersParameters = service.initColumnRendererParameters(definition.definition()); Assert.assertNotNull(renderersParameters); - List<Object> consumeColumnRenderersHtml = renderersParameters.consumeColumnRenderersHtml(); - Assert.assertNotNull(consumeColumnRenderersHtml); - Set<String> columnRendererFunctions = ColumnRendererConsumers.htmlFunctions(definition.definition().getColumnRenderers()); + Set<String> columnRendererFunctions = ColumnRendererConsumers.htmlFunctions(renderersParameters); Assert.assertNotNull(columnRendererFunctions); - List<String> columnRendererInitCode = ColumnRendererConsumers.htmlInitCode(definition.definition().getColumnRenderers()); + List<String> columnRendererInitCode = ColumnRendererConsumers.htmlInitCode(renderersParameters); Assert.assertNotNull(columnRendererInitCode); } Report report = reportFixture.populateVariables(service, definition); ===================================== src/site/markdown/report/embedded-column-renderers.md ===================================== @@ -0,0 +1,61 @@ +# Documentation des règles de rendu de colonnes disponibles + +Ce document décrit toutes les règles de rendu de colonnes disponibles sur les rapports. + +### HighlightIfAbsoluteDeltaIsPositive + +Ce rendu permet de vérifier que les valeurs de chaque cellule de deux colonnes sont identiques. + +Si deux valeurs ne sont pas égales, on colorise la cellule en orange (avertissement) pour un premier seuil et en rouge +(erreur) selon un second seuil. + +Le rendu nécessite quatre paramètres : + +* la première colonne +* la seconde colonne +* le seuil pour afficher des avertissements +* le seuil pour afficher des erreurs + +Exemple d'utilisation : + +```properties +report.xxx.columnRenderers.1.type=HighlightIfAbsoluteDeltaIsPositive +report.ccc.columnRenderers.1.parameters=10|11|0.0001|0.5 +``` + +### HighlightIfNumericalValueIsPositive + +Ce rendu permet de vérifier que les valeurs de chaque celleule d'une colonne n'est pas zéro. + +Si la valeur n'est pas zéro, on colorise la cellule en orange (avertissement) pour un premier seuil et en rouge +(erreur) selon un second seuil. + +Le rendu nécessite quatre paramètres : + +* la colonne +* le seuil pour afficher des avertissements +* le seuil pour afficher des erreurs + +Exemple d'utilisation + +```properties +report.xxx.columnRenderers.1.type=HighlightIfNumericalValueIsPositive +report.xxx.columnRenderers.1.parameters=19|0.0001|0.5 +``` + +### HighlightIfEquals18nReferentialValue + +Ce rendu permet d'afficher de coloriser en rouge toute cellule des colonnes sélectionnées dont la valeur (de type +référentiel i18n) n'est pas celle spécifié (via son identifiant). + +Le rendu nécessite deux paramètres : + +* une liste de colonnes sépararées par des virgules +* l'identifiant du référentiel à mettre en valeur + +Exemple d'utilisation : + +```properties +report.xxx.columnRenderers.1.type=HighlightIfEquals18nReferentialValue +report.xxx.columnRenderers.1.parameters=14,15,16,17,18,19,20,21|fr.ird.referential.ps.common.AcquisitionStatus#1464000000000#999 +``` ===================================== src/site/markdown/report/syntax.md ===================================== @@ -4,13 +4,14 @@ Ce document décrit comment écrire un rapport. ## Syntaxe d'un rapport -Comme écrit dans la page de présentation des rapports, ceux-ci sont décrits dans un fichier de type *properties*. +Comme écrit dans la page de présentation des rapports, ceux-ci sont décrits dans un fichier de type *properties*. La syntaxe a été uniformisée en version 9. ### Identifiant d'un rapport -Chaque rapport possède un identifiant unique; celui-ci est utilisé pour identifier toute sa description dans le fichier global. +Chaque rapport possède un identifiant unique; celui-ci est utilisé pour identifier toute sa description dans le fichier +global. Voici un premier exemple de rapport pour mieux comprendre comment cela s'organise : @@ -114,7 +115,8 @@ Order By sg.code ``` ```report.xxx.variable.1.name``` définit l'*alias* de la variable que l'on pourra ensuite utiliser dans le -reste de la définition du rapport (par exemple dans d'autres variables, variables de répétition, requêtes ou opérations). +reste de la définition du rapport (par exemple dans d'autres variables, variables de répétition, requêtes ou +opérations). ```report.xxx.variable.1.type``` définit le type de la variable. Ce type doit être un type de *dto* ou une *référence de dto*. On ne peut pas ici utiliser un type d'*entité*, puisque l'interface graphique (selon le principe de @@ -175,9 +177,12 @@ Where t.id In :tripId \ Order By sg.code ``` -Cet exemple est intéressant car il nous permet de voir comment utiliser un type qui ne reflète pas exactement une entité. +Cet exemple est intéressant car il nous permet de voir comment utiliser un type qui ne reflète pas exactement une +entité. + +Ici **SpeciesFateDiscardModeDto** est un objet que l'on a ajouté dans le code de l'application pour refléter les +différents modes de captures : -Ici **SpeciesFateDiscardModeDto** est un objet que l'on a ajouté dans le code de l'application pour refléter les différents modes de captures : * conservé (sf.discard = ```false```) * rejeté (sf.discard = ```true```) * non connu (sf.discard = ```NULL```) @@ -200,26 +205,33 @@ and g in elements (m.gearUseFeatures) \ Order By g.gear.label2 ``` -```report.xxx.repeatVariable.1.name``` définit l'*alias* de la variable de répétition à utiliser dans une requête, autre variable de répétition ou opération +```report.xxx.repeatVariable.1.name``` définit l'*alias* de la variable de répétition à utiliser dans une requête, autre +variable de répétition ou opération ```report.xxx.repeatVariable.1.type``` définit le type de la variable de répétition. Contrairement aux variables, les -variables de répétition sont utilisées en interne pour exécuter le rapport, on peut donc utiliser directement des types d'*entité*, -ou bien des types *simples* qui correspondent aux types d'une colonne en base (**java.lang.String**, **java.lang.Float**, ...) +variables de répétition sont utilisées en interne pour exécuter le rapport, on peut donc utiliser directement des types +d'*entité*, +ou bien des types *simples* qui correspondent aux types d'une colonne en base (**java.lang.String**, **java.lang.Float +**, ...) -**À noter que contrairement aux variables, ici le type de la donnée sera toujours utilisé tel quel dans les autres requêtes : -si on définit une variable de type *entité*, c'est bien l'entité qui sera injectée dans la requête *hql*, et non pas juste son identifiant technique.** +**À noter que contrairement aux variables, ici le type de la donnée sera toujours utilisé tel quel dans les autres +requêtes : +si on définit une variable de type *entité*, c'est bien l'entité qui sera injectée dans la requête *hql*, et non pas +juste son identifiant technique.** -```report.xxx.repeatVariable.1.request``` définit la requête **hql** pour récupérer l'univers des valeurs de la variable de répétition +```report.xxx.repeatVariable.1.request``` définit la requête **hql** pour récupérer l'univers des valeurs de la variable +de répétition Il est possible d'utiliser dans une requête de variable de répétition, un alias sur toute variable du rapport (dont la variable spéciale **tripId**), ainsi que tout autre alias de variable de répétition. **À noter que l'outil de rapport utilise uniquement l'ordre induit dans la description du rapport, si par exemple la -première variable de répétition était dépendante de la seconde, alors le rapport ne pourra jamais être exécuté puisque la +première variable de répétition était dépendante de la seconde, alors le rapport ne pourra jamais être exécuté puisque +la première variable attend que l'univers de la seconde soit calculée...** -On peut aussi ajouter une ligne supplémentaire pour indiquer que l'on veut rajouter la valeur **nulle** dans -l'univers de cette variable de répétion, ce qui peut etre utilise (et nous nous en servons), mais que nous ne pouvons +On peut aussi ajouter une ligne supplémentaire pour indiquer que l'on veut rajouter la valeur **nulle** dans +l'univers de cette variable de répétion, ce qui peut etre utilise (et nous nous en servons), mais que nous ne pouvons pas décrire via la requete *hql. ```properties @@ -237,7 +249,8 @@ report.xxx.repeatVariable.1.comment=Un commentaire optionnel pour documenter la On distingue deux types de requêtes : * des requêtes *simples* -* des requêtes utilisant une variable de répétition (pour celles-ci on doit décrire en plus alors la variable de répétition à utiliser) +* des requêtes utilisant une variable de répétition (pour celles-ci on doit décrire en plus alors la variable de + répétition à utiliser) Les deux types de requêtes nécessitent les trois lignes, comme décrit dans l'exemple suivant : @@ -252,14 +265,20 @@ From fr.ird.observe.entities.data.ll.common.GearUseFeaturesImpl g \ Where g.id = :gearUseFeaturesId ``` -```report.xxx.request.1.location``` définit la position dans le résultat final où positionner le résultat de cette requête. +```report.xxx.request.1.location``` définit la position dans le résultat final où positionner le résultat de cette +requête. -```report.xxx.request.1.layout``` définit la disposition à utiliser pour placer le résultat de cette requête dans le résultat final. Deux valeurs sont possibles : +```report.xxx.request.1.layout``` définit la disposition à utiliser pour placer le résultat de cette requête dans le +résultat final. Deux valeurs sont possibles : -* **row** pour signifier que le résultat de la requête sera positionné en ligne à partir de la position précedemment définie. Ce mode correspond exactement au resultat de la requête. -* **column** pour signifier que le résultat de la requête sera positionné en colonne à partir de la position précedemment définie. Ce mode est une transposition du résultat de la requête : une ligne du résultat de la requête sera une colonne dans le résultat final +* **row** pour signifier que le résultat de la requête sera positionné en ligne à partir de la position précedemment + définie. Ce mode correspond exactement au resultat de la requête. +* **column** pour signifier que le résultat de la requête sera positionné en colonne à partir de la position + précedemment définie. Ce mode est une transposition du résultat de la requête : une ligne du résultat de la requête + sera une colonne dans le résultat final -```report.xxx.request.1.request``` définit le code **hql** qui permet de construire le résultat à placer ensuite dans le résultat final du rapport +```report.xxx.request.1.request``` définit le code **hql** qui permet de construire le résultat à placer ensuite dans le +résultat final du rapport Pour une requête avec variable de répétition, il faut alors ajouter les deux lignes suivantes : @@ -268,18 +287,24 @@ report.xxx.request.1.repeat.name=gearUseFeaturesId report.xxx.request.1.repeat.layout=column ``` -```report.xxx.request.1.repeat.name``` définit l'*alias* de la variable de répétition à utiliser. La requête sera exécutée +```report.xxx.request.1.repeat.name``` définit l'*alias* de la variable de répétition à utiliser. La requête sera +exécutée autant de fois qu'il y a de valeurs dans l'univers calculé de la variable de répétition. -**À noter qu'il faut alors que le corps de cette requête doit utiliser l'alias de cette variable de répétition, même si dans les faits, rien ne l'oblige, mais le résultat sera alors toujours le même...** +**À noter qu'il faut alors que le corps de cette requête doit utiliser l'alias de cette variable de répétition, même si +dans les faits, rien ne l'oblige, mais le résultat sera alors toujours le même...** -```report.xxx.request.1.repeat.layout``` définit la disposition à utiliser pour constuire le résultat final de la requête appliqué à chaque valeur de la variable de répétition. Deux valeurs sont possibles : +```report.xxx.request.1.repeat.layout``` définit la disposition à utiliser pour constuire le résultat final de la +requête appliqué à chaque valeur de la variable de répétition. Deux valeurs sont possibles : -* **row** pour signifier que pour chaque valeur de l'univers de la variable de répétition, le résultat de la requête sera positionné sur la même ligne; -* **column** pour signifier que pour chaque valeur de l'univers de la variable de répétition, le résultat de la requête sera positionné sur la même colonne. +* **row** pour signifier que pour chaque valeur de l'univers de la variable de répétition, le résultat de la requête + sera positionné sur la même ligne; +* **column** pour signifier que pour chaque valeur de l'univers de la variable de répétition, le résultat de la requête + sera positionné sur la même colonne. **À noter qu'aucune vérification n'est effectuée sur la cohérence entre la disposition de la requête et la disposition -de la variable de répétition. On peut alors obtenir un résultat final de la requête incohérent si les dispositions ne sont pas compatibles.** +de la variable de répétition. On peut alors obtenir un résultat final de la requête incohérent si les dispositions ne +sont pas compatibles.** Il est possible de documenter cette requête en utilisant une ligne optionnelle : @@ -317,7 +342,26 @@ Il est possible de documenter cette opération en utilisant une ligne optionnell report.xxx.operations.1.comment=Un commentaire optionnel pour documenter l'opération ``` -Les opérations disponibles et leur documentation sont décrites dans le document [suivant](./embedded-operations.html). +Les opérations disponibles et leur documentation sont décrites dans le document [suivant](./embedded-operations.html). + +### Rendu de colonnes + +Depuis la version **9.3.0**, il est possible de définir des rendus de colonnes via le fichier de définition, +ce rendu sera valable dans le client swing ainsi que dans les rapports html. + +Un rendu est défini par deux lignes : + +1. Une pour définir le type de rendu +2. Une pour paramétrer ce rendu + +```properties +report.xxx.columnRenderers.1.type=HighlightIfAbsoluteDeltaIsPositive +report.ccc.columnRenderers.1.parameters=10|11|0.0001|0.5 +``` + +Il est possible d'ajouter plusieurs rendus sur un même rapport. + +Les rendus disponibles et leur documentation sont décrits dans le document [suivant](./embedded-column-renderers.html). ## Pour aller plus loin ===================================== toolkit/api-report/pom.xml ===================================== @@ -38,10 +38,6 @@ <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> </dependency> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - </dependency> <dependency> <groupId>io.ultreia.java4all</groupId> <artifactId>java-lang</artifactId> ===================================== toolkit/api-report/src/main/java/fr/ird/observe/report/ColumnRendererConsumer.java ===================================== @@ -22,8 +22,7 @@ package fr.ird.observe.report; * #L% */ -import com.google.common.io.Resources; -import fr.ird.observe.report.renderers.HighlightIfAbsoluteDeltaIsPositive; +import fr.ird.observe.dto.ObserveUtil; import io.ultreia.java4all.util.SingletonSupplier; import org.jdesktop.swingx.JXTable; import org.jdesktop.swingx.decorator.ColorHighlighter; @@ -31,6 +30,8 @@ import org.jdesktop.swingx.decorator.HighlightPredicate; import java.awt.Color; import java.io.IOException; +import java.io.InputStream; +import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.Objects; @@ -53,13 +54,18 @@ public interface ColumnRendererConsumer<P extends ColumnRendererParameters> { } static SingletonSupplier<String> htmlFunctions(Class<?> type) { - return SingletonSupplier.of(() -> { - try { - return Resources.toString(Objects.requireNonNull(HighlightIfAbsoluteDeltaIsPositive.class.getResource(type.getSimpleName() + ".js")), StandardCharsets.UTF_8); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); + String resourceName = type.getSimpleName() + ".js"; + URL url = Objects.requireNonNull(type.getResource(resourceName), "Could not find resource: " + resourceName); + return ObserveUtil.loadResourceContentSupplier(url, ObserveUtil.removeJavaLicense()); + } + + static String loadResourceContent(URL url) { + try (InputStream in = url.openStream()) { + String content = new String(in.readAllBytes(), StandardCharsets.UTF_8); + return content.substring(content.indexOf("*/") + 2).trim(); + } catch (IOException e) { + throw new RuntimeException(e); + } } /** @@ -90,13 +96,9 @@ public interface ColumnRendererConsumer<P extends ColumnRendererParameters> { return parseParameters(parameters); } - Object consumeHtml(P parameters); - void consumeSwing(P parameters, JXTable table); String htmlFunctions(); - default String htmlInitCode() { - return String.format("init%s(renderers[i++], result, data);\n", getClass().getSimpleName()); - } + String htmlInitCode(P parameters); } ===================================== toolkit/api-report/src/main/java/fr/ird/observe/report/ColumnRendererConsumers.java ===================================== @@ -23,12 +23,10 @@ package fr.ird.observe.report; */ import fr.ird.observe.report.definition.ColumnRendererDefinition; -import io.ultreia.java4all.util.matrix.DataMatrix; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jdesktop.swingx.JXTable; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; @@ -71,25 +69,21 @@ public final class ColumnRendererConsumers { return get().getConsumer(definition.getName()).createParameters(requestExecutor, definition.getParameters()); } - public static <P extends ColumnRendererParameters, R extends ColumnRendererConsumer<P>> Object consumeHtml(P parameters) { - return get().getConsumer(parameters.name()).consumeHtml(parameters); - } - - public static <P extends ColumnRendererParameters, R extends ColumnRendererConsumer<P>> void consumeSwing(P parameters, JXTable table) { + public static <P extends ColumnRendererParameters> void consumeSwing(P parameters, JXTable table) { get().getConsumer(parameters.name()).consumeSwing(parameters, table); } - public static Set<String> htmlFunctions(ColumnRendererDefinition[] columnRenderers) { - return Arrays.stream(columnRenderers).map(columnRenderer -> get().getConsumer(columnRenderer.getName()).htmlFunctions()).collect(Collectors.toCollection(TreeSet::new)); + public static Set<String> htmlFunctions(ReportColumnRenderersParameters columnRenderers) { + return columnRenderers.getColumnRendererParameters().stream().map(columnRenderer -> get().getConsumer(columnRenderer.name()).htmlFunctions()).collect(Collectors.toCollection(TreeSet::new)); } - public static List<String> htmlInitCode(ColumnRendererDefinition[] columnRenderers) { - return Arrays.stream(columnRenderers).map(r -> get().getConsumer(r.getName()).htmlInitCode()).collect(Collectors.toList()); + public static List<String> htmlInitCode(ReportColumnRenderersParameters columnRenderers) { + return columnRenderers.getColumnRendererParameters().stream().map(r -> get().getConsumer(r.name()).htmlInitCode(r)).collect(Collectors.toList()); } @SuppressWarnings("unchecked") - public <P extends ColumnRendererParameters, R extends ColumnRendererConsumer<P>> R getConsumer(String name) { - return (R) Objects.requireNonNull(consumers.get(name), "Could not find column renderer with name: " + name); + public <P extends ColumnRendererParameters> ColumnRendererConsumer<P> getConsumer(String name) { + return (ColumnRendererConsumer<P>) Objects.requireNonNull(consumers.get(name), "Could not find column renderer with name: " + name); } } ===================================== toolkit/api-report/src/main/java/fr/ird/observe/report/ReportColumnRenderersParameters.java ===================================== @@ -28,7 +28,6 @@ import org.jdesktop.swingx.JXTable; import java.util.Collections; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; /** * Created at 29/11/2023. @@ -53,8 +52,4 @@ public class ReportColumnRenderersParameters implements JsonAware { public void consumeColumnRenderersSwing(JXTable table) { getColumnRendererParameters().forEach(r -> ColumnRendererConsumers.consumeSwing(r, table)); } - - public List<Object> consumeColumnRenderersHtml() { - return getColumnRendererParameters().stream().map(ColumnRendererConsumers::consumeHtml).collect(Collectors.toList()); - } } ===================================== toolkit/api-report/src/main/java/fr/ird/observe/report/json/ReportColumnRenderersParametersAdapter.java ===================================== @@ -0,0 +1,84 @@ +package fr.ird.observe.report.json; + +/*- + * #%L + * ObServe Toolkit :: API :: Report + * %% + * Copyright (C) 2008 - 2023 IRD, Ultreia.io + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import com.google.auto.service.AutoService; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import fr.ird.observe.report.ColumnRendererParameters; +import fr.ird.observe.report.ReportColumnRenderersParameters; +import io.ultreia.java4all.lang.Objects2; +import io.ultreia.java4all.util.json.JsonAdapter; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * Created at 01/12/2023. + * + * @author Tony Chemit - dev@tchemit.fr + * @since 9.3.0 + */ +@AutoService(JsonAdapter.class) +public class ReportColumnRenderersParametersAdapter implements JsonDeserializer<ReportColumnRenderersParameters>, JsonSerializer<ReportColumnRenderersParameters>, JsonAdapter { + + private static final String TYPE = "type"; + private static final String VALUE = "value"; + + @Override + public Class<?> type() { + return ReportColumnRenderersParameters.class; + } + + @Override + public ReportColumnRenderersParameters deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonArray columnRendererParametersJson = json.getAsJsonArray(); + List<ColumnRendererParameters> list = new ArrayList<>(columnRendererParametersJson.size()); + for (JsonElement itemJson : columnRendererParametersJson) { + JsonObject asJsonObject = itemJson.getAsJsonObject(); + String key = context.deserialize(asJsonObject.get(TYPE), String.class); + Class<ColumnRendererParameters> itemType = Objects2.forName(key); + list.add(context.deserialize(asJsonObject.get(VALUE), itemType)); + } + return new ReportColumnRenderersParameters(list); + } + + @Override + public JsonElement serialize(ReportColumnRenderersParameters src, Type typeOfSrc, JsonSerializationContext context) { + JsonArray result = new JsonArray(); + for (ColumnRendererParameters columnRendererParameter : src.getColumnRendererParameters()) { + JsonObject item = new JsonObject(); + item.addProperty(TYPE, columnRendererParameter.getClass().getName()); + item.add(VALUE, context.serialize(columnRendererParameter)); + result.add(item); + } + return result; + } +} ===================================== toolkit/api-report/src/main/java/fr/ird/observe/report/renderers/HighlightIfAbsoluteDeltaIsPositive.java ===================================== @@ -31,7 +31,6 @@ import org.jdesktop.swingx.decorator.ComponentAdapter; import org.jdesktop.swingx.decorator.HighlightPredicate; import java.awt.Component; -import java.util.Map; import static fr.ird.observe.report.renderers.HighlightIfAbsoluteDeltaIsPositive.Parameters; @@ -66,15 +65,6 @@ public class HighlightIfAbsoluteDeltaIsPositive implements ColumnRendererConsume return new Parameters(column1, column2, warningThreshHold, errorThreshHold); } - @Override - public Object consumeHtml(Parameters parameters) { - return Map.of("name", name(), - "column1", parameters.getColumn1(), - "column2", parameters.getColumn2(), - "warningThreshHold", parameters.getWarningThreshHold(), - "errorThreshHold", parameters.getErrorThreshHold()); - } - @Override public void consumeSwing(Parameters parameters, JXTable table) { ColumnRendererConsumer.addHighLighters(table, @@ -88,6 +78,11 @@ public class HighlightIfAbsoluteDeltaIsPositive implements ColumnRendererConsume return HTML_FUNCTION.get(); } + @Override + public String htmlInitCode(Parameters parameters) { + return String.format("init%s(%d, %d, %s, %s, result, data);\n", parameters.name(), parameters.getColumn1(), parameters.getColumn2(), parameters.getWarningThreshHold(), parameters.getErrorThreshHold()); + } + public static final class Parameters implements ColumnRendererParameters { private final int column1; private final int column2; ===================================== toolkit/api-report/src/main/java/fr/ird/observe/report/renderers/HighlightIfNotI18nReferentialValue.java → toolkit/api-report/src/main/java/fr/ird/observe/report/renderers/HighlightIfEquals18nReferentialValue.java ===================================== @@ -31,23 +31,21 @@ import org.jdesktop.swingx.JXTable; import org.jdesktop.swingx.decorator.ColorHighlighter; import java.awt.Color; -import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeSet; -import static fr.ird.observe.report.renderers.HighlightIfNotI18nReferentialValue.Parameters; +import static fr.ird.observe.report.renderers.HighlightIfEquals18nReferentialValue.Parameters; /** - * Created at 29/11/2023. + * Created at 02/12/2023. * * @author Tony Chemit - dev@tchemit.fr * @since 9.3.0 */ -@SuppressWarnings("rawtypes") @AutoService(ColumnRendererConsumer.class) -public class HighlightIfNotI18nReferentialValue implements ColumnRendererConsumer<Parameters> { - private final static SingletonSupplier<String> HTML_FUNCTION = ColumnRendererConsumer.htmlFunctions(HighlightIfNotI18nReferentialValue.class); +public class HighlightIfEquals18nReferentialValue implements ColumnRendererConsumer<Parameters> { + private final static SingletonSupplier<String> HTML_FUNCTION = ColumnRendererConsumer.htmlFunctions(HighlightIfEquals18nReferentialValue.class); @Override public int parametersCount() { @@ -78,19 +76,12 @@ public class HighlightIfNotI18nReferentialValue implements ColumnRendererConsume return result.setLabel(label); } - @Override - public Object consumeHtml(Parameters parameters) { - return Map.of("name", name(), - "columns", parameters.getColumns(), - "label", parameters.getLabel()); - } - @Override public void consumeSwing(Parameters parameters, JXTable table) { table.addHighlighter(new ColorHighlighter((renderer, adapter) -> { Object value = adapter.getValue(); int column = adapter.convertColumnIndexToModel(adapter.column); - return parameters.getColumns().contains(column) && !Objects.equals(value, parameters.getLabel()); + return parameters.getColumns().contains(column) && Objects.equals(value, parameters.getLabel()); }, Color.RED, Color.BLACK)); } @@ -99,6 +90,11 @@ public class HighlightIfNotI18nReferentialValue implements ColumnRendererConsume return HTML_FUNCTION.get(); } + @Override + public String htmlInitCode(Parameters parameters) { + return String.format("init%s(%s, '%s', result, data);\n", parameters.name(), parameters.getColumns(), parameters.getLabel()); + } + public static final class Parameters implements ColumnRendererParameters { private final Set<Integer> columns; private final String id; @@ -118,7 +114,7 @@ public class HighlightIfNotI18nReferentialValue implements ColumnRendererConsume @Override public String name() { - return HighlightIfNotI18nReferentialValue.class.getSimpleName(); + return HighlightIfEquals18nReferentialValue.class.getSimpleName(); } public Set<Integer> getColumns() { ===================================== toolkit/api-report/src/main/java/fr/ird/observe/report/renderers/HighlightIfNumericalValueIsPositive.java ===================================== @@ -31,7 +31,6 @@ import org.jdesktop.swingx.decorator.ComponentAdapter; import org.jdesktop.swingx.decorator.HighlightPredicate; import java.awt.Component; -import java.util.Map; import static fr.ird.observe.report.renderers.HighlightIfNumericalValueIsPositive.Parameters; @@ -65,14 +64,6 @@ public class HighlightIfNumericalValueIsPositive implements ColumnRendererConsum } - @Override - public Object consumeHtml(Parameters parameters) { - return Map.of("name", name(), - "column", parameters.getColumn(), - "warningThreshHold", parameters.getWarningThreshHold(), - "errorThreshHold", parameters.getErrorThreshHold()); - } - @Override public void consumeSwing(Parameters parameters, JXTable table) { ColumnRendererConsumer.addHighLighters(table, @@ -86,6 +77,11 @@ public class HighlightIfNumericalValueIsPositive implements ColumnRendererConsum return HTML_FUNCTION.get(); } + @Override + public String htmlInitCode(Parameters parameters) { + return String.format("init%s(%d, %f, %f, result, data);\n", parameters.name(), parameters.getColumn(), parameters.getWarningThreshHold(), parameters.getErrorThreshHold()); + } + public static final class Parameters implements ColumnRendererParameters { private final int column; private final double warningThreshHold; ===================================== toolkit/api-report/src/main/resources/fr/ird/observe/report/renderers/HighlightIfAbsoluteDeltaIsPositive.js ===================================== @@ -46,9 +46,7 @@ function HighlightIfAbsoluteDeltaIsPositive(cell, row, column2, warningThreshHol }; } -function initHighlightIfAbsoluteDeltaIsPositive(renderer, result, json) { - let column1 = renderer["column1"]; - let column2 = renderer["column2"]; +function initHighlightIfAbsoluteDeltaIsPositive(column1, column2, warningThreshHold, errorThreshHold, result, json) { let height = json.height; let data = json.data; for (let row = 0; row < height; row++) { @@ -58,8 +56,6 @@ function initHighlightIfAbsoluteDeltaIsPositive(renderer, result, json) { } } } - let warningThreshHold = renderer["warningThreshHold"]; - let errorThreshHold = renderer["errorThreshHold"]; result[column1] = { name: result [column1], formatter: cell => { ===================================== toolkit/api-report/src/main/resources/fr/ird/observe/report/renderers/HighlightIfNotI18nReferentialValue.js → toolkit/api-report/src/main/resources/fr/ird/observe/report/renderers/HighlightIfEquals18nReferentialValue.js ===================================== @@ -19,27 +19,24 @@ * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ -function HighlightIfNotI18nReferentialValue(cell, label) { +function HighlightIfEquals18nReferentialValue(cell, label) { if (!!!cell) { return; } if (cell === label) { - return; + return { + 'data-cell-content': cell, + 'class': 'gridjs-td cellError', + }; } - return { - 'data-cell-content': cell, - 'class': 'gridjs-td cellError', - }; } -function initHighlightIfNotI18nReferentialValue(renderer, result, json) { - let columns = renderer["columns"]; - let label = renderer["label"]; +function initHighlightIfEquals18nReferentialValue(columns, label, result, json) { for (let j = 0; j < columns.length; j++) { let column = columns[j]; result [column] = { name: result [column], - attributes: cell => HighlightIfNotI18nReferentialValue(cell, label) + attributes: cell => HighlightIfEquals18nReferentialValue(cell, label) }; } } ===================================== toolkit/api-report/src/main/resources/fr/ird/observe/report/renderers/HighlightIfNumericalValueIsPositive.js ===================================== @@ -39,8 +39,7 @@ function HighlightIfNumericalValueIsPositive(cell, warningThreshHold, errorThres }; } -function initHighlightIfNumericalValueIsPositive(renderer, result, json) { - let column = renderer["column"]; +function initHighlightIfNumericalValueIsPositive(column, warningThreshHold, errorThreshHold, result, json) { let height = json.height; let data = json.data; for (let row = 0; row < height; row++) { @@ -48,8 +47,6 @@ function initHighlightIfNumericalValueIsPositive(renderer, result, json) { data[row][column] = "-"; } } - let warningThreshHold = renderer["warningThreshHold"]; - let errorThreshHold = renderer["errorThreshHold"]; result[column] = { name: result [column], formatter: cell => { ===================================== toolkit/api/src/main/java/fr/ird/observe/dto/ObserveUtil.java ===================================== @@ -31,15 +31,21 @@ import fr.ird.observe.spi.module.BusinessSubModule; import io.ultreia.java4all.config.ApplicationConfig; import io.ultreia.java4all.config.ConfigResource; import io.ultreia.java4all.lang.Strings; +import io.ultreia.java4all.util.SingletonSupplier; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.core.config.Configurator; import org.hashids.Hashids; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; +import java.io.IOException; +import java.io.InputStream; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.MalformedURLException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.text.Collator; @@ -66,7 +72,6 @@ import java.util.function.Supplier; public class ObserveUtil { public static final String SQL_GZ_EXTENSION_PATTERN = "^.+\\.sql\\.gz|.+\\.SQL\\.GZ$"; - public static final String CSV_EXTENSION_PATTERN = "^.+\\.csv|.+\\.CSV$"; public static final String SQL_GZ_EXTENSION = ".sql.gz"; public static final String CSV_EXTENSION = ".csv"; public static final String PROPERTIES_EXTENSION_PATTERN = "^.+\\.properties|.+\\.PROPERTIES$"; @@ -75,38 +80,7 @@ public class ObserveUtil { public static final String PNG_EXTENSION = ".png"; public static final String JS_ENGINE_NAME = "rhino"; private static final Hashids ID_GENERATOR = new Hashids("ObServeHasSomeSalt", 8, "0123456789#abcdefghijklmnopqrestuvwxyz"); - - @SuppressWarnings("rawtypes") - private static class ClassComparator<C extends Class<?>> implements Comparator<C> { - - private final Map<Class, String> cache; - private final Function<Class, String> function; - - private final Collator collator; - - private ClassComparator(Function<Class, String> function, Locale locale) { - this.cache = new HashMap<>(); - this.function = function; - this.collator = Collator.getInstance(locale); - this.collator.setStrength(Collator.PRIMARY); - } - - @Override - public int compare(Class o1, Class o2) { - String s1 = getValue(o1); - String s2 = getValue(o2); - return this.collator.compare(s1, s2); - } - - String getValue(Class klass) { - return cache.computeIfAbsent(klass, k -> function.apply(klass)); - } - - public void sort(List<C> list) { - list.sort(this); - cache.clear(); - } - } + private static final Logger log = LogManager.getLogger(ObserveUtil.class); public static String newUUID(Date now) { return ID_GENERATOR.encode(now.getTime()); @@ -253,15 +227,65 @@ public class ObserveUtil { } /** - * * @param jsonString the json string in compact mode * @return the gson in a pretty mode */ - public static String toPrettyFormat(String jsonString) - { + public static String toPrettyFormat(String jsonString) { JsonObject json = JsonParser.parseString(jsonString).getAsJsonObject(); Gson gson = new GsonBuilder().setPrettyPrinting().create(); return gson.toJson(json); } + + public static SingletonSupplier<String> loadResourceContentSupplier(URL url, Function<String, String> contentTransformer) { + return SingletonSupplier.of(() -> { + String content = loadResourceContent(url); + return contentTransformer == null ? content : contentTransformer.apply(content); + }); + } + + public static String loadResourceContent(URL url) { + log.info("Loading resource content: {}", url); + try (InputStream in = url.openStream()) { + return new String(in.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Function<String, String> removeJavaLicense() { + return content -> content.substring(content.indexOf("*/") + 2).trim(); + } + + @SuppressWarnings("rawtypes") + private static class ClassComparator<C extends Class<?>> implements Comparator<C> { + + private final Map<Class, String> cache; + private final Function<Class, String> function; + + private final Collator collator; + + private ClassComparator(Function<Class, String> function, Locale locale) { + this.cache = new HashMap<>(); + this.function = function; + this.collator = Collator.getInstance(locale); + this.collator.setStrength(Collator.PRIMARY); + } + + @Override + public int compare(Class o1, Class o2) { + String s1 = getValue(o1); + String s2 = getValue(o2); + return this.collator.compare(s1, s2); + } + + String getValue(Class klass) { + return cache.computeIfAbsent(klass, k -> function.apply(klass)); + } + + public void sort(List<C> list) { + list.sort(this); + cache.clear(); + } + } } ===================================== toolkit/api/src/main/java/fr/ird/observe/spi/json/java4all/DataMatrixAdapter.java ===================================== @@ -0,0 +1,103 @@ +package fr.ird.observe.spi.json.java4all; + +/*- + * #%L + * ObServe Toolkit :: API + * %% + * Copyright (C) 2008 - 2023 IRD, Ultreia.io + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import com.google.auto.service.AutoService; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import io.ultreia.java4all.util.json.JsonAdapter; +import io.ultreia.java4all.util.matrix.DataMatrix; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * Created at 01/12/2023. + * + * @author Tony Chemit - dev@tchemit.fr + * @since 9.3.0 + */ +@AutoService(JsonAdapter.class) +public class DataMatrixAdapter implements JsonDeserializer<DataMatrix>, JsonSerializer<DataMatrix>, JsonAdapter { + + private static final String WIDTH = "width"; + private static final String HEIGHT = "height"; + private static final String X = "x"; + private static final String Y = "y"; + private static final String ROWS = "rows"; + + @Override + public Class<?> type() { + return DataMatrix.class; + } + + @Override + public DataMatrix deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { + JsonObject dataMatrixAsJsonObject = json.getAsJsonObject(); + + DataMatrix result = new DataMatrix(); + result.setWidth(context.deserialize(dataMatrixAsJsonObject.get(WIDTH), int.class)); + result.setHeight(context.deserialize(dataMatrixAsJsonObject.get(HEIGHT), int.class)); + result.setX(context.deserialize(dataMatrixAsJsonObject.get(X), int.class)); + result.setY(context.deserialize(dataMatrixAsJsonObject.get(Y), int.class)); + JsonArray rows = dataMatrixAsJsonObject.getAsJsonArray(ROWS); + Object[][] data = new Object[result.getHeight()][result.getWidth()]; + int index = 0; + for (JsonElement rowElement : rows) { + String[] deserialize = rowElement.getAsString().split("\\|\\|"); + Object[] row = new Object[deserialize.length]; + for (int i = 0; i < deserialize.length; i++) { + String s = deserialize[i]; + row[i] = s.equals("$") ? null : s; + } + data[index++] = row; + } + result.setData(data); + return result; + } + + @Override + public JsonElement serialize(DataMatrix src, Type typeOfSrc, JsonSerializationContext context) { + JsonObject result = new JsonObject(); + result.addProperty(WIDTH, src.getWidth()); + result.addProperty(HEIGHT, src.getHeight()); + result.addProperty(X, src.getX()); + result.addProperty(Y, src.getY()); + JsonArray rows = new JsonArray(src.getHeight()); + result.add(ROWS, rows); + for (Object[] row : src.getData()) { + rows.add(new JsonPrimitive(Arrays.stream(row).map(d -> d == null ? "$" : d.toString()).collect(Collectors.joining("||")))); + } + + return result; + } +} + View it on GitLab: https://gitlab.com/ultreiaio/ird-observe/-/commit/ba11dbb303d01e49b683e6d671... -- View it on GitLab: https://gitlab.com/ultreiaio/ird-observe/-/commit/ba11dbb303d01e49b683e6d671... You're receiving this email because of your account on gitlab.com.
participants (1)
-
Tony CHEMIT (@tchemit)