Skip to content

Commit

Permalink
Fix #826: Historique des valorisations de propriétés
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaslhostis committed Apr 6, 2020
1 parent 15172ed commit a893a60
Show file tree
Hide file tree
Showing 36 changed files with 803 additions and 116 deletions.
10 changes: 5 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ stages:
jobs:
include:
- stage: build
env: BUILD DOCKER IMAGE
name: Build Docker image
install: echo Not performing default install step
script: |
docker build -t hesperides/hesperides:$TRAVIS_COMMIT \
Expand All @@ -43,10 +43,10 @@ jobs:
paths:
- hesperides-docker-image.tar
- stage: test
env: CUCUMBER TESTS
name: Cucumber tests
script: mvn test -pl '!tests/activedirectory-integration,!tests/mongo-integration,!tests/regression'
- stage: test
env: MONGO INTEGRATION TESTS
name: MongoDB integration tests
before_script: sudo service mongod start
script:
- sleep 15 # waiting for MongoDB to start up
Expand All @@ -56,14 +56,14 @@ jobs:
- sleep 30 # waiting for the SpringBoot app to start up
- mvn -pl tests/mongo-integration verify
- stage: deploy
env: DEPLOY TO DOCKER HUB
name: Deploy to Docker Hub
script:
- docker load --input hesperides-docker-image.tar
- .travis/docker_build_push.sh
workspaces:
use: hesperides-docker-image
- stage: deploy
env: DEPLOY TO NEXUS (SONATYPE)
name: Deploy to Nexus (Sonatype)
before_script:
- openssl aes-256-cbc -K $encrypted_2ec9da69c070_key -iv $encrypted_2ec9da69c070_iv
-in ./.travis/codesigning.asc.enc -out ./.travis/codesigning.asc -d
Expand Down
2 changes: 1 addition & 1 deletion bootstrap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@
</plugins>
</build>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,13 @@ private static Module.Key parseModuleKey(String streamName, String moduleName) {

public List<EventView> 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<EventView> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ private static PropertyVisitorsSequence buildPropertyVisitorsSequence(PlatformVi
boolean shouldHidePasswordProperties) {

EnumSet<PropertyType> propertiesToInclude = EnumSet.of(GLOBAL);
DeployedModuleView deployedModule = platform.getDeployedModule(modulePath, moduleKey);
DeployedModuleView deployedModule = platform.findActiveDeployedModuleByModulePathAndModuleKey(modulePath, moduleKey);

PropertyVisitorsSequence firstPropertyVisitorsSequence = PropertyValuationBuilder.buildFirstPropertyVisitorsSequence(
deployedModule, modulePropertiesModels, shouldHidePasswordProperties, propertiesToInclude);
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
public interface EventRepository {

@QueryHandler
List<EventView> onGetEventsQuery(GetEventsQuery query);
List<EventView> onGetLastToFirstEventsQuery(GetLastToFirstEventsQuery query);

@QueryHandler
List<EventView> onGetLastToFirstPlatformModulePropertiesUpdatedEvents(GetLastToFirstPlatformModulePropertiesUpdatedEvents query);

void cleanAggregateEvents(String aggregateIdentifier);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,11 +16,19 @@ protected EventQueries(QueryGateway queryGateway) {
super(queryGateway);
}

public List<EventView> getEvents(String aggregateId, Integer page, Integer size) {
return getEventsByTypes(aggregateId, new Class[0], page, size);
public List<EventView> getLastToFirstEvents(String aggregateId, Integer page, Integer size) {
return getLastToFirstEventsByTypes(aggregateId, new Class[0], page, size);
}

public List<EventView> getEventsByTypes(String aggregateId, Class[] eventTypes, Integer page, Integer size) {
return querySyncList(new GetEventsQuery(aggregateId, eventTypes, page, size), EventView.class);
public List<EventView> getLastToFirstEventsByType(String aggregateId, Class eventType, Integer page, Integer size) {
return getLastToFirstEventsByTypes(aggregateId, new Class[]{eventType}, page, size);
}

public List<EventView> getLastToFirstEventsByTypes(String aggregateId, Class[] eventTypes, Integer page, Integer size) {
return querySyncList(new GetLastToFirstEventsQuery(aggregateId, eventTypes, page, size), EventView.class);
}

public List<EventView> getLastToFirstPlatformModulePropertiesUpdatedEvents(String aggregateId, String propertiesPath, Integer page, Integer size) {
return querySyncList(new GetLastToFirstPlatformModulePropertiesUpdatedEvents(aggregateId, propertiesPath, page, size), EventView.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,7 @@ public interface PlatformProjectionRepository {

@QueryHandler
List<ApplicationView> onGetAllApplicationsDetailQuery(GetAllApplicationsDetailQuery query);

@QueryHandler
Boolean onIsProductionPlatformQuery(IsProductionPlatformQuery query);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
@NonFinal
public abstract class AbstractValuedProperty {
String name;
//boolean notActiveForThisVersion;

public static <T extends AbstractValuedProperty> List<T> filterAbstractValuedPropertyWithType(List<AbstractValuedProperty> properties, Class<T> clazz) {
return Optional.ofNullable(properties)
Expand All @@ -54,4 +53,4 @@ public static List<ValuedProperty> getFlatValuedProperties(final List<AbstractVa
}

protected abstract Stream<ValuedProperty> flattenProperties();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,8 @@ public boolean applicationExists(String applicationName) {
public List<ApplicationView> getAllApplicationsDetail() {
return querySyncList(new GetAllApplicationsDetailQuery(), ApplicationView.class);
}

public boolean isProductionPlatform(String platformId) {
return querySync(new IsProductionPlatformQuery(platformId), Boolean.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,22 @@ static List<PlatformView> setPlatformsWithPasswordIndicator(List<PlatformView> p
.collect(Collectors.toList());
}

public Stream<DeployedModuleView> getActiveDeployedModules() {
public Stream<DeployedModuleView> 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));
}
Expand Down Expand Up @@ -124,7 +124,7 @@ public List<TemplateContainer.Key> getActiveDeployedModulesKeys() {
}

public List<TemplateContainer.Key> getActiveDeployedModulesKeys(String propertiesPath) {
return getActiveDeployedModules()
return findActiveDeployedModules()
.filter(deployedModule -> isEmpty(propertiesPath) || deployedModule.getPropertiesPath().equals(propertiesPath))
.map(DeployedModuleView::getModuleKey)
.collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ValuedPropertyView> addedProperties;
List<UpdatedPropertyView> updatedProperties;
List<ValuedPropertyView> removedProperties;

public PropertiesEventView(EventView event,
List<ValuedPropertyView> addedProperties,
List<UpdatedPropertyView> updatedProperties,
List<ValuedPropertyView> 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<PropertiesEventView> buildPropertiesEvents(List<EventView> providedEvents, boolean isModuleProperties, boolean containsFirstEvent) {
List<PropertiesEventView> propertiesEvents = new ArrayList<>();

if (!isEmpty(providedEvents)) {
// L'algorithme dépend du tri dans l'ordre chronologique
providedEvents.sort(Comparator.comparing(EventView::getTimestamp));
Iterator<EventView> eventsIterator = providedEvents.iterator();

EventView previousEvent = eventsIterator.next(); // On a besoin de conserver `currentEvent` pour récupérer le timestamp

List<ValuedProperty> simpleValuedProperties = isModuleProperties
? extractSimpleValuedProperties(((PlatformModulePropertiesUpdatedEvent) previousEvent.getData()).getValuedProperties())
: ((PlatformPropertiesUpdatedEvent) previousEvent.getData()).getValuedProperties();

if (containsFirstEvent) {
// Premier évènement
List<ValuedPropertyView> 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<ValuedPropertyView> addedProperties = new ArrayList<>();
List<UpdatedPropertyView> updatedProperties = new ArrayList<>();
List<ValuedPropertyView> removedProperties = new ArrayList<>();

// Pour l'instant on ne traite que les propriétés simples
List<ValuedProperty> previousValuedProperties = isModuleProperties
? extractSimpleValuedProperties(((PlatformModulePropertiesUpdatedEvent) previousEvent.getData()).getValuedProperties())
: ((PlatformPropertiesUpdatedEvent) previousEvent.getData()).getValuedProperties();
List<ValuedProperty> currentValuedProperties = isModuleProperties
? extractSimpleValuedProperties(((PlatformModulePropertiesUpdatedEvent) currentEvent.getData()).getValuedProperties())
: ((PlatformPropertiesUpdatedEvent) currentEvent.getData()).getValuedProperties();

Map<String, ValuedProperty> previousPropertiesByName = simpleValuedPropertiesByName(previousValuedProperties);
Map<String, ValuedProperty> 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<ValuedProperty> extractSimpleValuedProperties(List<AbstractValuedProperty> valuedProperties) {
return valuedProperties.stream()
.filter(ValuedProperty.class::isInstance)
.map(ValuedProperty.class::cast)
.collect(toList());
}

private static Map<String, ValuedProperty> simpleValuedPropertiesByName(List<ValuedProperty> simpleValuedProperties) {
return simpleValuedProperties.stream()
.collect(Collectors.toMap(ValuedProperty::getName, identity()));
}

public PropertiesEventView hidePasswords(List<String> passwordProperties) {
List<ValuedPropertyView> addedProperties = hideValuedPasswordProperties(passwordProperties, getAddedProperties());
List<UpdatedPropertyView> updatedProperties = hideUpdatedPasswordProperties(passwordProperties, getUpdatedProperties());
List<ValuedPropertyView> removedProperties = hideValuedPasswordProperties(passwordProperties, getRemovedProperties());
return new PropertiesEventView(timestamp, author, comment, addedProperties, updatedProperties, removedProperties);
}

private static List<ValuedPropertyView> hideValuedPasswordProperties(List<String> passwordProperties, List<ValuedPropertyView> valuedProperties) {
return valuedProperties.stream().map(valuedProperty -> passwordProperties.contains(valuedProperty.getName())
? new ValuedPropertyView(valuedProperty.getName(), OBFUSCATED_PASSWORD_VALUE)
: valuedProperty
).collect(toList());
}

private static List<UpdatedPropertyView> hideUpdatedPasswordProperties(List<String> passwordProperties, List<UpdatedPropertyView> 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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,15 @@
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;

@Value
@EqualsAndHashCode(callSuper = true)
public class ValuedPropertyView extends AbstractValuedPropertyView {

private final static String OBFUSCATED_PASSWORD_VALUE = "********";
public final static String OBFUSCATED_PASSWORD_VALUE = "********";

String value;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Class<UserEvent>>, val page: Integer, val size: Integer)
data class CleanAggregateEventsQuery(val aggregateIdentifier: String)
data class GetLastToFirstEventsQuery(val aggregateIdentifier: String, val eventTypes: Array<Class<UserEvent>>, val page: Integer, val size: Integer)
data class GetLastToFirstPlatformModulePropertiesUpdatedEvents(val aggregateIdentifier: String, val propertiesPath: String, val page: Integer, val size: Integer)
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading

0 comments on commit a893a60

Please sign in to comment.