diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 46b21a2c9..c9063bac4 100755 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -118,4 +118,4 @@ - \ No newline at end of file + diff --git a/core/application/src/main/java/org/hesperides/core/application/events/EventUseCases.java b/core/application/src/main/java/org/hesperides/core/application/events/EventUseCases.java index 702e0d519..417c654df 100644 --- a/core/application/src/main/java/org/hesperides/core/application/events/EventUseCases.java +++ b/core/application/src/main/java/org/hesperides/core/application/events/EventUseCases.java @@ -83,13 +83,13 @@ private static Module.Key parseModuleKey(String streamName, String moduleName) { public List getEvents(TemplateContainer.Key key, Integer page, Integer size) { return moduleQueries.getOptionalModuleId(key) - .map(moduleId -> eventQueries.getEvents(moduleId, page, size)) + .map(moduleId -> eventQueries.getLastToFirstEvents(moduleId, page, size)) .orElseGet(Collections::emptyList); } public List getEvents(Platform.Key key, Integer page, Integer size) { return platformQueries.getOptionalPlatformId(key) - .map(platformId -> eventQueries.getEvents(platformId, page, size)) + .map(platformId -> eventQueries.getLastToFirstEvents(platformId, page, size)) .orElseGet(Collections::emptyList); } } diff --git a/core/application/src/main/java/org/hesperides/core/application/files/FileUseCases.java b/core/application/src/main/java/org/hesperides/core/application/files/FileUseCases.java index 8c7a32868..1a8714c3a 100644 --- a/core/application/src/main/java/org/hesperides/core/application/files/FileUseCases.java +++ b/core/application/src/main/java/org/hesperides/core/application/files/FileUseCases.java @@ -208,7 +208,7 @@ private static PropertyVisitorsSequence buildPropertyVisitorsSequence(PlatformVi boolean shouldHidePasswordProperties) { EnumSet propertiesToInclude = EnumSet.of(GLOBAL); - DeployedModuleView deployedModule = platform.getDeployedModule(modulePath, moduleKey); + DeployedModuleView deployedModule = platform.findActiveDeployedModuleByModulePathAndModuleKey(modulePath, moduleKey); PropertyVisitorsSequence firstPropertyVisitorsSequence = PropertyValuationBuilder.buildFirstPropertyVisitorsSequence( deployedModule, modulePropertiesModels, shouldHidePasswordProperties, propertiesToInclude); diff --git a/core/application/src/main/java/org/hesperides/core/application/platforms/PlatformUseCases.java b/core/application/src/main/java/org/hesperides/core/application/platforms/PlatformUseCases.java index 1adb99ed4..1a9f276fb 100755 --- a/core/application/src/main/java/org/hesperides/core/application/platforms/PlatformUseCases.java +++ b/core/application/src/main/java/org/hesperides/core/application/platforms/PlatformUseCases.java @@ -15,6 +15,7 @@ import org.hesperides.core.domain.modules.queries.ModuleQueries; import org.hesperides.core.domain.modules.queries.ModuleView; import org.hesperides.core.domain.platforms.PlatformCreatedEvent; +import org.hesperides.core.domain.platforms.PlatformPropertiesUpdatedEvent; import org.hesperides.core.domain.platforms.PlatformUpdatedEvent; import org.hesperides.core.domain.platforms.commands.PlatformCommands; import org.hesperides.core.domain.platforms.entities.DeployedModule; @@ -47,20 +48,23 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; -import org.springframework.util.CollectionUtils; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.Collections.emptyList; +import static java.util.Comparator.comparing; import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.*; import static org.apache.commons.lang3.StringUtils.isEmpty; import static org.apache.logging.log4j.util.Strings.isBlank; import static org.hesperides.core.application.platforms.properties.PropertyType.GLOBAL; import static org.hesperides.core.application.platforms.properties.PropertyType.WITHOUT_MODEL; import static org.hesperides.core.application.platforms.properties.PropertyValuationBuilder.buildPropertyVisitorsSequenceForGlobals; +import static org.hesperides.core.domain.platforms.entities.Platform.GLOBAL_PROPERTIES_PATH; import static org.hesperides.core.domain.platforms.queries.views.properties.AbstractValuedPropertyView.excludeUnusedValues; +import static org.springframework.util.CollectionUtils.isEmpty; @Component @Slf4j @@ -98,7 +102,7 @@ public PlatformUseCases(PlatformCommands platformCommands, } public String createPlatform(Platform platform, User user) { - if (platform.isProductionPlatform() && !user.hasProductionRoleForApplication(platform.getKey().getApplicationName())) { + if (isRestrictedPlatform(user, platform)) { throw new ForbiddenOperationException("Creating a production platform is reserved to production role"); } if (platformQueries.platformExists(platform.getKey())) { @@ -116,13 +120,13 @@ public String copyPlatform(Platform newPlatform, Platform.Key existingPlatformKe if ((newPlatform.isProductionPlatform() || existingPlatform.isProductionPlatform()) && !user.hasProductionRoleForApplication(newPlatform.getKey().getApplicationName())) { throw new ForbiddenOperationException("Creating a platform from a production platform is reserved to production role"); } - List deployedModules = DeployedModuleView.toDomainDeployedModules(existingPlatform.getActiveDeployedModules()); + List deployedModules = DeployedModuleView.toDomainDeployedModules(existingPlatform.findActiveDeployedModules()); if (!copyInstancesAndProperties) { deployedModules = deployedModules.stream() .map(DeployedModule::copyWithoutInstancesNorProperties) - .collect(Collectors.toList()); + .collect(toList()); } - List globalProperties = copyInstancesAndProperties ? ValuedPropertyView.toDomainValuedProperties(existingPlatform.getGlobalProperties()) : Collections.emptyList(); + List globalProperties = copyInstancesAndProperties ? ValuedPropertyView.toDomainValuedProperties(existingPlatform.getGlobalProperties()) : emptyList(); // cf. createPlatformFromExistingPlatform in https://github.com/voyages-sncf-technologies/hesperides/blob/fix/3.0.3/src/main/java/com/vsct/dt/hesperides/applications/AbstractApplicationsAggregate.java#L156 Platform newFullPlatform = new Platform( newPlatform.getKey(), @@ -149,15 +153,14 @@ public PlatformView getPlatform(Platform.Key platformKey, boolean withPasswordFl PlatformView platform = platformQueries.getOptionalPlatform(platformKey) .orElseThrow(() -> new PlatformNotFoundException(platformKey)); if (withPasswordFlag) { - boolean hasPasswords = !CollectionUtils.isEmpty(getPlatformsWithPassword(Collections.singletonList(platform))); + boolean hasPasswords = !isEmpty(getPlatformsWithPassword(Collections.singletonList(platform))); platform = platform.withPasswordIndicator(hasPasswords); } return platform; } public PlatformView getPlatformAtPointInTime(Platform.Key platformKey, long timestamp) { - String platformId = platformQueries.getOptionalPlatformId(platformKey) - .orElseThrow(() -> new PlatformNotFoundException(platformKey)); + String platformId = findPlatformId(platformKey); return platformQueries.getPlatformAtPointInTime(platformId, timestamp); } @@ -181,7 +184,7 @@ public void updatePlatform(Platform.Key platformKey, Platform newPlatform, boole public void deletePlatform(Platform.Key platformKey, User user) { PlatformView platform = platformQueries.getOptionalPlatform(platformKey) .orElseThrow(() -> new PlatformNotFoundException(platformKey)); - if (platform.isProductionPlatform() && !user.hasProductionRoleForApplication(platformKey.getApplicationName())) { + if (isRestrictedPlatform(user, platform)) { throw new ForbiddenOperationException("Deleting a production platform is reserved to production role"); } platformCommands.deletePlatform(platform.getId(), platformKey, user); @@ -211,7 +214,7 @@ private Set getPlatformsWithPassword(List platforms) .flatMap(List::stream) .map(DeployedModuleView::getModuleKey) .distinct() - .collect(Collectors.toList()); + .collect(toList()); List allPlatformsModules = moduleQueries.getModulesWithin(allPlatformsModuleKeys); List modulesWithPassword = moduleQueries.getModulesWithPasswordWithin(allPlatformsModuleKeys); @@ -224,7 +227,7 @@ private Set getPlatformsWithPassword(List platforms) .map(TechnoView::getKey) .map(TemplateContainerKeyView::toTechnoKey) .distinct() - .collect(Collectors.toList()); + .collect(toList()); Map> technoKeysByModuleMap = allPlatformsModules.stream().collect(toMap( ModuleView::getKey, @@ -232,7 +235,7 @@ private Set getPlatformsWithPassword(List platforms) .map(TechnoView::getKey) .map(TemplateContainerKeyView::toTechnoKey) .distinct() - .collect(Collectors.toList()) + .collect(toList()) )); List technosWithPassword = technoQueries.getTechnosWithPasswordWithin(allModulesTechnoKeys); @@ -250,7 +253,7 @@ private Set getPlatformsWithPassword(List platforms) platform -> platform.getDeployedModules().stream() .map(DeployedModuleView::getModuleKey) .distinct() - .collect(Collectors.toList()) + .collect(toList()) )); return moduleKeysByPlatformMap.entrySet().stream() @@ -287,7 +290,7 @@ public static Long getPropertiesVersionId(PlatformView platform, String properti if (Platform.isGlobalPropertiesPath(propertiesPath)) { propertiesVersionId = platform.getGlobalPropertiesVersionId(); } else { - propertiesVersionId = platform.getActiveDeployedModules() + propertiesVersionId = platform.findActiveDeployedModules() .filter(deployedModule -> deployedModule.getPropertiesPath().equals(propertiesPath)) .findFirst() .orElseThrow(() -> new DeployedModuleNotFoundException(platform.getPlatformKey(), propertiesPath)) @@ -336,8 +339,8 @@ public PropertiesDiff getPropertiesDiff(Platform.Key fromPlatformKey, List fromModulePropertiesModels = moduleQueries.getPropertiesModel(fromModuleKey); List toModulePropertiesModels = moduleQueries.getPropertiesModel(toModuleKey); - boolean fromShouldHidePasswordProperties = fromPlatform.isProductionPlatform() && !user.hasProductionRoleForApplication(fromPlatformKey.getApplicationName()); - boolean toShouldHidePasswordProperties = toPlatform.isProductionPlatform() && !user.hasProductionRoleForApplication(toPlatformKey.getApplicationName()); + boolean fromShouldHidePasswordProperties = isRestrictedPlatform(user, fromPlatform); + boolean toShouldHidePasswordProperties = isRestrictedPlatform(user, toPlatform); PropertyVisitorsSequence fromPropertyVisitors = buildModulePropertyVisitorsSequence( fromPlatform, fromModulePath, fromModuleKey, @@ -369,7 +372,7 @@ private static PropertyVisitorsSequence buildModulePropertyVisitorsSequence(Plat boolean shouldHidePasswordProperties) { EnumSet propertiesToInclude = EnumSet.of(GLOBAL, WITHOUT_MODEL); - DeployedModuleView deployedModule = platform.getDeployedModule(modulePath, moduleKey); + DeployedModuleView deployedModule = platform.findActiveDeployedModuleByModulePathAndModuleKey(modulePath, moduleKey); PropertyVisitorsSequence firstPropertyVisitorsSequence = PropertyValuationBuilder.buildFirstPropertyVisitorsSequence( deployedModule, modulePropertiesModels, shouldHidePasswordProperties, propertiesToInclude); @@ -407,7 +410,7 @@ public List getValuedProperties(Platform.Key platfor // Note: on devrait passer le timestamp aux 2 appels ci-dessous, cf. issue #724 List modulePropertiesModel = moduleQueries.getPropertiesModel(moduleKey); - properties = platform.getDeployedModule(propertiesPath).getValuedProperties(); + properties = platform.findActiveDeployedModuleByPropertiesPath(propertiesPath).getValuedProperties(); // On exclue les propriétés non valorisées ayant une valeur par défaut properties = AbstractValuedPropertyView.excludePropertiesWithOnlyDefaultValue(properties, modulePropertiesModel); @@ -416,7 +419,7 @@ public List getValuedProperties(Platform.Key platfor // surtout si c'est pour refaire une requête pour récupérer les propriétés // => Créer une requête isProductionPlatform ou réutiliser la plateforme // pour récupérer les propriétés - if (platform.isProductionPlatform() && !user.hasProductionRoleForApplication(platformKey.getApplicationName())) { + if (isRestrictedPlatform(user, platform)) { properties = AbstractValuedPropertyView.hidePasswordProperties(properties, modulePropertiesModel); } } @@ -427,7 +430,7 @@ public Map> getGlobalPropertiesUsage(Platfo PlatformView platform = platformQueries.getOptionalPlatform(platformKey).orElseThrow(() -> new PlatformNotFoundException(platformKey)); // On ne tient compte que des modules utilisés dans la platforme (pas des modules sauvegardés) - List deployedModules = platform.getActiveDeployedModules().collect(Collectors.toList()); + List deployedModules = platform.findActiveDeployedModules().collect(toList()); List moduleKeys = platform.getActiveDeployedModulesKeys(); List modulesProperties = moduleQueries.getModulesProperties(moduleKeys); @@ -452,7 +455,7 @@ public List saveProperties(Platform.Key platformKey, String userComment, User user) { PlatformView platform = getPlatform(platformKey); - if (platform.isProductionPlatform() && !user.hasProductionRoleForApplication(platformKey.getApplicationName())) { + if (isRestrictedPlatform(user, platform)) { throw new ForbiddenOperationException("Setting properties of a production platform is reserved to production role"); } @@ -524,12 +527,12 @@ public List getAllApplicationsDetail(boolean withPasswordFlag) .map(ApplicationView::getPlatforms) .flatMap(List::stream) .distinct() - .collect(Collectors.toList()); + .collect(toList()); Set platformsWithPassword = getPlatformsWithPassword(allApplicationsPlatforms); applications = applications.stream() .map(application -> application.withPasswordIndicator(platformsWithPassword)) - .collect(Collectors.toList()); + .collect(toList()); } return applications; @@ -537,14 +540,14 @@ public List getAllApplicationsDetail(boolean withPasswordFlag) public void purgeUnusedProperties(Platform.Key platformKey, String propertiesPath, User user) { PlatformView platform = getPlatform(platformKey); - if (platform.isProductionPlatform() && !user.hasProductionRoleForApplication(platformKey.getApplicationName())) { + if (isRestrictedPlatform(user, platform)) { throw new ForbiddenOperationException("Cleaning properties of a production platform is reserved to production role"); } if (Platform.isGlobalPropertiesPath(propertiesPath)) { throw new IllegalArgumentException("Cleaning only works for module properties, not for global properties"); } - DeployedModuleView deployedModule = platform.getDeployedModule(propertiesPath); + DeployedModuleView deployedModule = platform.findActiveDeployedModuleByPropertiesPath(propertiesPath); Module.Key moduleKey = Module.Key.fromPropertiesPath(propertiesPath); List propertiesModel = moduleQueries.getPropertiesModel(moduleKey); @@ -554,7 +557,7 @@ public void purgeUnusedProperties(Platform.Key platformKey, String propertiesPat List filteredValuedProperties = excludeUnusedValues(baseValues, propertiesModel, referencedProperties) .map(AbstractValuedPropertyView::toDomainValuedProperty) .map(AbstractValuedProperty.class::cast) - .collect(Collectors.toList()); + .collect(toList()); Long propertiesVersionId = getPropertiesVersionId(platform, propertiesPath); @@ -565,7 +568,7 @@ public void purgeUnusedProperties(Platform.Key platformKey, String propertiesPat public PlatformDetailedPropertiesView getDetailedProperties(Platform.Key platformKey, String propertiesPath, User user) { PlatformView platform = getPlatform(platformKey); - boolean hidePasswords = platform.isProductionPlatform() && !user.hasProductionRoleForApplication(platform.getPlatformKey().getApplicationName()); + boolean hidePasswords = isRestrictedPlatform(user, platform); List moduleKeys = platform.getActiveDeployedModulesKeys(propertiesPath); Map> propertiesByModuleKey = moduleQueries.getModulesProperties(moduleKeys).stream() .collect(toMap(ModulePropertiesView::getModuleKey, ModulePropertiesView::getProperties)); @@ -575,7 +578,7 @@ public PlatformDetailedPropertiesView getDetailedProperties(Platform.Key platfor List globalProperties = globalPropertyVisitorsSequence.toGlobalDetailedProperties(); // Propriétés détaillées de chaque module de la plateforme - List modulesDetailedProperties = platform.getActiveDeployedModules() + List modulesDetailedProperties = platform.findActiveDeployedModules() .filter(deployedModule -> isEmpty(propertiesPath) || deployedModule.getPropertiesPath().equals(propertiesPath)) .flatMap(deployedModule -> { List propertiesModel = propertiesByModuleKey.get(deployedModule.getModuleKey()); @@ -590,7 +593,7 @@ public PlatformDetailedPropertiesView getDetailedProperties(Platform.Key platfor List valuedProperties = deployedModule.getValuedProperties(); Set referencedProperties = propertyReferenceScanner.findAll(valuedProperties, deployedModule.getInstances()); List filteredValuedProperties = excludeUnusedValues(valuedProperties, propertiesModel, referencedProperties) - .collect(Collectors.toList()); + .collect(toList()); Set unusedProperties = valuedProperties.stream() .filter(valuedProperty -> !filteredValuedProperties.contains(valuedProperty)) .map(AbstractValuedPropertyView::getName) @@ -599,7 +602,7 @@ public PlatformDetailedPropertiesView getDetailedProperties(Platform.Key platfor return propertyVisitorsSequence.toModuleDetailedProperties( deployedModule.getPropertiesPath(), globalProperties, unusedProperties); }) - .collect(Collectors.toList()); + .collect(toList()); return new PlatformDetailedPropertiesView( platform.getApplicationName(), @@ -645,8 +648,7 @@ public List getPlatformEvents(Platform.Key platformKey, Integ // les derniers évènements en date et cette logique ne s'applique a // priori que dans le cas des mises à jour d'une plateforme. - String platformId = platformQueries.getOptionalPlatformId(platformKey) - .orElseThrow(() -> new PlatformNotFoundException(platformKey)); + String platformId = findPlatformId(platformKey); List platformEvents = new ArrayList<>(); List rawEvents = new ArrayList<>(); @@ -654,12 +656,12 @@ public List getPlatformEvents(Platform.Key platformKey, Integ // plateforme à retourner, tenant compte de la pagination for (int queryPage = 1; platformEvents.size() < (clientPage * size); queryPage++) { - List newRawEvents = eventQueries.getEventsByTypes( + List newRawEvents = eventQueries.getLastToFirstEventsByTypes( platformId, new Class[]{PlatformCreatedEvent.class, PlatformUpdatedEvent.class}, queryPage, eventsQuerySizeFactor * size); - if (CollectionUtils.isEmpty(newRawEvents)) { + if (isEmpty(newRawEvents)) { break; } rawEvents.addAll(newRawEvents); @@ -670,9 +672,79 @@ public List getPlatformEvents(Platform.Key platformKey, Integ // Tri et pagination appliqués à la toute fin return platformEvents .stream() - .sorted(Comparator.comparing(PlatformEventView::getTimestamp).reversed()) + .sorted(comparing(PlatformEventView::getTimestamp).reversed()) .skip((clientPage - 1) * size) .limit(size) - .collect(Collectors.toList()); + .collect(toList()); + } + + public List getPropertiesEvents(User user, + Platform.Key platformKey, + String propertiesPath, + Integer page, + Integer size) { + + String platformId = findPlatformId(platformKey); + boolean isModuleProperties = !GLOBAL_PROPERTIES_PATH.equals(propertiesPath); + boolean isProductionPlatform = isModuleProperties && platformQueries.isProductionPlatform(platformId); + List propertiesEvents = new ArrayList<>(); + + boolean hidePasswords = isModuleProperties && isRestrictedPlatform(isProductionPlatform, user, platformKey.getApplicationName()); + List events = isModuleProperties + ? eventQueries.getLastToFirstPlatformModulePropertiesUpdatedEvents(platformId, propertiesPath, page, size) + : eventQueries.getLastToFirstEventsByType(platformId, PlatformPropertiesUpdatedEvent.class, page, size); + + if (!isEmpty(events)) { + // On récupère le premier évènement de la page suivante pour faire + // la comparaison avec le dernier élément de la liste en cours + Optional firstEventOfNextPage = (isModuleProperties + ? eventQueries.getLastToFirstPlatformModulePropertiesUpdatedEvents(platformId, propertiesPath, page + 1, size) + : eventQueries.getLastToFirstEventsByType(platformId, PlatformPropertiesUpdatedEvent.class, page + 1, size) + ).stream().findFirst(); + // On en profite pour déterminer si le premier évènement + // en date fait partie de la liste en cours + boolean containsFirstEvent = true; + if (firstEventOfNextPage.isPresent()) { + events.add(firstEventOfNextPage.get()); + containsFirstEvent = false; + } + + List passwordProperties = findPasswordProperties(hidePasswords, propertiesPath); + propertiesEvents = PropertiesEventView.buildPropertiesEvents(events, isModuleProperties, containsFirstEvent).stream() + .map(propertiesEvent -> hidePasswords ? propertiesEvent.hidePasswords(passwordProperties) : propertiesEvent) + .sorted(comparing(PropertiesEventView::getTimestamp).reversed()) + .collect(toList()); + } + return propertiesEvents; + } + + private List findPasswordProperties(boolean hidePasswords, String propertiesPath) { + List passwordProperties = new ArrayList<>(); + if (hidePasswords) { + Module.Key moduleKey = Module.Key.fromPropertiesPath(propertiesPath); + List moduleProperties = moduleQueries.getPropertiesModel(moduleKey); + passwordProperties = AbstractPropertyView.getAllSimpleProperties(moduleProperties) + .filter(PropertyView::isPassword) + .map(PropertyView::getName) + .collect(toUnmodifiableList()); + } + return passwordProperties; + } + + private static boolean isRestrictedPlatform(User user, PlatformView platform) { + return isRestrictedPlatform(platform.isProductionPlatform(), user, platform.getApplicationName()); + } + + private static boolean isRestrictedPlatform(User user, Platform platform) { + return isRestrictedPlatform(platform.isProductionPlatform(), user, platform.getKey().getApplicationName()); + } + + private static boolean isRestrictedPlatform(boolean isProductionPlatform, User user, String applicationName) { + return isProductionPlatform && !user.hasProductionRoleForApplication(applicationName); + } + + private String findPlatformId(Platform.Key platformKey) { + return platformQueries.getOptionalPlatformId(platformKey) + .orElseThrow(() -> new PlatformNotFoundException(platformKey)); } } diff --git a/core/domain/src/main/java/org/hesperides/core/domain/events/EventRepository.java b/core/domain/src/main/java/org/hesperides/core/domain/events/EventRepository.java index 368113199..77b9a5562 100644 --- a/core/domain/src/main/java/org/hesperides/core/domain/events/EventRepository.java +++ b/core/domain/src/main/java/org/hesperides/core/domain/events/EventRepository.java @@ -8,7 +8,10 @@ public interface EventRepository { @QueryHandler - List onGetEventsQuery(GetEventsQuery query); + List onGetLastToFirstEventsQuery(GetLastToFirstEventsQuery query); + + @QueryHandler + List onGetLastToFirstPlatformModulePropertiesUpdatedEvents(GetLastToFirstPlatformModulePropertiesUpdatedEvents query); void cleanAggregateEvents(String aggregateIdentifier); } diff --git a/core/domain/src/main/java/org/hesperides/core/domain/events/queries/EventQueries.java b/core/domain/src/main/java/org/hesperides/core/domain/events/queries/EventQueries.java index f33db5b6c..7a19506d6 100644 --- a/core/domain/src/main/java/org/hesperides/core/domain/events/queries/EventQueries.java +++ b/core/domain/src/main/java/org/hesperides/core/domain/events/queries/EventQueries.java @@ -2,7 +2,8 @@ import org.axonframework.queryhandling.QueryGateway; import org.hesperides.commons.axon.AxonQueries; -import org.hesperides.core.domain.events.GetEventsQuery; +import org.hesperides.core.domain.events.GetLastToFirstEventsQuery; +import org.hesperides.core.domain.events.GetLastToFirstPlatformModulePropertiesUpdatedEvents; import org.springframework.stereotype.Component; import java.util.List; @@ -15,11 +16,19 @@ protected EventQueries(QueryGateway queryGateway) { super(queryGateway); } - public List getEvents(String aggregateId, Integer page, Integer size) { - return getEventsByTypes(aggregateId, new Class[0], page, size); + public List getLastToFirstEvents(String aggregateId, Integer page, Integer size) { + return getLastToFirstEventsByTypes(aggregateId, new Class[0], page, size); } - public List getEventsByTypes(String aggregateId, Class[] eventTypes, Integer page, Integer size) { - return querySyncList(new GetEventsQuery(aggregateId, eventTypes, page, size), EventView.class); + public List getLastToFirstEventsByType(String aggregateId, Class eventType, Integer page, Integer size) { + return getLastToFirstEventsByTypes(aggregateId, new Class[]{eventType}, page, size); + } + + public List getLastToFirstEventsByTypes(String aggregateId, Class[] eventTypes, Integer page, Integer size) { + return querySyncList(new GetLastToFirstEventsQuery(aggregateId, eventTypes, page, size), EventView.class); + } + + public List getLastToFirstPlatformModulePropertiesUpdatedEvents(String aggregateId, String propertiesPath, Integer page, Integer size) { + return querySyncList(new GetLastToFirstPlatformModulePropertiesUpdatedEvents(aggregateId, propertiesPath, page, size), EventView.class); } } diff --git a/core/domain/src/main/java/org/hesperides/core/domain/platforms/PlatformProjectionRepository.java b/core/domain/src/main/java/org/hesperides/core/domain/platforms/PlatformProjectionRepository.java index cdc83d9cf..356e4b517 100644 --- a/core/domain/src/main/java/org/hesperides/core/domain/platforms/PlatformProjectionRepository.java +++ b/core/domain/src/main/java/org/hesperides/core/domain/platforms/PlatformProjectionRepository.java @@ -79,4 +79,7 @@ public interface PlatformProjectionRepository { @QueryHandler List onGetAllApplicationsDetailQuery(GetAllApplicationsDetailQuery query); + + @QueryHandler + Boolean onIsProductionPlatformQuery(IsProductionPlatformQuery query); } diff --git a/core/domain/src/main/java/org/hesperides/core/domain/platforms/entities/properties/AbstractValuedProperty.java b/core/domain/src/main/java/org/hesperides/core/domain/platforms/entities/properties/AbstractValuedProperty.java index a5f048fe5..a38eb0b7d 100644 --- a/core/domain/src/main/java/org/hesperides/core/domain/platforms/entities/properties/AbstractValuedProperty.java +++ b/core/domain/src/main/java/org/hesperides/core/domain/platforms/entities/properties/AbstractValuedProperty.java @@ -34,7 +34,6 @@ @NonFinal public abstract class AbstractValuedProperty { String name; - //boolean notActiveForThisVersion; public static List filterAbstractValuedPropertyWithType(List properties, Class clazz) { return Optional.ofNullable(properties) @@ -54,4 +53,4 @@ public static List getFlatValuedProperties(final List flattenProperties(); -} \ No newline at end of file +} diff --git a/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/PlatformQueries.java b/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/PlatformQueries.java index e0c17f8f0..ada7bd6a0 100644 --- a/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/PlatformQueries.java +++ b/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/PlatformQueries.java @@ -115,4 +115,8 @@ public boolean applicationExists(String applicationName) { public List getAllApplicationsDetail() { return querySyncList(new GetAllApplicationsDetailQuery(), ApplicationView.class); } + + public boolean isProductionPlatform(String platformId) { + return querySync(new IsProductionPlatformQuery(platformId), Boolean.class); + } } diff --git a/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/PlatformView.java b/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/PlatformView.java index 0e8c11fbc..401f05318 100755 --- a/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/PlatformView.java +++ b/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/PlatformView.java @@ -80,22 +80,22 @@ static List setPlatformsWithPasswordIndicator(List p .collect(Collectors.toList()); } - public Stream getActiveDeployedModules() { + public Stream findActiveDeployedModules() { return Optional.ofNullable(deployedModules) .orElse(Collections.emptyList()) .stream().filter(deployedModule -> deployedModule.getId() != null && deployedModule.getId() > 0); } - public DeployedModuleView getDeployedModule(String modulePath, Module.Key moduleKey) { - return getActiveDeployedModules() + public DeployedModuleView findActiveDeployedModuleByModulePathAndModuleKey(String modulePath, Module.Key moduleKey) { + return findActiveDeployedModules() .filter(deployedModule -> deployedModule.getModulePath().equalsIgnoreCase(modulePath) && deployedModule.getModuleKey().equals(moduleKey)) .findFirst() .orElseThrow(() -> new ModuleNotFoundException(moduleKey, modulePath)); } - public DeployedModuleView getDeployedModule(String propertiesPath) { - return getActiveDeployedModules() + public DeployedModuleView findActiveDeployedModuleByPropertiesPath(String propertiesPath) { + return findActiveDeployedModules() .filter(deployedModule -> deployedModule.getPropertiesPath().equals(propertiesPath)) .findAny().orElseThrow(() -> new DeployedModuleNotFoundException(getPlatformKey(), propertiesPath)); } @@ -124,7 +124,7 @@ public List getActiveDeployedModulesKeys() { } public List getActiveDeployedModulesKeys(String propertiesPath) { - return getActiveDeployedModules() + return findActiveDeployedModules() .filter(deployedModule -> isEmpty(propertiesPath) || deployedModule.getPropertiesPath().equals(propertiesPath)) .map(DeployedModuleView::getModuleKey) .collect(Collectors.toList()); diff --git a/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/PropertiesEventView.java b/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/PropertiesEventView.java new file mode 100644 index 000000000..64dd2ab65 --- /dev/null +++ b/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/PropertiesEventView.java @@ -0,0 +1,174 @@ +package org.hesperides.core.domain.platforms.queries.views; + +import lombok.AllArgsConstructor; +import lombok.Value; +import org.hesperides.core.domain.events.queries.EventView; +import org.hesperides.core.domain.platforms.PlatformModulePropertiesUpdatedEvent; +import org.hesperides.core.domain.platforms.PlatformPropertiesUpdatedEvent; +import org.hesperides.core.domain.platforms.entities.properties.AbstractValuedProperty; +import org.hesperides.core.domain.platforms.entities.properties.ValuedProperty; + +import java.time.Instant; +import java.util.*; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toList; +import static org.hesperides.core.domain.platforms.queries.views.properties.ValuedPropertyView.OBFUSCATED_PASSWORD_VALUE; +import static org.springframework.util.CollectionUtils.isEmpty; + +@Value +@AllArgsConstructor +public class PropertiesEventView { + Instant timestamp; + String author; + String comment; + List addedProperties; + List updatedProperties; + List removedProperties; + + public PropertiesEventView(EventView event, + List addedProperties, + List updatedProperties, + List removedProperties) { + timestamp = event.getTimestamp(); + author = event.getData().getUser(); + comment = event.getData() instanceof PlatformModulePropertiesUpdatedEvent ? ((PlatformModulePropertiesUpdatedEvent) event.getData()).getUserComment() : null; + this.addedProperties = addedProperties; + this.updatedProperties = updatedProperties; + this.removedProperties = removedProperties; + } + + public static List buildPropertiesEvents(List providedEvents, boolean isModuleProperties, boolean containsFirstEvent) { + List propertiesEvents = new ArrayList<>(); + + if (!isEmpty(providedEvents)) { + // L'algorithme dépend du tri dans l'ordre chronologique + providedEvents.sort(Comparator.comparing(EventView::getTimestamp)); + Iterator eventsIterator = providedEvents.iterator(); + + EventView previousEvent = eventsIterator.next(); // On a besoin de conserver `currentEvent` pour récupérer le timestamp + + List simpleValuedProperties = isModuleProperties + ? extractSimpleValuedProperties(((PlatformModulePropertiesUpdatedEvent) previousEvent.getData()).getValuedProperties()) + : ((PlatformPropertiesUpdatedEvent) previousEvent.getData()).getValuedProperties(); + + if (containsFirstEvent) { + // Premier évènement + List firstAddedProperties = simpleValuedProperties.stream() + .map(ValuedPropertyView::new) + .collect(toList()); + propertiesEvents.add(new PropertiesEventView(previousEvent, firstAddedProperties, emptyList(), emptyList())); + } + + while (eventsIterator.hasNext()) { + // On a besoin de conserver `currentEvent` pour le timestamp + EventView currentEvent = eventsIterator.next(); + // Si une propriété est dans l'évènement précédent mais pas dans l'évènement + // courant, c'est une propriété supprimée. Si elle n'est pas dans l'évènement + // précédent mais dans l'évènement courant, c'est une nouvelle propriété. Si + // elle est présente dans l'évènement précédent et l'évènement courant mais + // que sa valeur est différente, c'est une propriété mise à jour. + List addedProperties = new ArrayList<>(); + List updatedProperties = new ArrayList<>(); + List removedProperties = new ArrayList<>(); + + // Pour l'instant on ne traite que les propriétés simples + List previousValuedProperties = isModuleProperties + ? extractSimpleValuedProperties(((PlatformModulePropertiesUpdatedEvent) previousEvent.getData()).getValuedProperties()) + : ((PlatformPropertiesUpdatedEvent) previousEvent.getData()).getValuedProperties(); + List currentValuedProperties = isModuleProperties + ? extractSimpleValuedProperties(((PlatformModulePropertiesUpdatedEvent) currentEvent.getData()).getValuedProperties()) + : ((PlatformPropertiesUpdatedEvent) currentEvent.getData()).getValuedProperties(); + + Map previousPropertiesByName = simpleValuedPropertiesByName(previousValuedProperties); + Map currentPropertiesByName = simpleValuedPropertiesByName(currentValuedProperties); + + previousPropertiesByName.values().forEach(previousProperty -> { + ValuedProperty remainingProperty = currentPropertiesByName.getOrDefault(previousProperty.getName(), null); + if (remainingProperty == null) { + // Propriété supprimée + removedProperties.add(new ValuedPropertyView(previousProperty)); + } else if (!Objects.equals(previousProperty.getValue(), remainingProperty.getValue())) { + // Propriété modifiée + updatedProperties.add(new UpdatedPropertyView(previousProperty, remainingProperty)); + } + }); + + currentPropertiesByName.values().forEach(currentProperty -> { + if (!previousPropertiesByName.containsKey(currentProperty.getName())) { + // Nouvelle propriété + addedProperties.add(new ValuedPropertyView(currentProperty)); + } + }); + + if (!isEmpty(addedProperties) || !isEmpty(updatedProperties) || !isEmpty(removedProperties)) { + propertiesEvents.add(new PropertiesEventView(currentEvent, addedProperties, updatedProperties, removedProperties)); + } + + previousEvent = currentEvent; + } + } + return propertiesEvents; + } + + private static List extractSimpleValuedProperties(List valuedProperties) { + return valuedProperties.stream() + .filter(ValuedProperty.class::isInstance) + .map(ValuedProperty.class::cast) + .collect(toList()); + } + + private static Map simpleValuedPropertiesByName(List simpleValuedProperties) { + return simpleValuedProperties.stream() + .collect(Collectors.toMap(ValuedProperty::getName, identity())); + } + + public PropertiesEventView hidePasswords(List passwordProperties) { + List addedProperties = hideValuedPasswordProperties(passwordProperties, getAddedProperties()); + List updatedProperties = hideUpdatedPasswordProperties(passwordProperties, getUpdatedProperties()); + List removedProperties = hideValuedPasswordProperties(passwordProperties, getRemovedProperties()); + return new PropertiesEventView(timestamp, author, comment, addedProperties, updatedProperties, removedProperties); + } + + private static List hideValuedPasswordProperties(List passwordProperties, List valuedProperties) { + return valuedProperties.stream().map(valuedProperty -> passwordProperties.contains(valuedProperty.getName()) + ? new ValuedPropertyView(valuedProperty.getName(), OBFUSCATED_PASSWORD_VALUE) + : valuedProperty + ).collect(toList()); + } + + private static List hideUpdatedPasswordProperties(List passwordProperties, List updatedProperties) { + return updatedProperties.stream().map(updatedProperty -> passwordProperties.contains(updatedProperty.getName()) + ? new UpdatedPropertyView(updatedProperty.getName(), OBFUSCATED_PASSWORD_VALUE, OBFUSCATED_PASSWORD_VALUE) + : updatedProperty + ).collect(toList()); + } + + @Value + @AllArgsConstructor + public static class ValuedPropertyView { + String name; + String value; + + public ValuedPropertyView(ValuedProperty valuedProperty) { + name = valuedProperty.getName(); + value = valuedProperty.getValue(); + } + } + + @Value + @AllArgsConstructor + public static class UpdatedPropertyView { + String name; + String oldValue; + String newValue; + + public UpdatedPropertyView(ValuedProperty previousProperty, ValuedProperty remainingProperty) { + name = previousProperty.getName(); + oldValue = previousProperty.getValue(); + newValue = remainingProperty.getValue(); + } + } +} diff --git a/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/properties/ValuedPropertyView.java b/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/properties/ValuedPropertyView.java index cb2dc07ad..457b869af 100644 --- a/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/properties/ValuedPropertyView.java +++ b/core/domain/src/main/java/org/hesperides/core/domain/platforms/queries/views/properties/ValuedPropertyView.java @@ -27,11 +27,7 @@ import org.hesperides.core.domain.templatecontainers.queries.AbstractPropertyView; import org.hesperides.core.domain.templatecontainers.queries.PropertyView; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -39,7 +35,7 @@ @EqualsAndHashCode(callSuper = true) public class ValuedPropertyView extends AbstractValuedPropertyView { - private final static String OBFUSCATED_PASSWORD_VALUE = "********"; + public final static String OBFUSCATED_PASSWORD_VALUE = "********"; String value; diff --git a/core/domain/src/main/kotlin/org/hesperides/core/domain/events/Events.kt b/core/domain/src/main/kotlin/org/hesperides/core/domain/events/Events.kt index a5374cc07..f2afb8c7b 100644 --- a/core/domain/src/main/kotlin/org/hesperides/core/domain/events/Events.kt +++ b/core/domain/src/main/kotlin/org/hesperides/core/domain/events/Events.kt @@ -2,5 +2,5 @@ package org.hesperides.core.domain.events import org.hesperides.core.domain.security.UserEvent -data class GetEventsQuery(val aggregateIdentifier: String, val eventTypes: Array>, val page: Integer, val size: Integer) -data class CleanAggregateEventsQuery(val aggregateIdentifier: String) +data class GetLastToFirstEventsQuery(val aggregateIdentifier: String, val eventTypes: Array>, val page: Integer, val size: Integer) +data class GetLastToFirstPlatformModulePropertiesUpdatedEvents(val aggregateIdentifier: String, val propertiesPath: String, val page: Integer, val size: Integer) diff --git a/core/domain/src/main/kotlin/org/hesperides/core/domain/platforms/Platform.kt b/core/domain/src/main/kotlin/org/hesperides/core/domain/platforms/Platform.kt index 618dd4cae..aae9e6cf7 100644 --- a/core/domain/src/main/kotlin/org/hesperides/core/domain/platforms/Platform.kt +++ b/core/domain/src/main/kotlin/org/hesperides/core/domain/platforms/Platform.kt @@ -44,3 +44,4 @@ data class GetInstancesModelQuery(val platformKey: Platform.Key, val propertiesP data class InstanceExistsQuery(val platformKey: Platform.Key, val propertiesPath: String, val instanceName: String) data class ApplicationExistsQuery(val applicationName: String) class GetAllApplicationsDetailQuery +data class IsProductionPlatformQuery(val platformId: String) diff --git a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/events/MongoAxonEventRepository.java b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/events/MongoAxonEventRepository.java index eecd70514..71fb35076 100644 --- a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/events/MongoAxonEventRepository.java +++ b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/events/MongoAxonEventRepository.java @@ -3,21 +3,23 @@ import io.micrometer.core.annotation.Timed; import org.axonframework.queryhandling.QueryHandler; import org.hesperides.core.domain.events.EventRepository; -import org.hesperides.core.domain.events.GetEventsQuery; +import org.hesperides.core.domain.events.GetLastToFirstEventsQuery; +import org.hesperides.core.domain.events.GetLastToFirstPlatformModulePropertiesUpdatedEvents; import org.hesperides.core.domain.events.queries.EventView; +import org.hesperides.core.domain.platforms.PlatformModulePropertiesUpdatedEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; -import org.springframework.util.CollectionUtils; import java.util.List; -import java.util.stream.Collectors; import java.util.stream.Stream; +import static java.util.stream.Collectors.toList; import static org.hesperides.commons.SpringProfiles.FAKE_MONGO; import static org.hesperides.commons.SpringProfiles.MONGO; +import static org.springframework.util.CollectionUtils.isEmpty; @Profile({MONGO, FAKE_MONGO}) @Repository @@ -30,22 +32,38 @@ public MongoAxonEventRepository(MongoEventRepository mongoEventRepository) { this.mongoEventRepository = mongoEventRepository; } - @QueryHandler @Override @Timed - public List onGetEventsQuery(GetEventsQuery query) { - List payloadTypes = Stream.of(query.getEventTypes()).map(Class::getName).collect(Collectors.toList()); - Pageable pageable = query.getPage() > 0 && query.getSize() > 0 - ? PageRequest.of(query.getPage() - 1, query.getSize()) - : Pageable.unpaged(); + @QueryHandler + public List onGetLastToFirstEventsQuery(GetLastToFirstEventsQuery query) { + List payloadTypes = Stream.of(query.getEventTypes()) + .map(Class::getName) + .collect(toList()); - List events = CollectionUtils.isEmpty(payloadTypes) + Pageable pageable = buildPageable(query.getPage(), query.getSize()); + + List events = isEmpty(payloadTypes) ? mongoEventRepository.findAllByAggregateIdentifierOrderByTimestampDesc(query.getAggregateIdentifier(), pageable) : mongoEventRepository.findAllByAggregateIdentifierAndPayloadTypeInOrderByTimestampDesc(query.getAggregateIdentifier(), payloadTypes, pageable); return events.stream() .map(EventDocument::toEventView) - .collect(Collectors.toList()); + .collect(toList()); + } + + @Override + public List onGetLastToFirstPlatformModulePropertiesUpdatedEvents(GetLastToFirstPlatformModulePropertiesUpdatedEvents query) { + String propertiesPathPayload = "" + query.getPropertiesPath() + ""; + Pageable pageable = buildPageable(query.getPage(), query.getSize()); + + return mongoEventRepository.findAllByAggregateIdentifierAndPayloadTypeAndSerializedPayloadLikeOrderByTimestampDesc( + query.getAggregateIdentifier(), + PlatformModulePropertiesUpdatedEvent.class.getName(), + propertiesPathPayload, + pageable) + .stream() + .map(EventDocument::toEventView) + .collect(toList()); } @Override @@ -53,4 +71,10 @@ public List onGetEventsQuery(GetEventsQuery query) { public void cleanAggregateEvents(String aggregateIdentifier) { mongoEventRepository.deleteAllByAggregateIdentifier(aggregateIdentifier); } + + private static Pageable buildPageable(Integer page, Integer size) { + return page != null && size != null && page > 0 && size > 0 + ? PageRequest.of(page - 1, size) + : Pageable.unpaged(); + } } diff --git a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/events/MongoEventRepository.java b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/events/MongoEventRepository.java index 7d658d613..1b2100ee3 100644 --- a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/events/MongoEventRepository.java +++ b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/events/MongoEventRepository.java @@ -11,7 +11,11 @@ public interface MongoEventRepository extends MongoRepository findAllByAggregateIdentifierOrderByTimestampDesc(String aggregateIdentifier, Pageable pageable); - List findAllByAggregateIdentifierAndPayloadTypeInOrderByTimestampDesc(String aggregateIdentifier, List payloadTypes, Pageable pageable); + List findAllByAggregateIdentifierAndPayloadTypeInOrderByTimestampDesc( + String aggregateIdentifier, List payloadTypes, Pageable pageable); void deleteAllByAggregateIdentifier(String aggregateIdentifier); + + List findAllByAggregateIdentifierAndPayloadTypeAndSerializedPayloadLikeOrderByTimestampDesc( + String aggregateIdentifier, String payloadType, String serializedPayload, Pageable pageable); } diff --git a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/modules/ModuleDocument.java b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/modules/ModuleDocument.java index 0cc0db786..22fb4234d 100755 --- a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/modules/ModuleDocument.java +++ b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/modules/ModuleDocument.java @@ -113,7 +113,7 @@ public ModulePropertiesView toModulePropertiesView() { return new ModulePropertiesView( getDomainKey(), properties.stream() - .map(abstractPropertyDocument -> abstractPropertyDocument.toView()) + .map(AbstractPropertyDocument::toView) .collect(Collectors.toList())); } } diff --git a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/platforms/MongoPlatformProjectionRepository.java b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/platforms/MongoPlatformProjectionRepository.java index 75b2d61c8..4c2f8d0a6 100644 --- a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/platforms/MongoPlatformProjectionRepository.java +++ b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/platforms/MongoPlatformProjectionRepository.java @@ -38,6 +38,7 @@ import javax.annotation.PostConstruct; import java.time.Instant; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -417,9 +418,8 @@ public Boolean onApplicationExistsQuery(ApplicationExistsQuery query) { @Timed public List onGetAllApplicationsDetailQuery(GetAllApplicationsDetailQuery query) { - return Optional.ofNullable(platformRepository.findAll()) - .map(List::stream) - .orElse(Stream.empty()) + return Optional.ofNullable(platformRepository.findAll()).stream() + .flatMap(Collection::stream) .map(PlatformDocument::toPlatformView) .collect(groupingBy(PlatformView::getApplicationName)) .entrySet() @@ -429,6 +429,11 @@ public List onGetAllApplicationsDetailQuery(GetAllApplicationsD } + @Override + public Boolean onIsProductionPlatformQuery(IsProductionPlatformQuery query) { + return platformRepository.existsByIdAndIsProductionPlatform(query.getPlatformId(), true); + } + private PlatformDocument getPlatformAtPointInTime(String platformId, Long timestamp) { DomainEventStream eventStream = eventStorageEngine.readEvents(platformId).filter(domainEventMessage -> (timestamp == null || domainEventMessage.getTimestamp().toEpochMilli() < timestamp) diff --git a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/platforms/MongoPlatformRepository.java b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/platforms/MongoPlatformRepository.java index b4d384c89..bac0c6d6f 100644 --- a/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/platforms/MongoPlatformRepository.java +++ b/core/infrastructure/src/main/java/org/hesperides/core/infrastructure/mongo/platforms/MongoPlatformRepository.java @@ -59,4 +59,6 @@ public interface MongoPlatformRepository extends MongoRepository findGlobalPropertiesVersionIdByPlatformKey(PlatformKeyDocument platformKeyDocument); boolean existsByKeyApplicationName(String applicationName); + + boolean existsByIdAndIsProductionPlatform(String platformId, boolean isProductionPlatform); } diff --git a/core/presentation/src/main/java/org/hesperides/core/presentation/controllers/PlatformsController.java b/core/presentation/src/main/java/org/hesperides/core/presentation/controllers/PlatformsController.java index 6c6fa9983..946961020 100755 --- a/core/presentation/src/main/java/org/hesperides/core/presentation/controllers/PlatformsController.java +++ b/core/presentation/src/main/java/org/hesperides/core/presentation/controllers/PlatformsController.java @@ -193,7 +193,7 @@ public ResponseEntity> searchPlatforms(@RequestParam("a return ResponseEntity.ok(searchResultOutputs); } - @ApiOperation("Retrieve a platform events") + @ApiOperation("Retrieve platform events") @GetMapping("/{application_name}/platforms/{platform_name:.+}/events") public ResponseEntity> getPlatformEvents(@PathVariable("application_name") final String applicationName, @PathVariable("platform_name") final String platformName, diff --git a/core/presentation/src/main/java/org/hesperides/core/presentation/controllers/PropertiesController.java b/core/presentation/src/main/java/org/hesperides/core/presentation/controllers/PropertiesController.java index c05214522..bb1cd3184 100644 --- a/core/presentation/src/main/java/org/hesperides/core/presentation/controllers/PropertiesController.java +++ b/core/presentation/src/main/java/org/hesperides/core/presentation/controllers/PropertiesController.java @@ -9,11 +9,13 @@ import org.hesperides.core.domain.platforms.entities.Platform; import org.hesperides.core.domain.platforms.entities.properties.AbstractValuedProperty; import org.hesperides.core.domain.platforms.entities.properties.diff.PropertiesDiff; +import org.hesperides.core.domain.platforms.queries.views.PropertiesEventView; import org.hesperides.core.domain.platforms.queries.views.properties.AbstractValuedPropertyView; import org.hesperides.core.domain.platforms.queries.views.properties.GlobalPropertyUsageView; import org.hesperides.core.domain.platforms.queries.views.properties.PlatformDetailedPropertiesView; import org.hesperides.core.domain.security.entities.User; import org.hesperides.core.presentation.io.platforms.InstancesModelOutput; +import org.hesperides.core.presentation.io.platforms.PropertiesEventOutput; import org.hesperides.core.presentation.io.platforms.properties.GlobalPropertyUsageOutput; import org.hesperides.core.presentation.io.platforms.properties.PlatformDetailedPropertiesOutput; import org.hesperides.core.presentation.io.platforms.properties.PropertiesIO; @@ -172,7 +174,7 @@ public ResponseEntity cleanUnusedProperties(Authentication authentication, if (StringUtils.isEmpty(propertiesPath)) { // tous les modules - platformUseCases.getPlatform(platformKey).getActiveDeployedModules() + platformUseCases.getPlatform(platformKey).findActiveDeployedModules() .forEach(module -> platformUseCases.purgeUnusedProperties(platformKey, module.getPropertiesPath(), authenticatedUser)); } else { // un seul module @@ -194,4 +196,20 @@ public ResponseEntity getDetailedProperties(Au PlatformDetailedPropertiesOutput platformDetailedPropertiesOutput = new PlatformDetailedPropertiesOutput(platformDetailedPropertiesView); return ResponseEntity.ok(platformDetailedPropertiesOutput); } + + @ApiOperation("Retrieve properties events") + @GetMapping("/{application_name}/platforms/{platform_name:.+}/properties/events") + public ResponseEntity> getPropertiesEvents(Authentication authentication, + @PathVariable("application_name") final String applicationName, + @PathVariable("platform_name") final String platformName, + @RequestParam(value = "properties_path") final String propertiesPath, + @RequestParam(value = "page", required = false, defaultValue = "1") final Integer page, + @RequestParam(value = "size", required = false, defaultValue = "20") final Integer size) { + + User user = new User(authentication); + Platform.Key platformKey = new Platform.Key(applicationName, platformName); + List propertiesEventViews = platformUseCases.getPropertiesEvents(user, platformKey, propertiesPath, page, size); + List propertiesEventOutputs = PropertiesEventOutput.fromViews(propertiesEventViews); + return ResponseEntity.ok(propertiesEventOutputs); + } } diff --git a/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PlatformEventOutput.java b/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PlatformEventOutput.java index fc3d3d0cb..27d586ee3 100644 --- a/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PlatformEventOutput.java +++ b/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PlatformEventOutput.java @@ -30,8 +30,8 @@ import java.lang.reflect.Type; import java.util.List; -import java.util.stream.Collectors; +import static java.util.stream.Collectors.toList; import static org.hesperides.core.presentation.io.platforms.PlatformEventOutput.DeployedModuleAddedOutput.DEPLOYED_MODULE_ADDED; import static org.hesperides.core.presentation.io.platforms.PlatformEventOutput.DeployedModuleRemovedOutput.DEPLOYED_MODULE_REMOVED; import static org.hesperides.core.presentation.io.platforms.PlatformEventOutput.DeployedModuleUpdatedOutput.DEPLOYED_MODULE_UPDATED; @@ -50,7 +50,9 @@ public PlatformEventOutput(PlatformEventView platformEventView) { } public static List fromViews(List platformEventViews) { - return platformEventViews.stream().map(PlatformEventOutput::new).collect(Collectors.toList()); + return platformEventViews.stream() + .map(PlatformEventOutput::new) + .collect(toList()); } @Value @@ -124,7 +126,7 @@ public static List fromsViews(List cha throw new RuntimeException("Cant map event " + change); } return platformChangeOutput; - }).collect(Collectors.toList()); + }).collect(toList()); } } diff --git a/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PlatformIO.java b/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PlatformIO.java index 1a7be8e97..de2496a70 100644 --- a/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PlatformIO.java +++ b/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PlatformIO.java @@ -25,7 +25,6 @@ import com.google.gson.annotations.SerializedName; import lombok.AllArgsConstructor; import lombok.Value; -import org.hesperides.core.domain.platforms.entities.DeployedModule; import org.hesperides.core.domain.platforms.entities.Platform; import org.hesperides.core.domain.platforms.queries.views.PlatformView; import org.hesperides.core.presentation.io.OnlyPrintableCharacters; @@ -90,7 +89,7 @@ public PlatformIO(PlatformView platformView) { applicationName = platformView.getApplicationName(); version = platformView.getVersion(); isProductionPlatform = platformView.isProductionPlatform(); - deployedModules = DeployedModuleIO.fromActiveDeployedModuleViews(platformView.getActiveDeployedModules()); + deployedModules = DeployedModuleIO.fromActiveDeployedModuleViews(platformView.findActiveDeployedModules()); versionId = platformView.getVersionId(); this.hasPasswords = platformView.getHasPasswords(); } diff --git a/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PropertiesEventOutput.java b/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PropertiesEventOutput.java new file mode 100644 index 000000000..6ba44b85f --- /dev/null +++ b/core/presentation/src/main/java/org/hesperides/core/presentation/io/platforms/PropertiesEventOutput.java @@ -0,0 +1,79 @@ +package org.hesperides.core.presentation.io.platforms; + +import com.google.gson.annotations.SerializedName; +import lombok.AllArgsConstructor; +import lombok.Value; +import org.hesperides.core.domain.platforms.queries.views.PropertiesEventView; +import org.hesperides.core.domain.platforms.queries.views.PropertiesEventView.UpdatedPropertyView; +import org.hesperides.core.domain.platforms.queries.views.PropertiesEventView.ValuedPropertyView; + +import java.util.List; +import java.util.stream.Collectors; + +@Value +public class PropertiesEventOutput { + Long timestamp; + String author; + String comment; + @SerializedName("added_properties") + List addedProperties; + @SerializedName("updated_properties") + List updatedProperties; + @SerializedName("removed_properties") + List removedProperties; + + public static List fromViews(List views) { + return views.stream() + .map(PropertiesEventOutput::new) + .collect(Collectors.toList()); + } + + public PropertiesEventOutput(PropertiesEventView view) { + timestamp = view.getTimestamp().getEpochSecond(); + author = view.getAuthor(); + comment = view.getComment(); + addedProperties = ValuedPropertyOutput.fromViews(view.getAddedProperties()); + updatedProperties = UpdatedPropertyOutput.fromViews(view.getUpdatedProperties()); + removedProperties = ValuedPropertyOutput.fromViews(view.getRemovedProperties()); + } + + @Value + @AllArgsConstructor + public static class ValuedPropertyOutput { + String name; + String value; + + public static List fromViews(List views) { + return views.stream() + .map(ValuedPropertyOutput::new) + .collect(Collectors.toList()); + } + + public ValuedPropertyOutput(ValuedPropertyView view) { + this.name = view.getName(); + this.value = view.getValue(); + } + } + + @Value + @AllArgsConstructor + public static class UpdatedPropertyOutput { + String name; + @SerializedName("old_value") + String oldValue; + @SerializedName("new_value") + String newValue; + + public static List fromViews(List views) { + return views.stream() + .map(UpdatedPropertyOutput::new) + .collect(Collectors.toList()); + } + + public UpdatedPropertyOutput(UpdatedPropertyView view) { + this.name = view.getName(); + this.oldValue = view.getOldValue(); + this.newValue = view.getNewValue(); + } + } +} diff --git a/documentation/lightweight-architecture-decision-records/events_history/index.md b/documentation/lightweight-architecture-decision-records/events_history/index.md index cfdba6991..c843d7fed 100644 --- a/documentation/lightweight-architecture-decision-records/events_history/index.md +++ b/documentation/lightweight-architecture-decision-records/events_history/index.md @@ -18,6 +18,6 @@ Ces ressources seront conservées mais dépréciées. * Comparer les valorisations d'un module entre 2 versions 3 cas d'utilisations dans des modales _frontend_ : -1. [Historique des valorisations d'un module déployé](deployed_module_events.md) +1. [Historique des valorisations d'un module déployé](properties_events.md) 1. [Historique au niveau d'une plateforme](platform_events.md) 1. [Historique d'un module ou d'une techno](template_container_events.md) diff --git a/documentation/lightweight-architecture-decision-records/events_history/platform_events.md b/documentation/lightweight-architecture-decision-records/events_history/platform_events.md index 62116f630..c812cecc6 100644 --- a/documentation/lightweight-architecture-decision-records/events_history/platform_events.md +++ b/documentation/lightweight-architecture-decision-records/events_history/platform_events.md @@ -19,6 +19,7 @@ Les évènements contenant les données permettant d'extraire ces informations s [ { timestamp: Long, + author: String, changes: [ { event_name: "platform_created", diff --git a/documentation/lightweight-architecture-decision-records/events_history/deployed_module_events.md b/documentation/lightweight-architecture-decision-records/events_history/properties_events.md similarity index 69% rename from documentation/lightweight-architecture-decision-records/events_history/deployed_module_events.md rename to documentation/lightweight-architecture-decision-records/events_history/properties_events.md index f3d90166c..5abf81bb6 100644 --- a/documentation/lightweight-architecture-decision-records/events_history/deployed_module_events.md +++ b/documentation/lightweight-architecture-decision-records/events_history/properties_events.md @@ -4,7 +4,11 @@ Etape par étape : * Remettre en place la liste des modifications de propriétés avec le commentaire utilisateur * Permettre d'afficher les propriétés ajoutées, modifiées et supprimées * Permettre de déclencher un diff entre 2 valorisations d'un module, depuis la modale -* Bonus - Afficher les changements de versions du module + +On n'affiche pas les changements de versions d'un module pour les raisons suivantes : +* L'intérêt est limité +* Ça complexifie beaucoup le code +* Il n'y a pas de méthode propre pour connaitre l'historique des version d'un module déployé ## Endpoint @@ -20,25 +24,38 @@ Le traitement à exécuter consiste à comparer les évènements n et n-1 afin d [ { + timestamp: Long, author: String, comment: String, - added_properties: ["property-1", "property-2", ...], + added_properties: [ + { + name: String, + value: String + },... + ], updated_properties: [ { name: String, old_value: String, new_value: String - }, + },... + ], + removed_properties: [ + { + name: String, + value: String + },... ], - removed_properties: ["property-3"] } ] Le champ `commment` sera vide pour les propriété globales, qui n'en possèdent pas. -Les champs `added_properties` / `updated_properties` / `removed_properties` seront caclulés en comparant les évenements `PlatformModulePropertiesUpdatedEvent` consécutifs 2 à 2. +Les champs `added_properties` / `updated_properties` / `removed_properties` seront caclulés en comparant les évènements `PlatformModulePropertiesUpdatedEvent` consécutifs 2 à 2. -Ne pas oublier de tenir compte de l'évènement `RestoreDeletedPlatformEvent` dans le calcul de ces données. +Ne pas oublier de tenir compte : +* de l'évènement `RestoreDeletedPlatformEvent` dans le calcul de ces données +* des mot de passes de prod ## Pagination diff --git a/tests/bdd/src/main/java/org/hesperides/test/bdd/commons/HesperidesScenario.java b/tests/bdd/src/main/java/org/hesperides/test/bdd/commons/HesperidesScenario.java index 886769ae7..7182c0f09 100644 --- a/tests/bdd/src/main/java/org/hesperides/test/bdd/commons/HesperidesScenario.java +++ b/tests/bdd/src/main/java/org/hesperides/test/bdd/commons/HesperidesScenario.java @@ -24,7 +24,10 @@ import org.springframework.http.HttpStatus; import org.springframework.web.client.RestTemplate; +import java.util.List; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; public class HesperidesScenario { @@ -74,4 +77,10 @@ protected void assertNoContent() { protected void assertInternalServerError() { assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, testContext.getResponseStatusCode()); } + + protected void assertEqualsInAnyOrder(List expectedElements, List actualElements) { + assertTrue(expectedElements.size() == actualElements.size() && + expectedElements.containsAll(actualElements) && actualElements.containsAll(expectedElements)); + + } } diff --git a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/PlatformClient.java b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/PlatformClient.java index 3fec499c5..bc94181b1 100644 --- a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/PlatformClient.java +++ b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/PlatformClient.java @@ -301,4 +301,20 @@ public void getPlatformEvents(PlatformIO platform, Integer page, Integer size) { platform.getApplicationName(), platform.getPlatformName()); } + + public void getDeployedModuleEvents(PlatformIO platform, String propertiesPath, Integer page, Integer size) { + String url = "/applications/{application_name}/platforms/{platform_name}/properties/events?properties_path={properties_path}"; + if (page != null) { + url += "&page=" + page; + } + if (size != null) { + url += "&size=" + size; + } + restTemplate.getForEntity( + url, + PropertiesEventOutput[].class, + platform.getApplicationName(), + platform.getPlatformName(), + propertiesPath); + } } diff --git a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/GetDeployedModuleEvents.java b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/GetDeployedModuleEvents.java new file mode 100644 index 000000000..a7625823e --- /dev/null +++ b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/GetDeployedModuleEvents.java @@ -0,0 +1,77 @@ +package org.hesperides.test.bdd.platforms.scenarios; + +import io.cucumber.datatable.DataTable; +import io.cucumber.java.DataTableType; +import io.cucumber.java8.En; +import org.hesperides.core.presentation.io.platforms.PropertiesEventOutput; +import org.hesperides.core.presentation.io.platforms.PropertiesEventOutput.UpdatedPropertyOutput; +import org.hesperides.core.presentation.io.platforms.PropertiesEventOutput.ValuedPropertyOutput; +import org.hesperides.test.bdd.commons.HesperidesScenario; +import org.hesperides.test.bdd.platforms.PlatformClient; +import org.hesperides.test.bdd.platforms.builders.DeployedModuleBuilder; +import org.hesperides.test.bdd.platforms.builders.PlatformBuilder; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; + +import static org.apache.commons.lang3.StringUtils.isEmpty; +import static org.hesperides.test.bdd.commons.DataTableHelper.decodeValue; + +public class GetDeployedModuleEvents extends HesperidesScenario implements En { + + @Autowired + private PlatformClient platformClient; + @Autowired + private PlatformBuilder platformBuilder; + @Autowired + private DeployedModuleBuilder deployedModuleBuilder; + + public GetDeployedModuleEvents() { + + When("^I get the( global)? properties events(?: for this module)?(?: with page (\\d) and size (\\d))?$", ( + String globalProperties, Integer page, Integer size) -> { + String propertiesPath = isEmpty(globalProperties) ? deployedModuleBuilder.buildPropertiesPath() : "#"; + platformClient.getDeployedModuleEvents(platformBuilder.buildInput(), propertiesPath, page, size); + }); + + Then("^the properties event at index (\\d) has these (added|updated|removed) properties$", ( + Integer eventIndex, String changeNature, DataTable dataTable) -> { + + PropertiesEventOutput propertiesEvent = testContext.getResponseBody(PropertiesEventOutput[].class)[eventIndex]; + switch (changeNature) { + case "added": + List expectedAddedProperties = dataTable.asList(ValuedPropertyOutput.class); + assertEqualsInAnyOrder(expectedAddedProperties, propertiesEvent.getAddedProperties()); + break; + case "updated": + List expectedUpdatedProperties = dataTable.asList(UpdatedPropertyOutput.class); + assertEqualsInAnyOrder(expectedUpdatedProperties, propertiesEvent.getUpdatedProperties()); + break; + case "removed": + List expectedRemovedProperties = dataTable.asList(ValuedPropertyOutput.class); + assertEqualsInAnyOrder(expectedRemovedProperties, propertiesEvent.getRemovedProperties()); + break; + default: + throw new RuntimeException("Wrong type of properties change nature: ${changeNature}"); + } + }); + } + + @DataTableType + public ValuedPropertyOutput valuedPropertyOutput(Map entry) { + return new ValuedPropertyOutput( + decodeValue(entry.get("name")), + decodeValue(entry.get("value")) + ); + } + + @DataTableType + public UpdatedPropertyOutput updatedPropertyOutput(Map entry) { + return new UpdatedPropertyOutput( + decodeValue(entry.get("name")), + decodeValue(entry.get("old_value")), + decodeValue(entry.get("new_value")) + ); + } +} diff --git a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/GetPlatformEvents.java b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/GetPlatformEvents.java index cf7d4c7b1..05f2960af 100644 --- a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/GetPlatformEvents.java +++ b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/GetPlatformEvents.java @@ -24,11 +24,11 @@ public class GetPlatformEvents extends HesperidesScenario implements En { public GetPlatformEvents() { - When("^I get this platform's events(?: with page (\\d) and size (\\d))?$", (Integer page, Integer size) -> { + When("^I get this platform events(?: with page (\\d) and size (\\d))?$", (Integer page, Integer size) -> { platformClient.getPlatformEvents(platformBuilder.buildInput(), page, size); }); - Then("^the event at index (\\d) contains \"([^\"]*)\"(?: with old version \"([^\"]*)\" and new version \"([^\"]*)\")?$", ( + Then("^the platform event at index (\\d) contains \"([^\"]*)\"(?: with old version \"([^\"]*)\" and new version \"([^\"]*)\")?$", ( Integer eventIndex, String changeName, String oldVersion, String newVersion) -> { List platformEvents = testContext.getResponseBodyAsList(); List changes = platformEvents.get(eventIndex).getChanges(); diff --git a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/SaveProperties.java b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/SaveProperties.java index 441493861..21c9c1b71 100755 --- a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/SaveProperties.java +++ b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/SaveProperties.java @@ -55,7 +55,10 @@ public class SaveProperties extends HesperidesScenario implements En { public SaveProperties() { - Given("^(?:the module \"([^\"]+)\"|the platform(?: \"([^\"]+)\")?)(?: in version \"([^\"]+)\")? has these (valued|global|instance|iterable)? properties(?: for the logical group \"([^\"]+)\")?$", ( + Given("^(?:the module \"([^\"]+)\"|the platform(?: \"([^\"]+)\")?)" + + "(?: in version \"([^\"]+)\")? " + + "has these (valued|global|instance|iterable)? properties" + + "(?: for the logical group \"([^\"]+)\")?$", ( String moduleName, String platformName, String moduleVersion, String propertiesNature, String logicalGroup, DataTable dataTable) -> { if (isNotEmpty(platformName)) { diff --git a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/UpdatePlatforms.java b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/UpdatePlatforms.java index b05cefaaa..0ed2e5d3d 100755 --- a/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/UpdatePlatforms.java +++ b/tests/bdd/src/main/java/org/hesperides/test/bdd/platforms/scenarios/UpdatePlatforms.java @@ -93,6 +93,7 @@ public void iUpdateThisPlatform( String copyProperties) { if (isNotEmpty(newModuleVersion)) { + deployedModuleBuilder.withVersion(newModuleVersion); platformBuilder.getDeployedModuleBuilders().get(0).withVersion(newModuleVersion); } diff --git a/tests/bdd/src/test/resources/platforms/get-platform-events.feature b/tests/bdd/src/test/resources/platforms/get-platform-events.feature index c00c9aa0c..942d2f163 100644 --- a/tests/bdd/src/test/resources/platforms/get-platform-events.feature +++ b/tests/bdd/src/test/resources/platforms/get-platform-events.feature @@ -5,36 +5,36 @@ Feature: Get platform events Scenario: Get first platform event "platform_created" Given an existing platform - When I get this platform's events - Then the event at index 0 contains "platform_created" + When I get this platform events + Then the platform event at index 0 contains "platform_created" Scenario: Get platform event "platform_version_updated" Given an existing platform with version "1" And I update this platform, changing the version to "2" - When I get this platform's events - Then the event at index 0 contains "platform_version_updated" with old version "1" and new version "2" + When I get this platform events + Then the platform event at index 0 contains "platform_version_updated" with old version "1" and new version "2" Scenario: Get platform event "deployed_module_version_updated" Given an existing module with version "1" And an existing platform with this module And a copy of this module in version "2" And I update this platform, upgrading its module version to "2" - When I get this platform's events - Then the event at index 0 contains "deployed_module_updated" with old version "1" and new version "2" + When I get this platform events + Then the platform event at index 0 contains "deployed_module_updated" with old version "1" and new version "2" Scenario: Get platform event "deployed_module_added" Given an existing platform And an existing module And I update this platform, adding this module - When I get this platform's events - Then the event at index 0 contains "deployed_module_added" + When I get this platform events + Then the platform event at index 0 contains "deployed_module_added" Scenario: Get platform event "deployed_module_removed" Given an existing module And an existing platform with this module And I update this platform, removing this module - When I get this platform's events - Then the event at index 0 contains "deployed_module_removed" + When I get this platform events + Then the platform event at index 0 contains "deployed_module_removed" Scenario: Get second page of platform events Given an existing platform with version "1" @@ -44,14 +44,14 @@ Feature: Get platform events And I update this platform, changing the version to "5" And I update this platform, changing the version to "6" And I update this platform, changing the version to "7" - When I get this platform's events with page 2 and size 3 - Then the event at index 0 contains "platform_version_updated" with old version "3" and new version "4" + When I get this platform events with page 2 and size 3 + Then the platform event at index 0 contains "platform_version_updated" with old version "3" and new version "4" Scenario: Get events of a restored platform Given an existing platform with version "1" And I update this platform, changing the version to "2" And I delete and restore this platform And I update this platform, changing the version to "3" - When I get this platform's events - Then the event at index 0 contains "platform_version_updated" with old version "2" and new version "3" - Then the event at index 1 contains "platform_version_updated" with old version "1" and new version "2" + When I get this platform events + Then the platform event at index 0 contains "platform_version_updated" with old version "2" and new version "3" + Then the platform event at index 1 contains "platform_version_updated" with old version "1" and new version "2" diff --git a/tests/bdd/src/test/resources/platforms/properties/get-properties-events.feature b/tests/bdd/src/test/resources/platforms/properties/get-properties-events.feature new file mode 100644 index 000000000..e0e638da1 --- /dev/null +++ b/tests/bdd/src/test/resources/platforms/properties/get-properties-events.feature @@ -0,0 +1,169 @@ +Feature: Get properties events + + Background: + Given an authenticated user + + Scenario: get properties events + Given an existing module with this template content + """ + {{ added-property }} + {{ modified-property }} + {{ removed-property }} + """ + And an existing platform with this module + And the platform has these valued properties + | name | value | + | added-property | val | + | modified-property | val-1 | + | removed-property | val | + And the platform has these valued properties + | name | value | + | added-property | val | + | modified-property | val-2 | + | another-property | val | + When I get the properties events + Then the properties event at index 0 has these added properties + | name | value | + | another-property | val | + And the properties event at index 0 has these updated properties + | name | old_value | new_value | + | modified-property | val-1 | val-2 | + And the properties event at index 0 has these removed properties + | name | value | + | removed-property | val | + And the properties event at index 1 has these added properties + | name | value | + | removed-property | val | + | modified-property | val-1 | + | added-property | val | + + Scenario: get properties events with hidden passwords + Given an existing module with this template content + """ + {{ password-property | @password }} + """ + And an existing prod platform with this module + And the platform has these valued properties + | name | value | + | password-property | SECRET-1 | + And the platform has these valued properties + | name | value | + | password-property | SECRET-2 | + And an authenticated lambda user + When I get the properties events + Then the properties event at index 0 has these updated properties + | name | old_value | new_value | + | password-property | ******** | ******** | + And the properties event at index 1 has these added properties + | name | value | + | password-property | ******** | + + Scenario: get properties events with displayed prod passwords + Given an existing module with this template content + """ + {{ password-property | @password }} + """ + And an existing prod platform with this module + And the platform has these valued properties + | name | value | + | password-property | SECRET-1 | + And the platform has these valued properties + | name | value | + | password-property | SECRET-2 | + When I get the properties events + Then the properties event at index 0 has these updated properties + | name | old_value | new_value | + | password-property | SECRET-1 | SECRET-2 | + And the properties event at index 1 has these added properties + | name | value | + | password-property | SECRET-1 | + + Scenario: get global properties events + Given an existing platform + And the platform has these global properties + | name | value | + | added-property | val | + | modified-property | val-1 | + | removed-property | val | + And the platform has these global properties + | name | value | + | added-property | val | + | modified-property | val-2 | + | another-property | val | + When I get the global properties events + Then the properties event at index 0 has these added properties + | name | value | + | another-property | val | + And the properties event at index 0 has these updated properties + | name | old_value | new_value | + | modified-property | val-1 | val-2 | + And the properties event at index 0 has these removed properties + | name | value | + | removed-property | val | + And the properties event at index 1 has these added properties + | name | value | + | removed-property | val | + | modified-property | val-1 | + | added-property | val | + + Scenario: get global properties events with page and size + Given an existing platform + And the platform has these global properties + | name | value | + | property | val-1 | + And the platform has these global properties + | name | value | + | property | val-2 | + And the platform has these global properties + | name | value | + | property | val-3 | + And the platform has these global properties + | name | value | + | property | val-4 | + When I get the global properties events with page 1 and size 3 + Then the properties event at index 0 has these updated properties + | name | old_value | new_value | + | property | val-3 | val-4 | + And the properties event at index 1 has these updated properties + | name | old_value | new_value | + | property | val-2 | val-3 | + And the properties event at index 2 has these updated properties + | name | old_value | new_value | + | property | val-1 | val-2 | + When I get the global properties events with page 2 and size 3 + Then the properties event at index 0 has these added properties + | name | value | + | property | val-1 | + + Scenario: get properties events with page and size + Given an existing module with this template content + """ + {{ property }} + """ + And an existing platform with this module + And the platform has these valued properties + | name | value | + | property | val-1 | + And the platform has these valued properties + | name | value | + | property | val-2 | + And the platform has these valued properties + | name | value | + | property | val-3 | + And the platform has these valued properties + | name | value | + | property | val-4 | + When I get the properties events with page 1 and size 3 + Then the properties event at index 0 has these updated properties + | name | old_value | new_value | + | property | val-3 | val-4 | + And the properties event at index 1 has these updated properties + | name | old_value | new_value | + | property | val-2 | val-3 | + And the properties event at index 2 has these updated properties + | name | old_value | new_value | + | property | val-1 | val-2 | + When I get the properties events with page 2 and size 3 + Then the properties event at index 0 has these added properties + | name | value | + | property | val-1 |