Tony CHEMIT pushed to branch develop at ultreiaio / ird-observe Commits: eadefbc7 by tchemit at 2020-04-01T14:43:33+02:00 fix ll logbook set haulingBreaks constraint strictlyPositiveNumber to positiveNumber - - - - - 1dbea9ea by tchemit at 2020-04-01T14:52:15+02:00 fix species validator - - - - - 57abb56f by tchemit at 2020-04-01T14:59:28+02:00 add missing landing delete action - - - - - 3a370fe2 by tchemit at 2020-04-01T15:24:30+02:00 reduce log level - - - - - f57dfbf7 by tchemit at 2020-04-01T15:24:33+02:00 add missing validation - - - - - adc1bf76 by tchemit at 2020-04-04T14:28:17+02:00 [SFA] Système de recopie de champs lors de la création d'une nouvelle activité de pêche - Closes #1454 - - - - - 15 changed files: - client-configuration/src/main/resources/observe-log4j2.xml - client-datasource-editor-api/src/main/java/fr/ird/observe/client/datasource/editor/DataSourceEditorLayerUI.java - client-datasource-editor-api/src/main/java/fr/ird/observe/client/datasource/editor/content/ContentUIHandler.java - client-datasource-editor-api/src/main/java/fr/ird/observe/client/datasource/editor/content/data/table/ContentTableEditorLayerUI.java - + client-datasource-editor-ll/src/main/java/fr/ird/observe/client/datasource/editor/content/data/ll/landing/actions/LandingUIDelete.java - dto/src/main/models/Observe-30-data-ll-observation.model - observe-i18n/src/main/i18n/translations/observe_en_GB.properties - observe-i18n/src/main/i18n/translations/observe_es_ES.properties - observe-i18n/src/main/i18n/translations/observe_fr_FR.properties - services-local/src/main/java/fr/ird/observe/services/local/service/data/ll/logbook/SetServiceLocal.java - services-validation/src/main/java/fr/ird/observe/validation/validators/AbstractSpeciesFieldDtoValidator.java - validation/src/main/i18n/getters/validation-fields.getter - validation/src/main/i18n/getters/validation-messages.getter - validation/src/main/resources/fr/ird/observe/dto/data/ll/logbook/ActivityDto-create-error-validation.xml - validation/src/main/resources/fr/ird/observe/dto/data/ll/logbook/ActivityDto-update-error-validation.xml Changes: ===================================== client-configuration/src/main/resources/observe-log4j2.xml ===================================== @@ -63,10 +63,10 @@ <AppenderRef ref="console"/> <AppenderRef ref="File"/> </Logger> - <Logger name="org.nuiton.jaxx.runtime.swing.action.JAXXObjectActionSupport" level="info" additivity="false"> - <AppenderRef ref="console"/> - <AppenderRef ref="File"/> - </Logger> +<!-- <Logger name="org.nuiton.jaxx.runtime.swing.action.JAXXObjectActionSupport" level="info" additivity="false">--> +<!-- <AppenderRef ref="console"/>--> +<!-- <AppenderRef ref="File"/>--> +<!-- </Logger>--> <Logger name="org.hibernate" level="warn" additivity="false"> <AppenderRef ref="console"/> <AppenderRef ref="File"/> ===================================== client-datasource-editor-api/src/main/java/fr/ird/observe/client/datasource/editor/DataSourceEditorLayerUI.java ===================================== @@ -139,9 +139,9 @@ public class DataSourceEditorLayerUI extends AbstractLayerUI<JComponent> impleme // compute focus owner FocusTraversalPolicy focusTraversalPolicy = contentUI.getFocusTraversalPolicy(); focusComponent = Objects.requireNonNull(focusTraversalPolicy).getFirstComponent(contentUI); - log.info(String.format("compute new form focus owner: %s", focusComponent)); + log.debug(String.format("compute new form focus owner: %s", focusComponent)); } - log.info(String.format("Set focus on content: %s", focusComponent)); + log.debug(String.format("Set focus on content: %s", focusComponent)); // this will change the focus contentUI.getHandler().setFormFocusOwner(focusComponent); } finally { ===================================== client-datasource-editor-api/src/main/java/fr/ird/observe/client/datasource/editor/content/ContentUIHandler.java ===================================== @@ -770,15 +770,15 @@ public abstract class ContentUIHandler<U extends ContentUI> implements ObserveSe boolean force = Objects.equals(true, button.getClientProperty("forceNavigation")); if (!force) { if (!button.isEnabled()) { - log.info(String.format("%sReject (disabled) action: %s - %s", prefix, button.getName(), button.getText())); + log.debug(String.format("%sReject (disabled) action: %s - %s", prefix, button.getName(), button.getText())); continue; } if (!button.isShowing()) { - log.info(String.format("%sReject (hidden) action: %s - %s", prefix, button.getName(), button.getText())); + log.debug(String.format("%sReject (hidden) action: %s - %s", prefix, button.getName(), button.getText())); continue; } if (Objects.equals(true, button.getClientProperty("skipNavigation"))) { - log.info(String.format("%sReject (skip) action: %s - %s", prefix, button.getName(), button.getText())); + log.debug(String.format("%sReject (skip) action: %s - %s", prefix, button.getName(), button.getText())); continue; } @@ -790,11 +790,11 @@ public abstract class ContentUIHandler<U extends ContentUI> implements ObserveSe } } if (!found) { - log.info(String.format("%sReject (out of zone) action: %s - %s", prefix, button.getName(), button.getText())); + log.debug(String.format("%sReject (out of zone) action: %s - %s", prefix, button.getName(), button.getText())); continue; } } - log.info(String.format("%sKeep action: %s - %s", prefix, button.getName(), button.getText())); + log.debug(String.format("%sKeep action: %s - %s", prefix, button.getName(), button.getText())); result.add(button); } } @@ -858,7 +858,7 @@ public abstract class ContentUIHandler<U extends ContentUI> implements ObserveSe if (SwingUtilities.getAncestorOfClass(ContentUI.class, newValue) != null) { // if (SwingUtilities.getAncestorNamed("body", newValue) != null) { // focus on content ui, let's keep it as the new form focus owner - log.info(String.format("%sSet content form focus owner: %s", prefix, newValue.getName())); + log.debug(String.format("%sSet content form focus owner: %s", prefix, newValue.getName())); focusAdjusting = true; try { getModel().setFormFocusOwner(newValue); @@ -872,7 +872,7 @@ public abstract class ContentUIHandler<U extends ContentUI> implements ObserveSe if (focusAdjusting) { return; } - log.info(String.format("%sFocus changed on form %s", prefix, focusComponent)); + log.debug(String.format("%sFocus changed on form %s", prefix, focusComponent)); if (getClientUIContext().getMainUIModel().isBlockFocus()) { return; } @@ -883,7 +883,7 @@ public abstract class ContentUIHandler<U extends ContentUI> implements ObserveSe // SwingUtilities.invokeLater(focusComponent::requestFocusInWindow); } - log.info(String.format("%sSet form focus on %s", prefix, focusComponent)); + log.debug(String.format("%sSet form focus on %s", prefix, focusComponent)); if (focusComponent != null) { focusAdjusting = true; SwingUtilities.invokeLater(() -> { ===================================== client-datasource-editor-api/src/main/java/fr/ird/observe/client/datasource/editor/content/data/table/ContentTableEditorLayerUI.java ===================================== @@ -63,7 +63,7 @@ public class ContentTableEditorLayerUI extends ObserveBlockingLayerUI implements boolean focusOnTable = model.isFocusOnTable(); if (focusOnTable) { - log.info(String.format("Enter in table editor zone: %s", e)); + log.debug(String.format("Enter in table editor zone: %s", e)); model.setFocusOnTable(false); } } @@ -97,16 +97,16 @@ public class ContentTableEditorLayerUI extends ObserveBlockingLayerUI implements if (newValue) { return; } - log.info("Set focus on table editor"); + log.debug("Set focus on table editor"); Component focusComponent = editor.getModel().getFormFocusOwner(); - log.info(String.format("Set focus on table editor - initial form focus owner: %s", focusComponent)); + log.debug(String.format("Set focus on table editor - initial form focus owner: %s", focusComponent)); if (focusComponent == null) { // compute focus owner FocusTraversalPolicy focusTraversalPolicy = editor.getFocusTraversalPolicy(); focusComponent = Objects.requireNonNull(focusTraversalPolicy).getFirstComponent(editor); - log.info(String.format("compute new form focus owner: %s", focusComponent)); + log.debug(String.format("compute new form focus owner: %s", focusComponent)); } - log.info(String.format("Set focus on table editor: %s", focusComponent)); + log.debug(String.format("Set focus on table editor: %s", focusComponent)); // this will change the focus editor.getHandler().setFormFocusOwner(focusComponent); } finally { ===================================== client-datasource-editor-ll/src/main/java/fr/ird/observe/client/datasource/editor/content/data/ll/landing/actions/LandingUIDelete.java ===================================== @@ -0,0 +1,52 @@ +package fr.ird.observe.client.datasource.editor.content.data.ll.landing.actions; + +/*- + * #%L + * ObServe :: Client DataSource Editor LL + * %% + * Copyright (C) 2008 - 2020 IRD, Code Lutin, 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 fr.ird.observe.client.datasource.editor.content.data.ll.landing.LandingUI; +import fr.ird.observe.client.datasource.editor.content.data.open.actions.DeleteActionSupport; +import fr.ird.observe.dto.data.ll.landing.LandingDto; +import fr.ird.observe.dto.data.ll.landing.LandingReference; +import fr.ird.observe.navigation.model.edit.ObserveEditNode; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * @author Tony Chemit - dev@tchemit.fr + * @since 8 + */ +public class LandingUIDelete extends DeleteActionSupport<LandingDto, LandingReference, LandingUI> { + + private static final Logger log = LogManager.getLogger(LandingUIDelete.class); + + public LandingUIDelete() { + super(LandingDto.class); + } + + @Override + protected void doDelete(LandingUI ui, LandingDto bean, ObserveEditNode<LandingDto> editNode) { + log.info("Will delete Landing " + bean.getId()); + getServicesProvider().getLlLandingLandingService().delete(bean.getId()); + log.info("Delete done for Landing " + bean.getId()); + } + +} ===================================== dto/src/main/models/Observe-30-data-ll-observation.model ===================================== @@ -164,7 +164,7 @@ haulingStartLongitude + {*:1} Float | notNull haulingEndTimeStamp + {*:1} Date | notNull haulingEndLatitude + {*:1} Float | notNull haulingEndLongitude + {*:1} Float | notNull -haulingBreaks + {*:1} Integer | mayNotNull strictlyPositiveNumber +haulingBreaks + {*:1} Integer | mayNotNull positiveNumber monitored + {*:1} Boolean haulingStartQuadrant + {*:1} Integer | notNull haulingEndQuadrant + {*:1} Integer | notNull ===================================== observe-i18n/src/main/i18n/translations/observe_en_GB.properties ===================================== @@ -970,6 +970,7 @@ observe.data.ll.logbook.Activity.currentDirection=Current direction (°) observe.data.ll.logbook.Activity.currentSpeed=Current Speed (kt) observe.data.ll.logbook.Activity.dataQuality=Data quality observe.data.ll.logbook.Activity.date=Date +observe.data.ll.logbook.Activity.endTime=End time observe.data.ll.logbook.Activity.endTimeStamp=End timestamp observe.data.ll.logbook.Activity.fpaZone=FPA Zone observe.data.ll.logbook.Activity.latitude=Latitude @@ -3012,7 +3013,9 @@ observe.validation.activity.speed.bound=Activity speed is %s nd, which is more t observe.validation.activity.speed.bound.inter=Speed between two activities can't exceed %1$s nd, (activity %2$s speed is %3$s). observe.validation.activityLongline.desactivated.fpaZone=Fpa zone is disabled. observe.validation.activityLongline.endDate.after.currentTrip.startDate=End date must be after current trip start date +observe.validation.activityLongline.endDate.after.startDate=End date must be after start date observe.validation.activityLongline.endDate.before.currentTrip.endDate=End date must be before current trip end date +observe.validation.activityLongline.endTime.after.startTime=End time must be after start time observe.validation.activityLongline.startDate.after.currentTrip.startDate=Start date must be after current trip start date observe.validation.activityLongline.startDate.before.currentTrip.endDate=Start date must be before current trip end date observe.validation.baitsComposition.bound.individualSize=Size must be bound between %s and %s. ===================================== observe-i18n/src/main/i18n/translations/observe_es_ES.properties ===================================== @@ -970,6 +970,7 @@ observe.data.ll.logbook.Activity.currentDirection=Current direction (°) \#TODO observe.data.ll.logbook.Activity.currentSpeed=Current speed (kt) \#TODO observe.data.ll.logbook.Activity.dataQuality=Qualité de donnée \#TODO observe.data.ll.logbook.Activity.date=Día de observación +observe.data.ll.logbook.Activity.endTime=End time \#TODO observe.data.ll.logbook.Activity.endTimeStamp=End timstamp \#TODO observe.data.ll.logbook.Activity.fpaZone=ZEE observe.data.ll.logbook.Activity.latitude=Latitud @@ -3012,7 +3013,9 @@ observe.validation.activity.speed.bound=La velocidad de la actividad actual es % observe.validation.activity.speed.bound.inter=la velocidad entre dos actividades no debe sobrepasar %1$s nd, (actividad %2$s incorrecta, velocidad \: %3$s nd). observe.validation.activityLongline.desactivated.fpaZone=La zona FPA seleccionada está desactivada. observe.validation.activityLongline.endDate.after.currentTrip.startDate=End date must be after current trip start date \#TODO +observe.validation.activityLongline.endDate.after.startDate=End date must be after start date \#TODO observe.validation.activityLongline.endDate.before.currentTrip.endDate=End date must be before current trip end date \#TODO +observe.validation.activityLongline.endTime.after.startTime=End time must be after start time \#TODO observe.validation.activityLongline.startDate.after.currentTrip.startDate=Start date must be after current trip start date \#TODO observe.validation.activityLongline.startDate.before.currentTrip.endDate=Start date must be before current trip end date \#TODO observe.validation.baitsComposition.bound.individualSize=El tamaño debe ser entre %s y %s. @@ -3023,7 +3026,7 @@ observe.validation.baitsComposition.desactivated.baitType=El tipo de cebo selecc observe.validation.baitsComposition.required.baitType=La selección de un tipo es mandatorio. observe.validation.baitsComposition.required.proportion=La proporción está vacía. observe.validation.basket.bound.floatline1Length=La longitud del orinque 1 debe ser entre %1$s y %2$s. -observe.validation.basket.bound.floatline2Length=La longitud d'orinque 1 debe ser entre %1$s y %2$s.& +observe.validation.basket.bound.floatline2Length=La longitud d'orinque 1 debe ser entre %1$s y %2$s. observe.validation.basket.required.settingIdentifier=El identificador de la calada es mandatorio. observe.validation.bound.currentDirection=La dirección actual debe ser un número comprendido entre %1$s y %2$s. observe.validation.bound.currentSpeed=La velocidad actual debe ser un número comprendido entre %1$s y %2$s ===================================== observe-i18n/src/main/i18n/translations/observe_fr_FR.properties ===================================== @@ -970,6 +970,7 @@ observe.data.ll.logbook.Activity.currentDirection=Direction du courant (°) observe.data.ll.logbook.Activity.currentSpeed=vitesse du courant (nd) observe.data.ll.logbook.Activity.dataQuality=Qualité de donnée observe.data.ll.logbook.Activity.date=Jour d'observation +observe.data.ll.logbook.Activity.endTime=Heure de fin observe.data.ll.logbook.Activity.endTimeStamp=Horodatage de fin observe.data.ll.logbook.Activity.fpaZone=Zone FPA observe.data.ll.logbook.Activity.latitude=Latitude @@ -3012,7 +3013,9 @@ observe.validation.activity.speed.bound=La vitesse de l'activité courante est d observe.validation.activity.speed.bound.inter=La vitesse entre deux activités ne doit pas dépasser %1$s nd, (l'activité %2$s a une vitesse de %3$s nd). observe.validation.activityLongline.desactivated.fpaZone=La zone FPA sélectionnée est désactivée. observe.validation.activityLongline.endDate.after.currentTrip.startDate=La date de fin doit être supérieure ou égale à la date de début de marée +observe.validation.activityLongline.endDate.after.startDate=La date de fin doit être supérieure ou égale à la date de début de l'activité observe.validation.activityLongline.endDate.before.currentTrip.endDate=La date de fin doit être inférieur ou égale à la date de fin de marée +observe.validation.activityLongline.endTime.after.startTime=L'heure de fin doit être supérieure ou égale à l'heure de début de l'activité observe.validation.activityLongline.startDate.after.currentTrip.startDate=La date doit être supérieure ou égale à la date de début de marée observe.validation.activityLongline.startDate.before.currentTrip.endDate=La date doit être inférieure ou égale à la date de fin de marée observe.validation.baitsComposition.bound.individualSize=La taille doit être comprise entre %s et %s. @@ -3023,7 +3026,7 @@ observe.validation.baitsComposition.desactivated.baitType=Le type d'appât séle observe.validation.baitsComposition.required.baitType=La sélection d'un type est obligatoire. observe.validation.baitsComposition.required.proportion=Proportion non renseignée. observe.validation.basket.bound.floatline1Length=La longueur d'orin 1 être comprise entre %1$s et %2$s. -observe.validation.basket.bound.floatline2Length=La longueur d'orin 1 être comprise entre %1$s et %2$s.& +observe.validation.basket.bound.floatline2Length=La longueur d'orin 1 être comprise entre %1$s et %2$s. observe.validation.basket.required.settingIdentifier=L'identifiant de filage est obligatoire. observe.validation.bound.currentDirection=La direction courant doit être un entier compris entre %1$s et %2$s. observe.validation.bound.currentSpeed=La vitesse courant (en nd) doit être un nombre compris entre %1$s et %2$s. ===================================== services-local/src/main/java/fr/ird/observe/services/local/service/data/ll/logbook/SetServiceLocal.java ===================================== @@ -120,10 +120,34 @@ public class SetServiceLocal extends ObserveServiceLocal implements SetService { entity.fromDto(getReferentialLocale(), globalCompositionToCopy); entity.fromDto(getReferentialLocale(), dto); + // caracteristics tab + + entity.setSettingVesselSpeed(entityToCopy.getSettingVesselSpeed()); + entity.setTimeBetweenHooks(entityToCopy.getTimeBetweenHooks()); entity.setTotalLineLength(entityToCopy.getTotalLineLength()); entity.setBasketLineLength(entityToCopy.getBasketLineLength()); entity.setLengthBetweenBranchlines(entityToCopy.getLengthBetweenBranchlines()); + entity.setShooterUsed(entityToCopy.getShooterUsed()); + entity.setShooterSpeed(entityToCopy.getShooterSpeed()); + entity.setMaxDepthTargeted(entityToCopy.getMaxDepthTargeted()); + entity.setTotalSectionsCount(entityToCopy.getTotalSectionsCount()); + entity.setBasketsPerSectionCount(entityToCopy.getBasketsPerSectionCount()); + entity.setTotalBasketsCount(entityToCopy.getTotalBasketsCount()); + entity.setBranchlinesPerBasketCount(entityToCopy.getBranchlinesPerBasketCount()); + entity.setTotalHooksCount(entityToCopy.getTotalHooksCount()); entity.setLineType(entityToCopy.getLineType()); + entity.setWeightedSwivel(entityToCopy.getWeightedSwivel()); + entity.setSwivelWeight(entityToCopy.getSwivelWeight()); + entity.setWeightedSnap(entityToCopy.getWeightedSnap()); + entity.setSnapWeight(entityToCopy.getSnapWeight()); + entity.setMonitored(entityToCopy.getMonitored()); + + // lightsticks tab + + entity.setLightsticksUsed(entityToCopy.getLightsticksUsed()); + entity.setTotalLightsticksCount(entityToCopy.getTotalLightsticksCount()); + entity.setLightsticksType(entityToCopy.getLightsticksType()); + entity.setLightsticksColor(entityToCopy.getLightsticksColor()); SaveResultDto result = saveEntity(entity); if (dto.isNotPersisted()) { ===================================== services-validation/src/main/java/fr/ird/observe/validation/validators/AbstractSpeciesFieldDtoValidator.java ===================================== @@ -74,6 +74,7 @@ public abstract class AbstractSpeciesFieldDtoValidator extends FieldValidatorSup private Float ratio; private String speciesField = "species"; private Bound bound; + private Bound boundWithRatio; private String getSpeciesField() { return speciesField; @@ -202,7 +203,7 @@ public abstract class AbstractSpeciesFieldDtoValidator extends FieldValidatorSup return; } - Bound boundWithRatio = bound.applyRatio(ratio); + boundWithRatio = bound.applyRatio(ratio); if (log.isDebugEnabled()) { log.debug("Bound : " + bound); @@ -218,6 +219,14 @@ public abstract class AbstractSpeciesFieldDtoValidator extends FieldValidatorSup } } + public Float getMin() { + return boundWithRatio.getMin(); + } + + public Float getMax() { + return boundWithRatio.getMax(); + } + private Bound getBound(SpeciesDto species) { Float min = getBoundMin(species); ===================================== validation/src/main/i18n/getters/validation-fields.getter ===================================== @@ -48,6 +48,7 @@ observe.data.ll.landing.LandingPart.onBoardProcessing observe.data.ll.landing.LandingPart.weight observe.data.ll.logbook.Activity.currentDirection observe.data.ll.logbook.Activity.currentSpeed +observe.data.ll.logbook.Activity.endTime observe.data.ll.logbook.Activity.endTimeStamp observe.data.ll.logbook.Activity.fpaZone observe.data.ll.logbook.Activity.relatedObservedActivity ===================================== validation/src/main/i18n/getters/validation-messages.getter ===================================== @@ -2,7 +2,9 @@ observe.validation.activity.duplicated.time observe.validation.activity.null.dcp observe.validation.activity.required.observedSystem.for.nonTargetCatch observe.validation.activityLongline.endDate.after.currentTrip.startDate +observe.validation.activityLongline.endDate.after.startDate observe.validation.activityLongline.endDate.before.currentTrip.endDate +observe.validation.activityLongline.endTime.after.startTime observe.validation.activityLongline.startDate.after.currentTrip.startDate observe.validation.activityLongline.startDate.before.currentTrip.endDate observe.validation.catchLongline.required.count.when.acquisitionModeIsGrouped ===================================== validation/src/main/resources/fr/ird/observe/dto/data/ll/logbook/ActivityDto-create-error-validation.xml ===================================== @@ -77,6 +77,21 @@ </param> <message>observe.validation.activityLongline.endDate.after.currentTrip.startDate</message> </field-validator> + <field-validator type="fieldexpression" short-circuit="true"> + <param name="expression"> + <![CDATA[ endDate == null || startDate == null || startDate.time <= endDate.time ]]> + </param> + <message>observe.validation.activityLongline.endTime.after.startTime</message> + </field-validator> + </field> + + <field name="endTime"> + <field-validator type="fieldexpression" short-circuit="true"> + <param name="expression"> + <![CDATA[ endTime == null || startTime == null || startTime.time <= endTime.time ]]> + </param> + <message>observe.validation.activityLongline.endTime.after.startTime</message> + </field-validator> </field> <field name="seaSurfaceTemperature"> ===================================== validation/src/main/resources/fr/ird/observe/dto/data/ll/logbook/ActivityDto-update-error-validation.xml ===================================== @@ -88,6 +88,21 @@ </param> <message>observe.validation.activityLongline.endDate.after.currentTrip.startDate</message> </field-validator> + <field-validator type="fieldexpression" short-circuit="true"> + <param name="expression"> + <![CDATA[ endDate == null || startDate == null || startDate.time <= endDate.time ]]> + </param> + <message>observe.validation.activityLongline.endDate.after.startDate</message> + </field-validator> + </field> + + <field name="endTime"> + <field-validator type="fieldexpression" short-circuit="true"> + <param name="expression"> + <![CDATA[ endTime == null || startTime == null || startTime.time <= endTime.time ]]> + </param> + <message>observe.validation.activityLongline.endTime.after.startTime</message> + </field-validator> </field> <field name="currentDirection"> View it on GitLab: https://gitlab.com/ultreiaio/ird-observe/-/compare/d3e61f870af3a0e57f6cad814... -- View it on GitLab: https://gitlab.com/ultreiaio/ird-observe/-/compare/d3e61f870af3a0e57f6cad814... You're receiving this email because of your account on gitlab.com.