From b848f2a56ecb8d4d59419b4d437826f5b8474b9a Mon Sep 17 00:00:00 2001 From: Tor-Einar Skog <tor-einar.skog@nibio.no> Date: Fri, 23 Aug 2024 11:17:25 +0200 Subject: [PATCH] Add option to use gridded weather data in forecast configuration form [VIPSUTV-733] --- pom.xml | 2 +- .../ForecastConfigurationController.java | 8 +++ .../controller/session/ForecastBean.java | 1 + .../session/SessionControllerGetter.java | 2 +- .../logic/entity/ForecastConfiguration.java | 12 +++- .../no/nibio/web/forms/FormValidator.java | 4 ++ .../db/migration/V18__UseGridWeatherData.sql | 3 + .../vips/logic/i18n/vipslogictexts.properties | 2 + .../logic/i18n/vipslogictexts_bs.properties | 2 + .../logic/i18n/vipslogictexts_hr.properties | 2 + .../logic/i18n/vipslogictexts_nb.properties | 2 + .../logic/i18n/vipslogictexts_sr.properties | 2 + .../i18n/vipslogictexts_zh_CN.properties | 2 + .../forecastConfigurationForm.json | 5 ++ src/main/webapp/js/validateForm.js | 5 +- .../templates/forecastConfigurationForm.ftl | 58 ++++++++++++++++++- 16 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/db/migration/V18__UseGridWeatherData.sql diff --git a/pom.xml b/pom.xml index b942c53a..c2c4bc1d 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ <groupId>no.nibio.vips.</groupId> <artifactId>VIPSLogic</artifactId> <packaging>war</packaging> - <version>2024.2</version> + <version>2024.3</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java index b7277f97..61c6e423 100755 --- a/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java +++ b/src/main/java/no/nibio/vips/logic/controller/servlet/ForecastConfigurationController.java @@ -35,6 +35,9 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; + +import org.slf4j.LoggerFactory; + import no.nibio.vips.logic.controller.session.ForecastBean; import no.nibio.vips.logic.controller.session.OrganismBean; import no.nibio.vips.logic.controller.session.UserBean; @@ -59,6 +62,8 @@ import no.nibio.web.forms.FormValidator; * @author Tor-Einar Skog <tor-einar.skog@nibio.no> */ public class ForecastConfigurationController extends HttpServlet { + + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(ForecastConfigurationController.class); @PersistenceContext(unitName="VIPSLogic-PU") EntityManager em; @@ -314,10 +319,13 @@ public class ForecastConfigurationController extends HttpServlet { "forecastConfigurationMultipleNewForm" :"forecastConfigurationForm"; FormValidation formValidation = FormValidator.validateForm(formId,request,getServletContext()); + LOGGER.debug("formValidation=" + formValidation.isValid()); // Also validation the model specific fields String modelId = formValidation.getFormField("modelId").getWebValue(); FormValidation modelFieldFormValidation = FormValidator.validateForm("models/" + modelId, request, getServletContext()); + // Additional input check: If the Grid data checkbox is not checked, a + if(formValidation.isValid() && modelFieldFormValidation.isValid()) { if(formId.equals("forecastConfigurationForm")) diff --git a/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java b/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java index 6aa46f8b..9e6cdcb1 100755 --- a/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java +++ b/src/main/java/no/nibio/vips/logic/controller/session/ForecastBean.java @@ -600,6 +600,7 @@ public class ForecastBean { forecastConfiguration.setCropOrganismId(em.find(Organism.class, formFields.get("cropOrganismId").getValueAsInteger())); forecastConfiguration.setPestOrganismId(em.find(Organism.class, formFields.get("pestOrganismId").getValueAsInteger())); forecastConfiguration.setIsPrivate(formFields.get("isPrivate").getWebValue() != null); + forecastConfiguration.setUseGridWeatherData(formFields.get("useGridWeatherData").getWebValue() != null); PointOfInterest locationPoi = em.find(PointOfInterest.class, formFields.get("locationPointOfInterestId").getValueAsInteger()); forecastConfiguration.setLocationPointOfInterestId(locationPoi); PointOfInterest weatherStationPoi = em.find(PointOfInterestWeatherStation.class, formFields.get("weatherStationPointOfInterestId").getValueAsInteger()); diff --git a/src/main/java/no/nibio/vips/logic/controller/session/SessionControllerGetter.java b/src/main/java/no/nibio/vips/logic/controller/session/SessionControllerGetter.java index 399bd43b..be60d079 100644 --- a/src/main/java/no/nibio/vips/logic/controller/session/SessionControllerGetter.java +++ b/src/main/java/no/nibio/vips/logic/controller/session/SessionControllerGetter.java @@ -34,7 +34,7 @@ public class SessionControllerGetter { // This obviously has to be changed when changing the application name in Maven // TODO: Refactor out to System properties (e.g. in standalone.xml in JBoss/WildFly) - public static final String JNDI_PATH = "java:global/VIPSLogic-2024.2/"; + public static final String JNDI_PATH = "java:global/VIPSLogic-2024.3/"; public static SchedulingBean getSchedulingBean() { diff --git a/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java b/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java index a17f2fba..c7f11e7d 100755 --- a/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java +++ b/src/main/java/no/nibio/vips/logic/entity/ForecastConfiguration.java @@ -50,7 +50,7 @@ import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDefs; /** - * @copyright 2014-2016 <a href="http://www.nibio.no/">NIBIO</a> + * @copyright 2014-2024 <a href="http://www.nibio.no/">NIBIO</a> * @author Tor-Einar Skog <tor-einar.skog@nibio.no> */ @Entity @@ -123,7 +123,17 @@ public class ForecastConfiguration implements Serializable, Comparable { private Organism pestOrganismId; @Column(name = "is_private") private Boolean isPrivate; + @Column(name = "use_grid_weather_data") + private Boolean useGridWeatherData; + public Boolean getUseGridWeatherData() { + return useGridWeatherData; + } + + public void setUseGridWeatherData(Boolean useGridWeatherData) { + this.useGridWeatherData = useGridWeatherData; + } + @Type(type = "IntegerArray") @Column(name = "grid_weather_station_point_of_interest_ids") private Integer[] gridWeatherStationPointOfInterestIds; diff --git a/src/main/java/no/nibio/web/forms/FormValidator.java b/src/main/java/no/nibio/web/forms/FormValidator.java index 65214d7e..d6d0ef0f 100755 --- a/src/main/java/no/nibio/web/forms/FormValidator.java +++ b/src/main/java/no/nibio/web/forms/FormValidator.java @@ -44,6 +44,7 @@ import no.nibio.vips.logic.controller.session.SessionControllerGetter; import no.nibio.vips.logic.controller.session.UserBean; import no.nibio.vips.logic.i18n.SessionLocaleUtil; import org.apache.commons.validator.routines.EmailValidator; +import org.slf4j.LoggerFactory; /** * Uses form configuration set in JSON files in [WARFILE]/formdefinitions/, or @@ -57,6 +58,8 @@ import org.apache.commons.validator.routines.EmailValidator; * @author Tor-Einar Skog <tor-einar.skog@nibio.no> */ public class FormValidator { + + private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(FormValidator.class); @EJB UserBean userBean; @@ -340,6 +343,7 @@ public class FormValidator { { field.setValid(false); field.setValidationMessage(resourceBundle.getString("fieldIsRequired")); + LOGGER.debug(field.getName() + " with a value of " + field.getWebValue() + " is considered to be NULL"); } } diff --git a/src/main/resources/db/migration/V18__UseGridWeatherData.sql b/src/main/resources/db/migration/V18__UseGridWeatherData.sql new file mode 100644 index 00000000..0775bd72 --- /dev/null +++ b/src/main/resources/db/migration/V18__UseGridWeatherData.sql @@ -0,0 +1,3 @@ +-- Adding this property when adding support for gridded weather datasources in VIPS +ALTER TABLE forecast_configuration +ADD COLUMN use_grid_weather_data BOOLEAN DEFAULT FALSE; \ No newline at end of file diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties index 80a357d2..ea2b3d71 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties @@ -1052,3 +1052,5 @@ privacyStatement=Privacy statement privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation +useGridWeatherData=Use grid weather data +doNotUse=Do not use diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties index 87647c45..b632979d 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties @@ -1046,3 +1046,5 @@ privacyStatement=Privacy statement privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation +useGridWeatherData=Use grid weather data +doNotUse=Do not use diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties index 351048a4..286433a3 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties @@ -1044,3 +1044,5 @@ privacyStatement=Privacy statement privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation +useGridWeatherData=Use grid weather data +doNotUse=Do not use diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties index 1e03844c..8629648d 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties @@ -1052,3 +1052,5 @@ privacyStatement=Personvernerkl\u00e6ring privacyStatementFileName=Personvernerklaering_NIBIO-VIPS.pdf thresholdDSVMax=DSV-terskel for h\u00f8y infeksjonsrisiko thresholdDSVTempMin=Minimumstemperatur for beregning av DSV +useGridWeatherData=Bruk v\u00e6rdata fra rutenett +doNotUse=Ikke bruk diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties index 1a791041..044f7b23 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties @@ -1046,3 +1046,5 @@ privacyStatement=Privacy statement privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation +useGridWeatherData=Use grid weather data +doNotUse=Do not use diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties index 0384f07d..66a5fd1c 100755 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties @@ -1040,3 +1040,5 @@ privacyStatement=Privacy statement privacyStatementFileName=Privacy_statement_NIBIO-VIPS.pdf thresholdDSVMax=DSV threshold for high infection risk thresholdDSVTempMin=Minimum temperature for DSV calculation +useGridWeatherData=Use grid weather data +doNotUse=Do not use diff --git a/src/main/webapp/formdefinitions/forecastConfigurationForm.json b/src/main/webapp/formdefinitions/forecastConfigurationForm.json index 95c43c59..ca6fd66a 100755 --- a/src/main/webapp/formdefinitions/forecastConfigurationForm.json +++ b/src/main/webapp/formdefinitions/forecastConfigurationForm.json @@ -57,6 +57,11 @@ "dataType" : "STRING", "required" : false }, + { + "name" : "useGridWeatherData", + "dataType" : "STRING", + "required" : false + }, { "name" : "locationPointOfInterestId", "dataType" : "INTEGER", diff --git a/src/main/webapp/js/validateForm.js b/src/main/webapp/js/validateForm.js index 0e4a8e21..833dfe22 100755 --- a/src/main/webapp/js/validateForm.js +++ b/src/main/webapp/js/validateForm.js @@ -268,7 +268,8 @@ function validateFieldActual(fieldEl, theForm, formDefinitionKey) // Single select field - check for nullValue if(fieldDefinition.fieldType === fieldTypes.TYPE_SELECT_SINGLE) { - webValue = fieldEl.options[fieldEl.selectedIndex].value; + // Fallback if this is not a select list (could be a readonly list using a twin hidden field) + webValue = webValue = fieldEl.options != undefined ? fieldEl.options[fieldEl.selectedIndex].value : fieldEl.value; if(fieldDefinition.nullValue === webValue && fieldDefinition.required === true) { invalidizeField(fieldEl, theForm, getI18nMsg("fieldIsRequired",null)); @@ -471,7 +472,7 @@ function validateFieldActual(fieldEl, theForm, formDefinitionKey) } /** - * Recursive function to travers upwards in tree until we find the form + * Recursive function to traverse upwards in tree until we find the form * for the given element * @param {DOMElement} fieldEl * @returns {DOMelement} the form diff --git a/src/main/webapp/templates/forecastConfigurationForm.ftl b/src/main/webapp/templates/forecastConfigurationForm.ftl index c4656b58..234ef983 100755 --- a/src/main/webapp/templates/forecastConfigurationForm.ftl +++ b/src/main/webapp/templates/forecastConfigurationForm.ftl @@ -190,6 +190,47 @@ } } }; + var handleUseGridWeatherDataClicked = function(theCheckBox, weatherStationPointOfInterestId) { + weatherStationList = document.getElementById("weatherStationPointOfInterestId"); + weatherStationPointOfInterestIdHiddenField = document.getElementById("weatherStationPointOfInterestIdHidden"); + if(theCheckBox.checked) + { + // Select weatherStationId -2 + weatherStationList.selectedIndex = 0; + // Disable the weatherstation select list + weatherStationList.disabled=true; + weatherStationList.name="weatherStationPointOfInterestIdDisabled"; + + // Enable the hidden field + weatherStationPointOfInterestIdHiddenField.disabled=false + weatherStationPointOfInterestIdHiddenField.name="weatherStationPointOfInterestId"; + } + else + { + // Select weatherStationId -1 OR the optionally provided weatherStationPointOfInterestId + if(weatherStationPointOfInterestId == undefined || weatherStationPointOfInterestId == null) + { + weatherStationList.selectedIndex = 1; + } + else + { + for(let i=0;i<weatherStationList.options.length;i++) + { + weatherStationList.options[i].selected = parseInt(weatherStationList.options[i].value) == weatherStationPointOfInterestId; + } + } + // Enable the weather station select list + weatherStationList.disabled=false; + weatherStationList.name="weatherStationPointOfInterestId"; + // Disable the hidden field + weatherStationPointOfInterestIdHiddenField.disabled=true + weatherStationPointOfInterestIdHiddenField.name="weatherStationPointOfInterestIdDisabled"; + } + }; + + // Setting weather station select list state correct on page load + handleUseGridWeatherDataClicked(document.getElementById("useGridWeatherData")<#if forecastConfiguration.weatherStationPointOfInterestId?has_content>,${forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId}</#if>); + </script> </#macro> <#macro custom_css> @@ -253,6 +294,7 @@ <#if ! user.isSuperUser() && ! user.isOrganizationAdmin()> readonly="readonly" disabled="disabled"</#if>/> </label> ${i18nBundle.isPrivate} + <span class="help-block" id="${formId}_isPrivate_validation"></span> </div> </div> <div class="form-group"> @@ -272,7 +314,7 @@ <#if !multipleNew?has_content || !multipleNew> <div class="form-group"> <label for="locationPointOfInterestId">${i18nBundle.locationPointOfInterestId}</label> - <select class="form-control" name="locationPointOfInterestId" onblur="validateField(this);"> + <select class="form-control" id="locationPointOfInterestId" name="locationPointOfInterestId" onblur="validateField(this);"> <option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.locationPointOfInterestId?lower_case}</option> <#list locationPointOfInterests?sort_by("name") as poi> <option value="${poi.pointOfInterestId}"<#if forecastConfiguration.locationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.locationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option> @@ -282,14 +324,24 @@ </div> <div class="form-group"> <label for="weatherStationPointOfInterestId">${i18nBundle.weatherStationPointOfInterestId}</label> - <select class="form-control" name="weatherStationPointOfInterestId" onblur="validateField(this);"> - <option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option> + <select class="form-control" id="weatherStationPointOfInterestId" name="weatherStationPointOfInterestId" onblur="if(!document.getElementById('useGridWeatherData').checked) {validateField(this);};"> + <option value="-2">${i18nBundle.doNotUse} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option> + <option value="-1"<#if !forecastConfiguration.weatherStationPointOfInterestId?has_content && !forecastConfiguration.useGridWeatherData> selected="selected"</#if>>${i18nBundle.pleaseSelect} ${i18nBundle.weatherStationPointOfInterestId?lower_case}</option> <#list weatherStationPointOfInterests?sort_by("name") as poi> <option value="${poi.pointOfInterestId}"<#if forecastConfiguration.weatherStationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.weatherStationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option> </#list> </select> <span class="help-block" id="${formId}_weatherStationPointOfInterestId_validation"></span> </div> + <input type="hidden" id="weatherStationPointOfInterestIdHidden" name="weatherStationPointOfInterestIdDisabled" value="-2" disabled="disabled"/> + <div class="form-group"> + <div class="checkbox"> + <label> + <input type="checkbox" id="useGridWeatherData" name="useGridWeatherData"<#if forecastConfiguration.useGridWeatherData?has_content && forecastConfiguration.useGridWeatherData == true> checked="checked"</#if> onclick="handleUseGridWeatherDataClicked(this);"/> + </label> + ${i18nBundle.useGridWeatherData} + <span class="help-block" id="${formId}_useGridWeatherData_validation"></span> + </div> <#else> <input type="hidden" name="multipleNew" value="true"/> <div class="form-group"> -- GitLab