Tony CHEMIT pushed to branch develop at ultreiaio / ird-observe Commits: d39cba11 by Tony Chemit at 2022-09-02T19:04:58+02:00 UI de gestion des associations espèce/océan pas disponible sur Océan - Closes #2366 - - - - - 048de6cf by Tony Chemit at 2022-09-02T19:04:58+02:00 Persistence - Close streams that are coming from database to release as soon as possible underlying resources - - - - - 7bdf5cac by Tony Chemit at 2022-09-02T19:07:12+02:00 Merge branch 'feature/features/ocean_and_species' into develop - - - - - 19 changed files: - client/datasource/editor/common/src/main/java/fr/ird/observe/client/datasource/editor/common/referential/common/OceanUI.jaxx - + client/datasource/editor/common/src/main/java/fr/ird/observe/client/datasource/editor/common/referential/common/OceanUI.jcss - core/api/dto/src/main/resources/META-INF/dto/Observe/DifferentialMetaModel.json - core/api/services/src/main/resources/META-INF/i18n/Observe-labels.properties - core/api/validation/src/main/i18n/getters/eugene.getter - core/persistence/java/src/main/java/fr/ird/observe/entities/data/ps/common/TripBatchSpi.java - + core/persistence/java/src/main/java/fr/ird/observe/entities/referential/common/OceanSpi.java - core/persistence/resources/src/main/resources/fr/ird/observe/entities/referential/common/Ocean/validation-create.json - core/persistence/resources/src/main/resources/fr/ird/observe/entities/referential/common/Ocean/validation-update.json - core/persistence/test/src/test/java/fr/ird/observe/persistence/test/DecoratorServiceTest.java - core/services/i18n/src/main/i18n/translations/services_en_GB.properties - core/services/i18n/src/main/i18n/translations/services_es_ES.properties - core/services/i18n/src/main/i18n/translations/services_fr_FR.properties - model/src/main/models/Observe/dto/01-referential-common.model - model/src/main/models/Observe/dto/attribute/ordered.properties - model/src/main/models/Observe/dto/class/i18nLabels.properties - toolkit/persistence/src/main/java/fr/ird/observe/spi/context/DtoEntityContext.java - toolkit/persistence/src/main/java/fr/ird/observe/spi/context/DtoEntityContextSupport.java - toolkit/persistence/src/main/java/fr/ird/observe/spi/context/ReferentialDtoEntityContext.java Changes: ===================================== client/datasource/editor/common/src/main/java/fr/ird/observe/client/datasource/editor/common/referential/common/OceanUI.jaxx ===================================== @@ -24,24 +24,61 @@ <import> fr.ird.observe.dto.referential.common.OceanDto fr.ird.observe.dto.referential.common.OceanReference + fr.ird.observe.dto.referential.common.SpeciesReference io.ultreia.java4all.jaxx.widgets.choice.BeanCheckBox + io.ultreia.java4all.jaxx.widgets.list.DoubleList </import> <BeanValidator id='validator' autoField='true' beanClass='fr.ird.observe.dto.referential.common.OceanDto' context='create' errorTableModel='{getErrorTableModel()}'/> <OceanUIModel id='model' constructorParams='@override:getNavigationSource(this)'/> <OceanDto id='bean'/> - <Table id='editTable' forceOverride="3"> + <Table id="editView" insets="0" fill="both"> <row> - <cell anchor='west'> - <JLabel id='atLeastOneSelectedLabel'/> - </cell> - <cell weightx="1" fill="both"> - <JPanel id="atLeastOneSelected" layout="{new GridLayout(2, 2)}" border='{BorderFactory.createLoweredBevelBorder()}'> - <BeanCheckBox id='northWestAllowed'/> - <BeanCheckBox id='northEastAllowed' /> - <BeanCheckBox id='southWestAllowed'/> - <BeanCheckBox id='southEastAllowed'/> - </JPanel> + <cell anchor="north" weightx="1" weighty="1"> + <JTabbedPane id='mainTabbedPane'> + <tab id='generalTab' i18nProperty=""> + <Table fill="both" weightx="1"> + <row> + <cell> + <Table id='editTable' styleClass="caracteristic" addToContainer="true" forceOverride="3"> + <row> + <cell anchor='west'> + <JLabel id='atLeastOneSelectedLabel'/> + </cell> + <cell weightx="1" fill="both"> + <JPanel id="atLeastOneSelected" layout="{new GridLayout(2, 2)}" border='{BorderFactory.createLoweredBevelBorder()}'> + <BeanCheckBox id='northWestAllowed'/> + <BeanCheckBox id='northEastAllowed' /> + <BeanCheckBox id='southWestAllowed'/> + <BeanCheckBox id='southEastAllowed'/> + </JPanel> + </cell> + </row> + </Table> + </cell> + </row> + <row> + <cell> + <Table id='editI18nTable' addToContainer="true"/> + </cell> + </row> + <row> + <cell weighty="1"> + <JLabel styleClass="skipI18n"/> + </cell> + </row> + </Table> + </tab> + <tab id='speciesTab' i18nProperty="species"> + <Table fill="both"> + <row> + <cell weightx="1" weighty="1"> + <DoubleList id='species' genericType='SpeciesReference'/> + </cell> + </row> + </Table> + </tab> + </JTabbedPane> </cell> </row> </Table> ===================================== client/datasource/editor/common/src/main/java/fr/ird/observe/client/datasource/editor/common/referential/common/OceanUI.jcss ===================================== @@ -0,0 +1,29 @@ +/*- + * #%L + * ObServe Client :: DataSource :: Editor :: Common + * %% + * Copyright (C) 2008 - 2022 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% + */ + +#mainTabbedPane { + _focusComponent:{newComponentArray(uri,species)}; +} + +/*#species { + border:{new TitledBorder(t("observe.referential.common.SpeciesList.speciesTab") + " ")}; +}*/ ===================================== core/api/dto/src/main/resources/META-INF/dto/Observe/DifferentialMetaModel.json ===================================== @@ -96,6 +96,7 @@ "northWestAllowed", "southEastAllowed", "southWestAllowed", + "species", "uri" ], "fr.ird.observe.dto.referential.common.SexDto": [ ===================================== core/api/services/src/main/resources/META-INF/i18n/Observe-labels.properties ===================================== @@ -20,7 +20,7 @@ # #L% ### #Generated by org.nuiton.i18n.plugin.GenerateI18nLabelsMojo -#Wed Jul 27 12:29:59 CEST 2022 +#Fri Sep 02 18:35:55 CEST 2022 observe.Business.createDate=observe.Common.createDate observe.Business.homeId=observe.Common.homeId observe.Business.id=observe.Common.id @@ -1826,6 +1826,7 @@ observe.referential.common.Ocean.code=observe.referential.Referential.code observe.referential.common.Ocean.codeAndHomeId=observe.referential.Referential.codeAndHomeId observe.referential.common.Ocean.createDate=observe.Common.createDate observe.referential.common.Ocean.enabled=observe.Common.enabled +observe.referential.common.Ocean.generalTab=observe.Common.generalTab observe.referential.common.Ocean.homeId=observe.Common.homeId observe.referential.common.Ocean.id=observe.Common.id observe.referential.common.Ocean.label=observe.Common.label @@ -1839,6 +1840,9 @@ observe.referential.common.Ocean.label7=observe.Common.label7 observe.referential.common.Ocean.label8=observe.Common.label8 observe.referential.common.Ocean.lastUpdateDate=observe.Common.lastUpdateDate observe.referential.common.Ocean.needComment=observe.Common.needComment +observe.referential.common.Ocean.species=observe.Common.species +observe.referential.common.Ocean.species.available=observe.Common.species.available +observe.referential.common.Ocean.species.selected=observe.Common.species.selected observe.referential.common.Ocean.status=observe.Common.status observe.referential.common.Ocean.uri=observe.Common.uri observe.referential.common.Ocean.version=observe.Common.version ===================================== core/api/validation/src/main/i18n/getters/eugene.getter ===================================== @@ -355,6 +355,7 @@ observe.referential.common.Ocean.northEastAllowed observe.referential.common.Ocean.northWestAllowed observe.referential.common.Ocean.southEastAllowed observe.referential.common.Ocean.southWestAllowed +observe.referential.common.Ocean.species observe.referential.common.Person.atLeastOneSelected observe.referential.common.Person.dataEntryOperator observe.referential.common.Person.dataSource ===================================== core/persistence/java/src/main/java/fr/ird/observe/entities/data/ps/common/TripBatchSpi.java ===================================== @@ -32,6 +32,7 @@ import fr.ird.observe.spi.service.ServiceContext; import java.util.Date; import java.util.LinkedHashSet; import java.util.stream.Collectors; +import java.util.stream.Stream; public class TripBatchSpi extends GeneratedTripBatchSpi { @@ -44,9 +45,12 @@ public class TripBatchSpi extends GeneratedTripBatchSpi { protected LinkedHashSet<PackagingReference> getPackaging(ServiceContext context, Trip trip) { Harbour landingHarbour = trip.getLandingHarbour(); Date date = trip.getEndDate(); - return Packaging.toReferenceSet(context.getReferentialLocale(), Packaging.getDao(context) - .streamAll() - .filter(p -> p.acceptHarbour(landingHarbour) && p.acceptDate(date)), null, null).stream().collect(Collectors.toCollection(LinkedHashSet::new)); + try (Stream<Packaging> stream = Packaging.getDao(context).streamAll()) { + return Packaging.toReferenceSet(context.getReferentialLocale(), + stream.filter(p -> p.acceptHarbour(landingHarbour) && p.acceptDate(date)), + null, + null).stream().collect(Collectors.toCollection(LinkedHashSet::new)); + } } } ===================================== core/persistence/java/src/main/java/fr/ird/observe/entities/referential/common/OceanSpi.java ===================================== @@ -0,0 +1,91 @@ +package fr.ird.observe.entities.referential.common; + +/*- + * #%L + * ObServe Core :: Persistence :: Java + * %% + * Copyright (C) 2008 - 2022 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 fr.ird.observe.dto.form.Form; +import fr.ird.observe.dto.referential.common.OceanDto; +import fr.ird.observe.dto.referential.common.SpeciesReference; +import fr.ird.observe.dto.result.SaveResultDto; +import fr.ird.observe.spi.result.AddEntityToUpdateStep; +import fr.ird.observe.spi.service.ServiceContext; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Tony Chemit - dev@tchemit.fr + * @since 9.0.7 + */ +public class OceanSpi extends GeneratedOceanSpi { + + @Override + public Form<OceanDto> loadForm(ServiceContext context, String id) { + Form<OceanDto> form = super.loadForm(context, id); + // add species that use this ocean, to be able to edit species list from Ocean form + // See https://gitlab.com/ultreiaio/ird-observe/-/issues/2366 + try (Stream<Species> speciesStream = Species.loadEntities(context, s -> s.getOceanTopiaIds().contains(id))) { + List<SpeciesReference> speciesReferences = Species.toReferenceSet(context.getReferentialLocale(), speciesStream, null, null, null).toList(); + form.getObject().setSpecies(speciesReferences); + } + return form; + } + + @Override + public SaveResultDto saveEntity(ServiceContext context, Ocean entity, OceanDto dto) { + Set<Species> speciesToUpdate = new LinkedHashSet<>(); + Set<String> newSpeciesWithThisOceanIds = dto.getSpecies().stream().map(SpeciesReference::getId).collect(Collectors.toSet()); + if (dto.isPersisted()) { + // In that case, we could have some species to update by removing some ocean associations + try (Stream<Species> speciesStream = Species.loadEntities(context, s -> s.getOceanTopiaIds().contains(entity.getId()))) { + speciesStream.forEach(oldSpeciesWithThisOcean -> { + String oldSpeciesWithThisOceanId = oldSpeciesWithThisOcean.getTopiaId(); + if (!newSpeciesWithThisOceanIds.contains(oldSpeciesWithThisOceanId)) { + // this species was removed from the ocean + oldSpeciesWithThisOcean.removeOcean(entity); + speciesToUpdate.add(oldSpeciesWithThisOcean); + } else { + // this species still have this ocean, no need to keep it + newSpeciesWithThisOceanIds.remove(oldSpeciesWithThisOceanId); + } + }); + } + } + if (!newSpeciesWithThisOceanIds.isEmpty()) { + // need to add this ocean to those species + try (Stream<Species> speciesStream = Species.loadEntities(context, s -> newSpeciesWithThisOceanIds.contains(s.getTopiaId()))) { + speciesStream.forEach(s -> { + s.addOcean(entity); + speciesToUpdate.add(s); + }); + } + } + AddEntityToUpdateStep update = newSaveHelper(context).update(this, entity); + for (Species species : speciesToUpdate) { + update.update(Species.SPI, species); + } + return update.build(entity); + } +} //OceanSpi ===================================== core/persistence/resources/src/main/resources/fr/ird/observe/entities/referential/common/Ocean/validation-create.json ===================================== @@ -54,6 +54,14 @@ "southWestAllowed is mandatory" ] }, + "species": { + "errors": [ + "check if referential species is disabled (only if validation is strong)" + ], + "warnings": [ + "check if referential species is disabled (only if validation is not strong)" + ] + }, "status": { "warnings": [ "referential is enabled" ===================================== core/persistence/resources/src/main/resources/fr/ird/observe/entities/referential/common/Ocean/validation-update.json ===================================== @@ -54,6 +54,14 @@ "southWestAllowed is mandatory" ] }, + "species": { + "errors": [ + "check if referential species is disabled (only if validation is strong)" + ], + "warnings": [ + "check if referential species is disabled (only if validation is not strong)" + ] + }, "status": { "warnings": [ "referential is enabled" ===================================== core/persistence/test/src/test/java/fr/ird/observe/persistence/test/DecoratorServiceTest.java ===================================== @@ -52,6 +52,7 @@ import org.junit.Test; import java.util.NoSuchElementException; import java.util.Objects; +import java.util.stream.Stream; /** * Created by tchemit on 31/08/17. @@ -120,22 +121,24 @@ public class DecoratorServiceTest extends PersistenceTestSupportRead { R reference1 = spi.toReference(ReferentialLocale.FR, newDto); decoratorService.installDecorator(reference1); decorate(referenceDecorator, reference1); - spi.getDao(persistenceContext).streamAll().limit(ObservePersistenceFixtures.ENTITIES_LIMIT_SIZE).forEach(entity -> { - D dto = spi.toDto(ReferentialLocale.FR, entity); - entity.registerDecorator(entityDecorator); - String entityToString = entityDecorator.decorate(entity); - Assert.assertNotNull(entityToString); - decoratorService.installDecorator(dto); - String dtoToString = decorate(decorator, dto); - if (!(dto instanceof LayoutAware)) { - Assert.assertEquals(entityToString, dtoToString); - } - - R reference = spi.toReference(ReferentialLocale.FR, dto); - decoratorService.installDecorator(reference); - String referenceToString = decorate(referenceDecorator, reference); - Assert.assertEquals(referenceToString, dtoToString); - }); + try (Stream<E> stream = spi.getDao(persistenceContext).streamAll().limit(ObservePersistenceFixtures.ENTITIES_LIMIT_SIZE)) { + stream.forEach(entity -> { + D dto = spi.toDto(ReferentialLocale.FR, entity); + entity.registerDecorator(entityDecorator); + String entityToString = entityDecorator.decorate(entity); + Assert.assertNotNull(entityToString); + decoratorService.installDecorator(dto); + String dtoToString = decorate(decorator, dto); + if (!(dto instanceof LayoutAware)) { + Assert.assertEquals(entityToString, dtoToString); + } + + R reference = spi.toReference(ReferentialLocale.FR, dto); + decoratorService.installDecorator(reference); + String referenceToString = decorate(referenceDecorator, reference); + Assert.assertEquals(referenceToString, dtoToString); + }); + } } } ===================================== core/services/i18n/src/main/i18n/translations/services_en_GB.properties ===================================== @@ -1478,6 +1478,7 @@ observe.referential.common.Ocean.northEastAllowed=Quadrant 1 (NE) observe.referential.common.Ocean.northWestAllowed=Quadrant 4 (NW) observe.referential.common.Ocean.southEastAllowed=Quadrant 2 (SE) observe.referential.common.Ocean.southWestAllowed=Quadrant 3 (SW) +observe.referential.common.Ocean.species=Species observe.referential.common.Ocean.type=Ocean observe.referential.common.Ocean.validation.atLeastOneSelected=At least one quadrant must be selected. observe.referential.common.Organism.type=Organism ===================================== core/services/i18n/src/main/i18n/translations/services_es_ES.properties ===================================== @@ -1478,6 +1478,7 @@ observe.referential.common.Ocean.northEastAllowed=Quadrant 1 (NE) observe.referential.common.Ocean.northWestAllowed=Quadrant 4 (NW) observe.referential.common.Ocean.southEastAllowed=Quadrant 2 (SE) observe.referential.common.Ocean.southWestAllowed=Quadrant 3 (SW) +observe.referential.common.Ocean.species=Especies observe.referential.common.Ocean.type=Océano observe.referential.common.Ocean.validation.atLeastOneSelected=At least one quadrant must be selected. observe.referential.common.Organism.type=Organismo ===================================== core/services/i18n/src/main/i18n/translations/services_fr_FR.properties ===================================== @@ -1478,6 +1478,7 @@ observe.referential.common.Ocean.northEastAllowed=Quadrant 1 (NE) observe.referential.common.Ocean.northWestAllowed=Quadrant 4 (NW) observe.referential.common.Ocean.southEastAllowed=Quadrant 2 (SE) observe.referential.common.Ocean.southWestAllowed=Quadrant 3 (SW) +observe.referential.common.Ocean.species=Espèces observe.referential.common.Ocean.type=Océan observe.referential.common.Ocean.validation.atLeastOneSelected=Veuillez sélectionner au moins un quadrant. observe.referential.common.Organism.type=Organisme ===================================== model/src/main/models/Observe/dto/01-referential-common.model ===================================== @@ -81,6 +81,7 @@ northEastAllowed boolean southEastAllowed boolean southWestAllowed boolean northWestAllowed boolean +species {*:*} referential.common.SpeciesReference referential.common.LengthMeasureMethod > referential.I18nReferential ===================================== model/src/main/models/Observe/dto/attribute/ordered.properties ===================================== @@ -57,6 +57,7 @@ data.ps.observation.Sample.attribute.sampleMeasure=true data.ps.observation.SetCatch.attribute.catches=true data.ps.observation.SetNonTargetCatchRelease.attribute.nonTargetCatchRelease=true referential.common.Gear.attribute.gearCharacteristic=true +referential.common.Ocean.attribute.species=true referential.common.Species.attribute.ocean=true referential.common.SpeciesGroup.attribute.speciesGroupReleaseMode=true referential.common.SpeciesList.attribute.species=true ===================================== model/src/main/models/Observe/dto/class/i18nLabels.properties ===================================== @@ -103,7 +103,7 @@ referential.common.Harbour=coordinate,country,latitude,locode,longitude,quadrant referential.common.LengthFormulaSupport=ocean,species,sex,coefficients,source,coefficientsInformation,generalTab,otherTab referential.common.LengthLengthParameter=inputOutputFormula,inputSizeMeasureType,outputInputFormula,outputSizeMeasureType,inputOutputFormulaInformation,outputInputFormulaInformation referential.common.LengthWeightParameter=lengthWeightFormula,meanLength,meanWeight,sizeMeasureType,weightLengthFormula,lengthWeightFormulaInformation,meanValues,weightLengthFormulaInformation -referential.common.Ocean=northEastAllowed,southEastAllowed,southWestAllowed,northWestAllowed,atLeastOneSelected,validation.atLeastOneSelected +referential.common.Ocean=northEastAllowed,southEastAllowed,southWestAllowed,northWestAllowed,atLeastOneSelected,validation.atLeastOneSelected,generalTab,species,species.available,species.selected referential.common.Organism=country,description referential.common.Person=captain,country,dataEntryOperator,dataSource,firstName,lastName,observer,psSampler,atLeastOneSelected,validation.atLeastOneSelected referential.common.ShipOwner=country,endDate,label,startDate ===================================== toolkit/persistence/src/main/java/fr/ird/observe/spi/context/DtoEntityContext.java ===================================== @@ -308,7 +308,9 @@ public interface DtoEntityContext< } default Set<D> loadEntitiesToDto(ServiceContext context) { - return toDtoSet(context.getReferentialLocale(), getDao(context).streamAll()); + try (Stream<E> stream = getDao(context).streamAll()) { + return toDtoSet(context.getReferentialLocale(), stream); + } } default Stream<E> loadEntities(ServiceContext context, Predicate<E> predicate) { ===================================== toolkit/persistence/src/main/java/fr/ird/observe/spi/context/DtoEntityContextSupport.java ===================================== @@ -131,10 +131,12 @@ public abstract class DtoEntityContextSupport< public DtoReferenceCollection<R> toReferenceSet(ServiceContext context, Date now) { ReferentialLocale referentialLocale = context.getReferentialLocale(); Set<R> references = new LinkedHashSet<>(); - getDao(context).streamAll().forEach(entity -> { - R reference = toReference(referentialLocale, entity); - references.add(reference); - }); + try (Stream<E> stream = getDao(context).streamAll()) { + stream.forEach(entity -> { + R reference = toReference(referentialLocale, entity); + references.add(reference); + }); + } return createReferenceSet(referentialLocale, references, now); } @@ -230,10 +232,12 @@ public abstract class DtoEntityContextSupport< public final Map<String, R> uniqueIndex(ServiceContext context) { Map<String, R> references = new LinkedHashMap<>(); ReferentialLocale referentialLocale = context.getReferentialLocale(); - getDao(context).streamAll().forEach(entity -> { - R reference = toReference(referentialLocale, entity); - references.put(reference.getId(), reference); - }); + try (Stream<E> stream = getDao(context).streamAll()) { + stream.forEach(entity -> { + R reference = toReference(referentialLocale, entity); + references.put(reference.getId(), reference); + }); + } return references; } ===================================== toolkit/persistence/src/main/java/fr/ird/observe/spi/context/ReferentialDtoEntityContext.java ===================================== @@ -89,6 +89,16 @@ public abstract class ReferentialDtoEntityContext< this.extraScripts = SingletonSupplier.of(this::loadExtraScripts); } + @Override + public Form<D> loadForm(ServiceContext context, String id) { + E entity = loadEntity(context, id); + return referentialEntityToForm(context.getReferentialLocale(), entity); + } + + public SaveResultDto saveEntity(ServiceContext context, E entity, D dto) { + return saveEntity(context, entity); + } + public Optional<ReplaceReferentialScript> getReplaceScript() { return getSqlScript().getReplaceReferentialScript(); } @@ -182,12 +192,6 @@ public abstract class ReferentialDtoEntityContext< return ReferentialDtoReferenceSet.of(toReferenceType(), references, now); } - @Override - public final Form<D> loadForm(ServiceContext context, String id) { - E entity = loadEntity(context, id); - return referentialEntityToForm(context.getReferentialLocale(), entity); - } - public final LinkedHashSet<E> toReferentialEntitySet(Collection<R> references) { LinkedHashSet<E> entityList = null; if (references != null && !references.isEmpty()) { @@ -274,7 +278,7 @@ public abstract class ReferentialDtoEntityContext< E entity = loadOrCreateEntityFromReferentialDto(context, dto); checkLastUpdateDate(context, entity, dto); fromDto(context.getReferentialLocale(), entity, dto); - return saveEntity(context, entity); + return saveEntity(context, entity, dto); } private SqlScript generateDuplicateScript(ServiceContext context, String id, String newId) { View it on GitLab: https://gitlab.com/ultreiaio/ird-observe/-/compare/0d5a7baa5292d12395407f228... -- View it on GitLab: https://gitlab.com/ultreiaio/ird-observe/-/compare/0d5a7baa5292d12395407f228... You're receiving this email because of your account on gitlab.com.