Create Rollout UI with Groups definition (#369)
* Rollout creation UI with groups definition Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Improved code quality Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Added a legend to the Rollouts Creation UI's PieChart Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Updated color palette for pie chart. Using RolloutGroupCreate for validation. Displaying pie chart for group editing. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Optimized ComboBoxBuilder Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Removed not necessary RPC interfaces. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Fix Rollout verification count query. Improved UI style. Added dependency management for d3. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Auto start and scheduled start of Rollouts Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Optimized RSQL Utility to avoid multiple joins on the same table. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Fixed RSQL Utility to avoid multiple joins on the same table to only work with OR nodes. Optimized validation queries for rollout group creation. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Asynchronous handling of groups validation in the create rollouts UI. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Added loading indicator to create rollout UI. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Avoid parallel validation for a user session. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Fix issues after merge of master into branch. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Improve code quality Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Refreshing the total target count after validation on the rollouts creation UI. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com> * Remove unused RolloutUpdate method. Optimization of the RolloutHelper. Comments for RolloutGroupsValidation and RSQLUtility with explanation. Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>
This commit is contained in:
committed by
Kai Zimmermann
parent
430bf632cf
commit
833b0795e3
@@ -32,6 +32,8 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
|
||||
|
||||
private Long forcetime;
|
||||
|
||||
private Long startAt;
|
||||
|
||||
private MgmtActionType type;
|
||||
|
||||
private List<MgmtRolloutGroup> groups;
|
||||
@@ -124,4 +126,19 @@ public class MgmtRolloutRestRequestBody extends AbstractMgmtRolloutConditionsEnt
|
||||
public void setGroups(List<MgmtRolloutGroup> groups) {
|
||||
this.groups = groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the start at timestamp in millis or null
|
||||
*/
|
||||
public Long getStartAt() {
|
||||
return startAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param startAt
|
||||
* the start at timestamp in millis or null
|
||||
*/
|
||||
public void setStartAt(Long startAt) {
|
||||
this.startAt = startAt;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ final class MgmtRolloutMapper {
|
||||
return entityFactory.rollout().create().name(restRequest.getName()).description(restRequest.getDescription())
|
||||
.set(distributionSet).targetFilterQuery(restRequest.getTargetFilterQuery())
|
||||
.actionType(MgmtRestModelMapper.convertActionType(restRequest.getType()))
|
||||
.forcedTime(restRequest.getForcetime());
|
||||
.forcedTime(restRequest.getForcetime()).startAt(restRequest.getStartAt());
|
||||
}
|
||||
|
||||
static RolloutGroupCreate fromRequest(final EntityFactory entityFactory, final MgmtRolloutGroup restRequest) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package org.eclipse.hawkbit.repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
@@ -27,11 +28,13 @@ import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
|
||||
/**
|
||||
* RolloutManagement to control rollouts e.g. like creating, starting, resuming
|
||||
@@ -42,7 +45,7 @@ public interface RolloutManagement {
|
||||
|
||||
/**
|
||||
* Checking running rollouts. Rollouts which are checked updating the
|
||||
* {@link Rollout#setLastCheck(long)} to indicate that the current instance
|
||||
* lastCheck to indicate that the current instance
|
||||
* is handling the specific rollout. This code should run as system-code.
|
||||
*
|
||||
* <pre>
|
||||
@@ -55,8 +58,8 @@ public interface RolloutManagement {
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* This method is attend to be called by a scheduler.
|
||||
* {@link RolloutScheduler}. And must be running in an transaction so it's
|
||||
* This method is intended to be called by a scheduler.
|
||||
* And must be running in an transaction so it's
|
||||
* splitted from the scheduler.
|
||||
*
|
||||
* Rollouts which are currently running are investigated, by means the
|
||||
@@ -95,6 +98,17 @@ public interface RolloutManagement {
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
|
||||
void checkStartingRollouts(long delayBetweenChecks);
|
||||
|
||||
/**
|
||||
* Checking Rollouts that are currently ready for an auto start.
|
||||
*
|
||||
* @param delayBetweenChecks
|
||||
* the time in milliseconds of the delay between the further and
|
||||
* this check. This check is only applied if the last check is
|
||||
* less than (lastcheck-delay).
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_WRITE)
|
||||
void checkReadyRollouts(long delayBetweenChecks);
|
||||
|
||||
/**
|
||||
* Counts all {@link Rollout}s in the repository.
|
||||
*
|
||||
@@ -181,6 +195,24 @@ public interface RolloutManagement {
|
||||
Rollout createRollout(@NotNull RolloutCreate rollout, @NotNull List<RolloutGroupCreate> groups,
|
||||
RolloutGroupConditions conditions);
|
||||
|
||||
/**
|
||||
* Calculates how many targets are addressed by each rollout group and
|
||||
* returns the validation information.
|
||||
*
|
||||
* @param groups
|
||||
* a list of rollout groups
|
||||
* @param targetFilter
|
||||
* the rollout
|
||||
* @param createdAt
|
||||
* timestamp when the rollout was created
|
||||
* @return the validation information
|
||||
* @throws RolloutIllegalStateException
|
||||
* thrown when no targets are targeted by the rollout
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_READ_AND_TARGET_READ)
|
||||
ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(List<RolloutGroupCreate> groups,
|
||||
String targetFilter, Long createdAt);
|
||||
|
||||
/**
|
||||
* Can be called on a Rollout in {@link RolloutStatus#CREATING} to
|
||||
* automatically fill it with targets.
|
||||
@@ -365,10 +397,6 @@ public interface RolloutManagement {
|
||||
*
|
||||
* @param update
|
||||
* rollout to be updated
|
||||
* @param name
|
||||
* to update or <code>null</code>
|
||||
* @param description
|
||||
* to update or <code>null</code>
|
||||
*
|
||||
* @return Rollout updated rollout
|
||||
*/
|
||||
|
||||
@@ -25,6 +25,9 @@ public class RolloutProperties {
|
||||
// used by @Scheduled annotation which needs constant
|
||||
public static final String PROP_STARTING_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.startingScheduler.fixedDelay:2000}";
|
||||
|
||||
// used by @Scheduled annotation which needs constant
|
||||
public static final String PROP_READY_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.rollout.readyScheduler.fixedDelay:30000}";
|
||||
|
||||
/**
|
||||
* Rollout scheduler configuration.
|
||||
*/
|
||||
@@ -65,6 +68,8 @@ public class RolloutProperties {
|
||||
|
||||
private final Scheduler startingScheduler = new Scheduler(2000L);
|
||||
|
||||
private final Scheduler readyScheduler = new Scheduler(30000L);
|
||||
|
||||
public Scheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
@@ -76,4 +81,8 @@ public class RolloutProperties {
|
||||
public Scheduler getStartingScheduler() {
|
||||
return startingScheduler;
|
||||
}
|
||||
|
||||
public Scheduler getReadyScheduler() {
|
||||
return readyScheduler;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,18 @@ public interface TargetFilterQueryManagement {
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
|
||||
Page<TargetFilterQuery> findTargetFilterQueryByFilter(@NotNull Pageable pageable, @NotNull String rsqlFilter);
|
||||
|
||||
/**
|
||||
* Retrieves all target filter query which have exactly the provided query.
|
||||
*
|
||||
* @param pageable
|
||||
* pagination parameter
|
||||
* @param query
|
||||
* the query saved in the target filter query
|
||||
* @return the page with the found {@link TargetFilterQuery}
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
|
||||
Page<TargetFilterQuery> findTargetFilterQueryByQuery(@NotNull Pageable pageable, String query);
|
||||
|
||||
/**
|
||||
* Retrieves all target filter query which {@link TargetFilterQuery}.
|
||||
*
|
||||
|
||||
@@ -76,6 +76,13 @@ public interface RolloutCreate {
|
||||
*/
|
||||
RolloutCreate forcedTime(Long forcedTime);
|
||||
|
||||
/**
|
||||
* @param startAt
|
||||
* for {@link Rollout#getStartAt()}
|
||||
* @return updated builder instance
|
||||
*/
|
||||
RolloutCreate startAt(Long startAt);
|
||||
|
||||
/**
|
||||
* @return peek on current state of {@link Rollout} in the builder
|
||||
*/
|
||||
|
||||
@@ -8,9 +8,14 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.builder;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.Action;
|
||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.hibernate.validator.constraints.NotEmpty;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Builder to update an existing {@link Rollout} entry. Defines all fields that
|
||||
* can be updated.
|
||||
@@ -30,4 +35,33 @@ public interface RolloutUpdate {
|
||||
* @return updated builder instance
|
||||
*/
|
||||
RolloutUpdate description(String description);
|
||||
|
||||
/**
|
||||
* @param setId
|
||||
* for {@link Rollout#getDistributionSet()}
|
||||
* @return updated builder instance
|
||||
*/
|
||||
RolloutUpdate set(long setId);
|
||||
|
||||
/**
|
||||
* @param actionType
|
||||
* for {@link Rollout#getActionType()}
|
||||
* @return updated builder instance
|
||||
*/
|
||||
RolloutUpdate actionType(@NotNull Action.ActionType actionType);
|
||||
|
||||
/**
|
||||
* @param forcedTime
|
||||
* for {@link Rollout#getForcedTime()}
|
||||
* @return updated builder instance
|
||||
*/
|
||||
RolloutUpdate forcedTime(Long forcedTime);
|
||||
|
||||
/**
|
||||
* @param startAt
|
||||
* for {@link Rollout#getStartAt()}
|
||||
* @return updated builder instance
|
||||
*/
|
||||
RolloutUpdate startAt(Long startAt);
|
||||
|
||||
}
|
||||
|
||||
@@ -59,6 +59,12 @@ public interface Rollout extends NamedEntity {
|
||||
*/
|
||||
long getForcedTime();
|
||||
|
||||
/**
|
||||
* @return Timestamp when the rollout should be started automatically. Can be null.
|
||||
*/
|
||||
Long getStartAt();
|
||||
|
||||
|
||||
/**
|
||||
* @return number of {@link Target}s in this rollout.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.model;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents information to validate the correct distribution of targets to
|
||||
* rollout groups.
|
||||
*/
|
||||
public class RolloutGroupsValidation {
|
||||
|
||||
/**
|
||||
* The total amount of targets in a {@link Rollout}
|
||||
*/
|
||||
private long totalTargets;
|
||||
|
||||
/**
|
||||
* A list containing the count of targets for each {@link RolloutGroup}
|
||||
*/
|
||||
private List<Long> targetsPerGroup;
|
||||
|
||||
/**
|
||||
* Instantiates a new validation result
|
||||
*
|
||||
* @param totalTargets
|
||||
* The total amount of targets in a {@link Rollout}
|
||||
* @param targetsPerGroup
|
||||
* A list containing the count of targets for each
|
||||
* {@link RolloutGroup}
|
||||
*/
|
||||
public RolloutGroupsValidation(final long totalTargets, @NotNull final List<Long> targetsPerGroup) {
|
||||
this.totalTargets = totalTargets;
|
||||
this.targetsPerGroup = targetsPerGroup;
|
||||
}
|
||||
|
||||
public long getTotalTargets() {
|
||||
return totalTargets;
|
||||
}
|
||||
|
||||
public List<Long> getTargetsPerGroup() {
|
||||
return targetsPerGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the count of targets that are in groups
|
||||
*/
|
||||
public long getTargetsInGroups() {
|
||||
return targetsPerGroup.stream().mapToLong(Long::longValue).sum();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the groups contain all targets
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return totalTargets == getTargetsInGroups();
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ package org.eclipse.hawkbit.repository.builder;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Create and update builder DTO.
|
||||
*
|
||||
@@ -21,6 +23,7 @@ public abstract class AbstractRolloutUpdateCreate<T> extends AbstractNamedEntity
|
||||
protected String targetFilterQuery;
|
||||
protected ActionType actionType;
|
||||
protected Long forcedTime;
|
||||
protected Long startAt;
|
||||
|
||||
public T set(final long set) {
|
||||
this.set = set;
|
||||
@@ -42,4 +45,24 @@ public abstract class AbstractRolloutUpdateCreate<T> extends AbstractNamedEntity
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public T startAt(final Long startAt) {
|
||||
this.startAt = startAt;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
public Optional<Long> getSet() {
|
||||
return Optional.ofNullable(set);
|
||||
}
|
||||
|
||||
public Optional<ActionType> getActionType() {
|
||||
return Optional.ofNullable(actionType);
|
||||
}
|
||||
|
||||
public Optional<Long> getForcedTime() {
|
||||
return Optional.ofNullable(forcedTime);
|
||||
}
|
||||
|
||||
public Optional<Long> getStartAt() {
|
||||
return Optional.ofNullable(startAt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.EntityNotFoundException;
|
||||
@@ -20,6 +22,7 @@ import javax.validation.ConstraintDeclarationException;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutFields;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
@@ -36,7 +39,6 @@ import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecuto
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.RolloutTargetGroup;
|
||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupActionEvaluator;
|
||||
import org.eclipse.hawkbit.repository.jpa.rollout.condition.RolloutGroupConditionEvaluator;
|
||||
@@ -52,6 +54,7 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupErrorCondit
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
|
||||
import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
|
||||
@@ -63,12 +66,13 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Modifying;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.scheduling.annotation.AsyncResult;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
@@ -78,6 +82,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.DefaultTransactionDefinition;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
/**
|
||||
@@ -89,8 +94,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(RolloutManagement.class);
|
||||
|
||||
/**
|
||||
* Maximum amount of targets that are assigned to a Rollout Group in one
|
||||
* transaction.
|
||||
* Max amount of targets that are handled in one transaction.
|
||||
*/
|
||||
private static final int TRANSACTION_TARGETS = 1000;
|
||||
|
||||
@@ -112,6 +116,9 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
@Autowired
|
||||
private RolloutGroupManagement rolloutGroupManagement;
|
||||
|
||||
@Autowired
|
||||
private DistributionSetManagement distributionSetManagement;
|
||||
|
||||
@Autowired
|
||||
private ActionRepository actionRepository;
|
||||
|
||||
@@ -135,15 +142,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
|
||||
@Override
|
||||
public Page<Rollout> findAll(final Pageable pageable) {
|
||||
return convertPage(rolloutRepository.findAll(pageable), pageable);
|
||||
}
|
||||
|
||||
private static Page<Rollout> convertPage(final Page<JpaRollout> findAll, final Pageable pageable) {
|
||||
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
|
||||
}
|
||||
|
||||
private static Slice<Rollout> convertPage(final Slice<JpaRollout> findAll, final Pageable pageable) {
|
||||
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0);
|
||||
return RolloutHelper.convertPage(rolloutRepository.findAll(pageable), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,7 +152,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
virtualPropertyReplacer);
|
||||
|
||||
final Page<JpaRollout> findAll = rolloutRepository.findAll(specification, pageable);
|
||||
return convertPage(findAll, pageable);
|
||||
return RolloutHelper.convertPage(findAll, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -246,7 +245,8 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
.collect(Collectors.toList());
|
||||
groups.forEach(RolloutHelper::verifyRolloutGroupHasConditions);
|
||||
|
||||
verifyRolloutGroupTargetCounts(groups, savedRollout);
|
||||
RolloutHelper.verifyRemainingTargets(
|
||||
calculateRemainingTargets(groups, savedRollout.getTargetFilterQuery(), savedRollout.getCreatedAt()));
|
||||
|
||||
// Persisting the groups
|
||||
RolloutGroup lastSavedGroup = null;
|
||||
@@ -401,22 +401,51 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
targets.forEach(target -> rolloutTargetGroupRepository.save(new RolloutTargetGroup(group, target)));
|
||||
}
|
||||
|
||||
private void verifyRolloutGroupTargetCounts(final List<RolloutGroup> groups, final JpaRollout rollout) {
|
||||
final String baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
|
||||
private long calculateRemainingTargets(final List<RolloutGroup> groups, final String targetFilter,
|
||||
final Long createdAt) {
|
||||
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
|
||||
final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter);
|
||||
if (totalTargets == 0) {
|
||||
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
|
||||
}
|
||||
|
||||
long targetCount = totalTargets;
|
||||
RolloutGroupsValidation validation = validateTargetsInGroups(groups, baseFilter, totalTargets);
|
||||
|
||||
return totalTargets - validation.getTargetsInGroups();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Async
|
||||
public ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups(final List<RolloutGroupCreate> groups,
|
||||
final String targetFilter, final Long createdAt) {
|
||||
|
||||
final String baseFilter = RolloutHelper.getTargetFilterQuery(targetFilter, createdAt);
|
||||
final long totalTargets = targetManagement.countTargetByTargetFilterQuery(baseFilter);
|
||||
if (totalTargets == 0) {
|
||||
throw new ConstraintDeclarationException("Rollout target filter does not match any targets");
|
||||
}
|
||||
|
||||
return new AsyncResult<>(validateTargetsInGroups(
|
||||
groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets));
|
||||
}
|
||||
|
||||
private RolloutGroupsValidation validateTargetsInGroups(final List<RolloutGroup> groups, final String baseFilter,
|
||||
final long totalTargets) {
|
||||
final List<Long> groupTargetCounts = new ArrayList<>(groups.size());
|
||||
final Map<String, Long> targetFilterCounts = groups.stream()
|
||||
.map(group -> RolloutHelper.getGroupTargetFilter(baseFilter, group)).distinct().collect(Collectors
|
||||
.toMap(Function.identity(), filter -> targetManagement.countTargetByTargetFilterQuery(filter)));
|
||||
|
||||
long unusedTargetsCount = 0;
|
||||
|
||||
for (int i = 0; i < groups.size(); i++) {
|
||||
final RolloutGroup group = groups.get(i);
|
||||
String groupTargetFilter = RolloutHelper.getGroupTargetFilter(baseFilter, group);
|
||||
RolloutHelper.verifyRolloutGroupTargetPercentage(group.getTargetPercentage());
|
||||
|
||||
final long targetsInGroupFilter = countTargetsOfGroup(baseFilter, totalTargets, group);
|
||||
final long overlappingTargets = countOverlappingTargetsWithPreviousGroups(baseFilter, groups, group, i);
|
||||
final long targetsInGroupFilter = targetFilterCounts.get(groupTargetFilter);
|
||||
final long overlappingTargets = countOverlappingTargetsWithPreviousGroups(baseFilter, groups, group, i,
|
||||
targetFilterCounts);
|
||||
|
||||
final long realTargetsInGroup;
|
||||
// Assume that targets which were not used in the previous groups
|
||||
@@ -430,37 +459,31 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
|
||||
final long reducedTargetsInGroup = Math
|
||||
.round(group.getTargetPercentage() / 100 * (double) realTargetsInGroup);
|
||||
targetCount -= reducedTargetsInGroup;
|
||||
groupTargetCounts.add(reducedTargetsInGroup);
|
||||
unusedTargetsCount += realTargetsInGroup - reducedTargetsInGroup;
|
||||
|
||||
}
|
||||
|
||||
RolloutHelper.verifyRemainingTargets(targetCount);
|
||||
|
||||
}
|
||||
|
||||
private long countTargetsOfGroup(final String baseFilter, final long baseFilterCount, final RolloutGroup group) {
|
||||
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
|
||||
return baseFilterCount;
|
||||
} else {
|
||||
return targetManagement.countTargetByTargetFilterQuery(baseFilter + ";" + group.getTargetFilterQuery());
|
||||
}
|
||||
return new RolloutGroupsValidation(totalTargets, groupTargetCounts);
|
||||
}
|
||||
|
||||
private long countOverlappingTargetsWithPreviousGroups(final String baseFilter, final List<RolloutGroup> groups,
|
||||
final RolloutGroup group, final int groupIndex) {
|
||||
final RolloutGroup group, final int groupIndex, final Map<String, Long> targetFilterCounts) {
|
||||
// there can't be overlapping targets in the first group
|
||||
if (groupIndex == 0) {
|
||||
return 0;
|
||||
}
|
||||
final List<RolloutGroup> previousGroups = groups.subList(0, groupIndex);
|
||||
String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(previousGroups, group);
|
||||
if (StringUtils.isEmpty(overlappingTargetsFilter)) {
|
||||
overlappingTargetsFilter = baseFilter;
|
||||
String overlappingTargetsFilter = RolloutHelper.getOverlappingWithGroupsTargetFilter(baseFilter, previousGroups,
|
||||
group);
|
||||
|
||||
if (targetFilterCounts.containsKey(overlappingTargetsFilter)) {
|
||||
return targetFilterCounts.get(overlappingTargetsFilter);
|
||||
} else {
|
||||
overlappingTargetsFilter = baseFilter + ";" + overlappingTargetsFilter;
|
||||
final long overlappingTargets = targetManagement.countTargetByTargetFilterQuery(overlappingTargetsFilter);
|
||||
targetFilterCounts.put(overlappingTargetsFilter, overlappingTargets);
|
||||
return overlappingTargets;
|
||||
}
|
||||
return targetManagement.countTargetByTargetFilterQuery(overlappingTargetsFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -469,8 +492,9 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
public Rollout startRollout(final Long rolloutId) {
|
||||
final JpaRollout rollout = Optional.ofNullable(rolloutRepository.findOne(rolloutId))
|
||||
.orElseThrow(() -> new EntityNotFoundException("Rollout with id " + rolloutId + " not found."));
|
||||
checkIfRolloutCanStarted(rollout, rollout);
|
||||
RolloutHelper.checkIfRolloutCanStarted(rollout, rollout);
|
||||
rollout.setStatus(RolloutStatus.STARTING);
|
||||
rollout.setLastCheck(0);
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
|
||||
@@ -649,19 +673,11 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void checkRunningRollouts(final long delayBetweenChecks) {
|
||||
final long lastCheck = System.currentTimeMillis();
|
||||
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.RUNNING);
|
||||
|
||||
if (updated == 0) {
|
||||
// nothing to check, maybe another instance already checked in
|
||||
// between
|
||||
LOGGER.debug("No rolloutcheck necessary for current scheduled check {}, next check at {}", lastCheck,
|
||||
lastCheck + delayBetweenChecks);
|
||||
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.RUNNING);
|
||||
if (rolloutsToCheck.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<JpaRollout> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
|
||||
RolloutStatus.RUNNING);
|
||||
LOGGER.info("Found {} running rollouts to check", rolloutsToCheck.size());
|
||||
|
||||
for (final JpaRollout rollout : rolloutsToCheck) {
|
||||
@@ -721,10 +737,9 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
final long updatedTargetCount = jpaRollout.getTotalTargets()
|
||||
- (rolloutGroup.getTotalTargets() - countTargetsOfRolloutGroup);
|
||||
jpaRollout.setTotalTargets(updatedTargetCount);
|
||||
final JpaRolloutGroup jpaRolloutGroup = rolloutGroup;
|
||||
jpaRolloutGroup.setTotalTargets((int) countTargetsOfRolloutGroup);
|
||||
rolloutGroup.setTotalTargets((int) countTargetsOfRolloutGroup);
|
||||
rolloutRepository.save(jpaRollout);
|
||||
rolloutGroupRepository.save(jpaRolloutGroup);
|
||||
rolloutGroupRepository.save(rolloutGroup);
|
||||
}
|
||||
|
||||
private long countTargetsFrom(final JpaRolloutGroup rolloutGroup) {
|
||||
@@ -811,18 +826,12 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void checkCreatingRollouts(final long delayBetweenChecks) {
|
||||
final long lastCheck = System.currentTimeMillis();
|
||||
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.CREATING);
|
||||
if (updated == 0) {
|
||||
// nothing to check, maybe another instance already checked in
|
||||
// between
|
||||
LOGGER.debug("No rollouts creating check necessary for current scheduled check {}, next check at {}",
|
||||
lastCheck, lastCheck + delayBetweenChecks);
|
||||
final List<Long> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.CREATING)
|
||||
.stream().map(Rollout::getId).collect(Collectors.toList());
|
||||
if (rolloutsToCheck.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Long> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck, RolloutStatus.CREATING)
|
||||
.stream().map(Rollout::getId).collect(Collectors.toList());
|
||||
LOGGER.info("Found {} creating rollouts to check", rolloutsToCheck.size());
|
||||
|
||||
rolloutsToCheck.forEach(this::fillRolloutGroupsWithTargets);
|
||||
@@ -833,18 +842,12 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void checkStartingRollouts(final long delayBetweenChecks) {
|
||||
final long lastCheck = System.currentTimeMillis();
|
||||
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, RolloutStatus.STARTING);
|
||||
if (updated == 0) {
|
||||
// nothing to check, maybe another instance already checked in
|
||||
// between
|
||||
LOGGER.debug("No rollouts starting check necessary for current scheduled check {}, next check at {}",
|
||||
lastCheck, lastCheck + delayBetweenChecks);
|
||||
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks,
|
||||
RolloutStatus.STARTING);
|
||||
if (rolloutsToCheck.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<JpaRollout> rolloutsToCheck = rolloutRepository.findByLastCheckAndStatus(lastCheck,
|
||||
RolloutStatus.STARTING);
|
||||
LOGGER.info("Found {} starting rollouts to check", rolloutsToCheck.size());
|
||||
|
||||
rolloutsToCheck.forEach(rollout -> {
|
||||
@@ -855,6 +858,42 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
|
||||
@Modifying
|
||||
public void checkReadyRollouts(final long delayBetweenChecks) {
|
||||
final List<JpaRollout> rolloutsToCheck = getRolloutsToCheckForStatus(delayBetweenChecks, RolloutStatus.READY);
|
||||
if (rolloutsToCheck.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.info("Found {} ready rollouts to check", rolloutsToCheck.size());
|
||||
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
rolloutsToCheck.forEach(rollout -> {
|
||||
if (rollout.getStartAt() != null && rollout.getStartAt() <= now) {
|
||||
startRollout(rollout.getId());
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private List<JpaRollout> getRolloutsToCheckForStatus(final long delayBetweenChecks, final RolloutStatus status) {
|
||||
final long lastCheck = System.currentTimeMillis();
|
||||
final int updated = rolloutRepository.updateLastCheck(lastCheck, delayBetweenChecks, status);
|
||||
if (updated == 0) {
|
||||
// nothing to check, maybe another instance already checked in
|
||||
// between
|
||||
LOGGER.debug("No rollouts starting check necessary for current scheduled check {}, next check at {}",
|
||||
lastCheck, lastCheck + delayBetweenChecks);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return rolloutRepository.findByLastCheckAndStatus(lastCheck, status);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long countRolloutsAll() {
|
||||
return rolloutRepository.count();
|
||||
@@ -862,25 +901,15 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
|
||||
@Override
|
||||
public Long countRolloutsAllByFilters(final String searchText) {
|
||||
return rolloutRepository.count(likeNameOrDescription(searchText));
|
||||
}
|
||||
|
||||
private static Specification<JpaRollout> likeNameOrDescription(final String searchText) {
|
||||
return (rolloutRoot, query, criteriaBuilder) -> {
|
||||
final String searchTextToLower = searchText.toLowerCase();
|
||||
return criteriaBuilder.or(
|
||||
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower),
|
||||
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)),
|
||||
searchTextToLower));
|
||||
};
|
||||
return rolloutRepository.count(RolloutHelper.likeNameOrDescription(searchText));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Slice<Rollout> findRolloutWithDetailedStatusByFilters(final Pageable pageable, final String searchText) {
|
||||
final Specification<JpaRollout> specs = likeNameOrDescription(searchText);
|
||||
final Specification<JpaRollout> specs = RolloutHelper.likeNameOrDescription(searchText);
|
||||
final Slice<JpaRollout> findAll = criteriaNoCountDao.findAll(specs, pageable, JpaRollout.class);
|
||||
setRolloutStatusDetails(findAll);
|
||||
return convertPage(findAll, pageable);
|
||||
return RolloutHelper.convertPage(findAll, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -898,6 +927,16 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
|
||||
update.getName().ifPresent(rollout::setName);
|
||||
update.getDescription().ifPresent(rollout::setDescription);
|
||||
update.getActionType().ifPresent(rollout::setActionType);
|
||||
update.getForcedTime().ifPresent(rollout::setForcedTime);
|
||||
update.getStartAt().ifPresent(rollout::setStartAt);
|
||||
update.getSet().ifPresent(setId -> {
|
||||
final DistributionSet set = distributionSetManagement.findDistributionSetById(setId);
|
||||
if (set == null) {
|
||||
throw new EntityNotFoundException("Distribution set cannot be set as it does not exists" + setId);
|
||||
}
|
||||
rollout.setDistributionSet(set);
|
||||
});
|
||||
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
@@ -906,7 +945,7 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
public Page<Rollout> findAllRolloutsWithDetailedStatus(final Pageable pageable) {
|
||||
final Page<JpaRollout> rollouts = rolloutRepository.findAll(pageable);
|
||||
setRolloutStatusDetails(rollouts);
|
||||
return convertPage(rollouts, pageable);
|
||||
return RolloutHelper.convertPage(rollouts, pageable);
|
||||
|
||||
}
|
||||
|
||||
@@ -939,13 +978,6 @@ public class JpaRolloutManagement implements RolloutManagement {
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
|
||||
if (!(RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getFinishedPercentForRunningGroup(final Long rolloutId, final Long rolloutGroupId) {
|
||||
final RolloutGroup rolloutGroup = Optional.ofNullable(rolloutGroupRepository.findOne(rolloutGroupId))
|
||||
|
||||
@@ -123,6 +123,15 @@ public class JpaTargetFilterQueryManagement implements TargetFilterQueryManageme
|
||||
return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<TargetFilterQuery> findTargetFilterQueryByQuery(final Pageable pageable, final String query) {
|
||||
List<Specification<JpaTargetFilterQuery>> specList = Collections.emptyList();
|
||||
if (!Strings.isNullOrEmpty(query)) {
|
||||
specList = Collections.singletonList(TargetFilterQuerySpecification.equalsQuery(query));
|
||||
}
|
||||
return convertPage(findTargetFilterQueryByCriteriaAPI(pageable, specList), pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<TargetFilterQuery> findTargetFilterQueryByAutoAssignDS(final Pageable pageable, final Long setId,
|
||||
final String rsqlFilter) {
|
||||
|
||||
@@ -680,7 +680,10 @@ public class JpaTargetManagement implements TargetManagement {
|
||||
public Long countTargetByTargetFilterQuery(final String targetFilterQuery) {
|
||||
final Specification<JpaTarget> specs = RSQLUtility.parse(targetFilterQuery, TargetFields.class,
|
||||
virtualPropertyReplacer);
|
||||
return targetRepository.count(specs);
|
||||
return targetRepository.count((root, query, cb) -> {
|
||||
query.distinct(true);
|
||||
return specs.toPredicate(root, query, cb);
|
||||
});
|
||||
}
|
||||
|
||||
private List<Object[]> getTargetIdNameResultSet(final Pageable pageRequest, final CriteriaBuilder cb,
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -16,10 +18,17 @@ import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
|
||||
import org.eclipse.hawkbit.repository.exception.ConstraintViolationException;
|
||||
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
|
||||
import org.eclipse.hawkbit.repository.jpa.builder.JpaRolloutGroupCreate;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaRollout_;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
/**
|
||||
* A collection of static helper methods for the {@link JpaRolloutManagement}
|
||||
@@ -69,7 +78,7 @@ final class RolloutHelper {
|
||||
* In case the given group is missing conditions or actions, they will be
|
||||
* set from the supplied default conditions.
|
||||
*
|
||||
* @param group
|
||||
* @param create
|
||||
* group to check
|
||||
* @param conditions
|
||||
* default conditions and actions
|
||||
@@ -144,10 +153,22 @@ final class RolloutHelper {
|
||||
* @return resulting target filter query
|
||||
*/
|
||||
static String getTargetFilterQuery(final Rollout rollout) {
|
||||
if (rollout.getCreatedAt() != null) {
|
||||
return rollout.getTargetFilterQuery() + ";createdat=le=" + rollout.getCreatedAt().toString();
|
||||
return getTargetFilterQuery(rollout.getTargetFilterQuery(), rollout.getCreatedAt());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param targetFilter
|
||||
* the target filter tp be extended
|
||||
* @param createdAt
|
||||
* timestamp
|
||||
* @return a target filter query that only matches targets that were created
|
||||
* after the provided timestamp.
|
||||
*/
|
||||
static String getTargetFilterQuery(final String targetFilter, final Long createdAt) {
|
||||
if (createdAt != null) {
|
||||
return targetFilter + ";createdat=le=" + createdAt.toString();
|
||||
}
|
||||
return rollout.getTargetFilterQuery();
|
||||
return targetFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,30 +236,69 @@ final class RolloutHelper {
|
||||
if (groups.stream().anyMatch(group -> StringUtils.isEmpty(group.getTargetFilterQuery()))) {
|
||||
return "";
|
||||
}
|
||||
return groups.stream().map(RolloutGroup::getTargetFilterQuery).collect(Collectors.joining(","));
|
||||
|
||||
return "(" + groups.stream().map(RolloutGroup::getTargetFilterQuery).distinct().sorted()
|
||||
.collect(Collectors.joining("),(")) + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an RSQL Filter that matches all targets that are in the provided
|
||||
* group and in the provided groups.
|
||||
*
|
||||
* @param baseFilter
|
||||
* the base filter from the rollout
|
||||
* @param groups
|
||||
* the rollout groups
|
||||
* @param group
|
||||
* the group
|
||||
* the target group
|
||||
* @return RSQL string without base filter of the Rollout. Can be an empty
|
||||
* string.
|
||||
*/
|
||||
static String getOverlappingWithGroupsTargetFilter(final List<RolloutGroup> groups, final RolloutGroup group) {
|
||||
static String getOverlappingWithGroupsTargetFilter(final String baseFilter, final List<RolloutGroup> groups,
|
||||
final RolloutGroup group) {
|
||||
final String groupFilter = group.getTargetFilterQuery();
|
||||
// when any previous group has the same filter as the target group the
|
||||
// overlap is 100%
|
||||
if (isTargetFilterInGroups(groupFilter, groups)) {
|
||||
return concatAndTargetFilters(baseFilter, groupFilter);
|
||||
}
|
||||
final String previousGroupFilters = getAllGroupsTargetFilter(groups);
|
||||
if (StringUtils.isNotEmpty(previousGroupFilters) && StringUtils.isNotEmpty(group.getTargetFilterQuery())) {
|
||||
return group.getTargetFilterQuery() + ";(" + previousGroupFilters + ")";
|
||||
} else if (StringUtils.isNotEmpty(previousGroupFilters)) {
|
||||
return "(" + previousGroupFilters + ")";
|
||||
} else if (StringUtils.isNotEmpty(group.getTargetFilterQuery())) {
|
||||
return group.getTargetFilterQuery();
|
||||
if (StringUtils.isNotEmpty(previousGroupFilters)) {
|
||||
if (StringUtils.isNotEmpty(groupFilter)) {
|
||||
return concatAndTargetFilters(baseFilter, groupFilter, previousGroupFilters);
|
||||
} else {
|
||||
return concatAndTargetFilters(baseFilter, previousGroupFilters);
|
||||
}
|
||||
}
|
||||
if (StringUtils.isNotEmpty(groupFilter)) {
|
||||
return concatAndTargetFilters(baseFilter, groupFilter);
|
||||
} else {
|
||||
return "";
|
||||
return baseFilter;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isTargetFilterInGroups(final String groupFilter, final List<RolloutGroup> groups) {
|
||||
return StringUtils.isNotEmpty(groupFilter)
|
||||
&& groups.stream().anyMatch(prevGroup -> StringUtils.isNotEmpty(prevGroup.getTargetFilterQuery())
|
||||
&& prevGroup.getTargetFilterQuery().equals(groupFilter));
|
||||
}
|
||||
|
||||
private static String concatAndTargetFilters(String... filters) {
|
||||
return "(" + Arrays.stream(filters).collect(Collectors.joining(");(")) + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param baseFilter
|
||||
* the base filter from the rollout
|
||||
* @param group
|
||||
* group for which the filter string should be created
|
||||
* @return the final target filter query for a rollout group
|
||||
*/
|
||||
static String getGroupTargetFilter(final String baseFilter, final RolloutGroup group) {
|
||||
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
|
||||
return baseFilter;
|
||||
} else {
|
||||
return concatAndTargetFilters(baseFilter, group.getTargetFilterQuery());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,4 +318,35 @@ final class RolloutHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param searchText
|
||||
* search string
|
||||
* @return criteria specification with a query for name or description of a
|
||||
* rollout
|
||||
*/
|
||||
static Specification<JpaRollout> likeNameOrDescription(final String searchText) {
|
||||
return (rolloutRoot, query, criteriaBuilder) -> {
|
||||
final String searchTextToLower = searchText.toLowerCase();
|
||||
return criteriaBuilder.or(
|
||||
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.name)), searchTextToLower),
|
||||
criteriaBuilder.like(criteriaBuilder.lower(rolloutRoot.get(JpaRollout_.description)),
|
||||
searchTextToLower));
|
||||
};
|
||||
}
|
||||
|
||||
static void checkIfRolloutCanStarted(final Rollout rollout, final Rollout mergedRollout) {
|
||||
if (!(Rollout.RolloutStatus.READY.equals(mergedRollout.getStatus()))) {
|
||||
throw new RolloutIllegalStateException("Rollout can only be started in state ready but current state is "
|
||||
+ rollout.getStatus().name().toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
static Page<Rollout> convertPage(final Page<JpaRollout> findAll, final Pageable pageable) {
|
||||
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, findAll.getTotalElements());
|
||||
}
|
||||
|
||||
static Slice<Rollout> convertPage(final Slice<JpaRollout> findAll, final Pageable pageable) {
|
||||
return new PageImpl<>(Collections.unmodifiableList(findAll.getContent()), pageable, 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ public class JpaRolloutCreate extends AbstractRolloutUpdateCreate<RolloutCreate>
|
||||
rollout.setDescription(description);
|
||||
rollout.setDistributionSet(findDistributionSetAndThrowExceptionIfNotFound(set));
|
||||
rollout.setTargetFilterQuery(targetFilterQuery);
|
||||
rollout.setStartAt(startAt);
|
||||
|
||||
if (actionType != null) {
|
||||
rollout.setActionType(actionType);
|
||||
|
||||
@@ -87,6 +87,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
||||
@Column(name = "rollout_groups_created")
|
||||
private int rolloutGroupsCreated;
|
||||
|
||||
@Column(name = "start_at")
|
||||
private Long startAt;
|
||||
|
||||
@Transient
|
||||
private transient TotalTargetCountStatus totalTargetCountStatus;
|
||||
|
||||
@@ -134,6 +137,15 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
||||
this.lastCheck = lastCheck;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getStartAt() {
|
||||
return startAt;
|
||||
}
|
||||
|
||||
public void setStartAt(Long startAt) {
|
||||
this.startAt = startAt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionType getActionType() {
|
||||
return actionType;
|
||||
|
||||
@@ -25,8 +25,7 @@ import org.springframework.stereotype.Component;
|
||||
/**
|
||||
* Scheduler to schedule the
|
||||
* {@link RolloutManagement#checkRunningRollouts(long)}. The delay between the
|
||||
* checks be be configured using the property
|
||||
* {@link #PROP_SCHEDULER_DELAY_PLACEHOLDER}.
|
||||
* checks be be configured using the properties from {@link RolloutProperties}.
|
||||
*/
|
||||
@Component
|
||||
// don't active the rollout scheduler in test, otherwise it is hard to test
|
||||
@@ -155,4 +154,40 @@ public class RolloutScheduler {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scheduler method called by the spring-async mechanism. Retrieves all
|
||||
* tenants from the {@link SystemManagement#findTenants()} and runs for each
|
||||
* tenant the {@link RolloutManagement#checkReadyRollouts(long)} in the
|
||||
* {@link SystemSecurityContext}. Used to auto start Rollouts as soon as
|
||||
* their startAt time is reached.
|
||||
*/
|
||||
@Scheduled(initialDelayString = RolloutProperties.PROP_READY_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = RolloutProperties.PROP_READY_SCHEDULER_DELAY_PLACEHOLDER)
|
||||
public void readyRolloutScheduler() {
|
||||
if (!rolloutProperties.getReadyScheduler().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOGGER.debug("rollout ready schedule checker has been triggered.");
|
||||
// run this code in system code privileged to have the necessary
|
||||
// permission to query and create entities.
|
||||
systemSecurityContext.runAsSystem(() -> {
|
||||
// workaround eclipselink that is currently not possible to
|
||||
// execute a query without multitenancy if MultiTenant
|
||||
// annotation is used.
|
||||
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
|
||||
// iterate through all tenants and execute the rollout check for
|
||||
// each tenant seperately.
|
||||
final List<String> tenants = systemManagement.findTenants();
|
||||
LOGGER.info("Checking ready rollouts for {} tenants", tenants.size());
|
||||
for (final String tenant : tenants) {
|
||||
tenantAware.runAsTenant(tenant, () -> {
|
||||
final long fixedDelay = rolloutProperties.getReadyScheduler().getFixedDelay();
|
||||
rolloutManagement.checkReadyRollouts(fixedDelay);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,15 +13,23 @@ import static org.eclipse.hawkbit.repository.FieldNameProvider.SUB_ATTRIBUTE_SEP
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Expression;
|
||||
import javax.persistence.criteria.From;
|
||||
import javax.persistence.criteria.Join;
|
||||
import javax.persistence.criteria.JoinType;
|
||||
import javax.persistence.criteria.MapJoin;
|
||||
import javax.persistence.criteria.Path;
|
||||
import javax.persistence.criteria.PluralJoin;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
|
||||
@@ -157,7 +165,6 @@ public final class RSQLUtility {
|
||||
|
||||
@Override
|
||||
public Predicate toPredicate(final Root<T> root, final CriteriaQuery<?> query, final CriteriaBuilder cb) {
|
||||
|
||||
final Node rootNode = parseRsql(rsql);
|
||||
|
||||
final JpqQueryRSQLVisitor<A, T> jpqQueryRSQLVisitor = new JpqQueryRSQLVisitor<>(root, cb, enumType,
|
||||
@@ -192,6 +199,9 @@ public final class RSQLUtility {
|
||||
private final CriteriaBuilder cb;
|
||||
private final Class<A> enumType;
|
||||
private final VirtualPropertyReplacer virtualPropertyReplacer;
|
||||
private int level;
|
||||
private boolean isOrLevel;
|
||||
private final Map<Integer, Set<Join<Object, Object>>> joinsInLevel = new HashMap<>(3);
|
||||
|
||||
private final SimpleTypeConverter simpleTypeConverter;
|
||||
|
||||
@@ -204,9 +214,41 @@ public final class RSQLUtility {
|
||||
simpleTypeConverter = new SimpleTypeConverter();
|
||||
}
|
||||
|
||||
private void beginLevel(boolean isOr) {
|
||||
level++;
|
||||
isOrLevel = isOr;
|
||||
joinsInLevel.put(level, new HashSet<>(2));
|
||||
}
|
||||
|
||||
private void endLevel() {
|
||||
joinsInLevel.remove(level);
|
||||
level--;
|
||||
isOrLevel = false;
|
||||
}
|
||||
|
||||
private Set<Join<Object, Object>> getCurrentJoins() {
|
||||
if(level > 0) {
|
||||
return joinsInLevel.get(level);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private Optional<Join<Object, Object>> findCurrentJoinOfType(final Class<?> type) {
|
||||
return getCurrentJoins().stream()
|
||||
.filter(j -> type.equals(j.getJavaType())).findFirst();
|
||||
}
|
||||
|
||||
private void addCurrentJoin(Join<Object, Object> join) {
|
||||
if(level > 0) {
|
||||
getCurrentJoins().add(join);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Predicate> visit(final AndNode node, final String param) {
|
||||
beginLevel(false);
|
||||
final List<Predicate> childs = acceptChilds(node);
|
||||
endLevel();
|
||||
if (!childs.isEmpty()) {
|
||||
return toSingleList(cb.and(childs.toArray(new Predicate[childs.size()])));
|
||||
}
|
||||
@@ -215,7 +257,9 @@ public final class RSQLUtility {
|
||||
|
||||
@Override
|
||||
public List<Predicate> visit(final OrNode node, final String param) {
|
||||
beginLevel(true);
|
||||
final List<Predicate> childs = acceptChilds(node);
|
||||
endLevel();
|
||||
if (!childs.isEmpty()) {
|
||||
return toSingleList(cb.or(childs.toArray(new Predicate[childs.size()])));
|
||||
}
|
||||
@@ -282,12 +326,28 @@ public final class RSQLUtility {
|
||||
new Exception());
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the Path for a field in the persistence layer and joins the
|
||||
* required models. This operation is part of a tree traversal through
|
||||
* an RSQL expression. It creates for every field that is not part of
|
||||
* the root model a join to the foreign model. This behavior is
|
||||
* optimized when several joins happen directly under an OR node in the
|
||||
* traversed tree. The same foreign model is only joined once.
|
||||
*
|
||||
* Example: tags.name==M;(tags.name==A,tags.name==B,tags.name==C) This
|
||||
* example joins the tags model only twice, because for the OR node in
|
||||
* brackets only one join is used.
|
||||
*
|
||||
* @param enumField
|
||||
* field from a FieldNameProvider to resolve on the
|
||||
* persistence layer
|
||||
* @param finalProperty
|
||||
* dot notated field path
|
||||
* @return the Path for a field
|
||||
*/
|
||||
private Path<Object> getFieldPath(final A enumField, final String finalProperty) {
|
||||
Path<Object> fieldPath = null;
|
||||
final String[] split = finalProperty.split("\\" + SUB_ATTRIBUTE_SEPERATOR);
|
||||
if (split.length == 0) {
|
||||
return root.get(split[0]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < split.length; i++) {
|
||||
final boolean isMapKeyField = enumField.isMap() && i == (split.length - 1);
|
||||
@@ -297,6 +357,21 @@ public final class RSQLUtility {
|
||||
|
||||
final String fieldNameSplit = split[i];
|
||||
fieldPath = (fieldPath != null) ? fieldPath.get(fieldNameSplit) : root.get(fieldNameSplit);
|
||||
if (fieldPath instanceof PluralJoin) {
|
||||
final Join<Object, ?> join = (Join<Object, ?>) fieldPath;
|
||||
final From<?, Object> joinParent = join.getParent();
|
||||
Optional<Join<Object, Object>> currentJoinOfType = findCurrentJoinOfType(join.getJavaType());
|
||||
if(currentJoinOfType.isPresent() && isOrLevel) {
|
||||
// remove the additional join and use the existing one
|
||||
joinParent.getJoins().remove(join);
|
||||
fieldPath = currentJoinOfType.get();
|
||||
} else {
|
||||
Join<Object, Object> newJoin = joinParent.join(fieldNameSplit, JoinType.LEFT);
|
||||
addCurrentJoin(newJoin);
|
||||
fieldPath = newJoin;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return fieldPath;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,19 @@ public final class TargetFilterQuerySpecification {
|
||||
// utility class
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link JpaTargetFilterQuery}s based
|
||||
* on is {@link JpaTargetFilterQuery#getQuery()}.
|
||||
*
|
||||
* @param queryValue
|
||||
* the query of the filter
|
||||
* @return the {@link JpaTargetFilterQuery} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTargetFilterQuery> equalsQuery(final String queryValue) {
|
||||
return (targetFilterQueryRoot, query, cb) -> cb.equal(targetFilterQueryRoot.get(JpaTargetFilterQuery_.query),
|
||||
queryValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link JpaTargetFilterQuery}s based
|
||||
* on is {@link JpaTargetFilterQuery#getName()}.
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sp_rollout
|
||||
ADD COLUMN start_at BIGINT;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sp_rollout
|
||||
ADD COLUMN start_at BIGINT;
|
||||
@@ -1096,6 +1096,12 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
"controllerId==" + targetPrefixName + "-*", distributionSet, successCondition, errorCondition);
|
||||
|
||||
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
|
||||
|
||||
rolloutManagement.checkReadyRollouts(0);
|
||||
|
||||
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
|
||||
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
|
||||
|
||||
rolloutManagement.startRollout(myRollout.getId());
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
@@ -1114,6 +1120,57 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify the creation and the automatic start of a rollout.")
|
||||
public void createAndAutoStartRollout() throws Exception {
|
||||
|
||||
final int amountTargetsForRollout = 500;
|
||||
final int amountGroups = 5;
|
||||
final String successCondition = "50";
|
||||
final String errorCondition = "80";
|
||||
final String rolloutName = "rolloutTest8";
|
||||
final String targetPrefixName = rolloutName;
|
||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet("dsFor" + rolloutName);
|
||||
testdataFactory.createTargets(amountTargetsForRollout, targetPrefixName + "-", targetPrefixName);
|
||||
|
||||
Rollout myRollout = createRolloutByVariables(rolloutName, "desc", amountGroups,
|
||||
"controllerId==" + targetPrefixName + "-*", distributionSet, successCondition, errorCondition);
|
||||
|
||||
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
|
||||
|
||||
// schedule rollout auto start into the future
|
||||
rolloutManagement.updateRollout(
|
||||
entityFactory.rollout().update(myRollout.getId()).startAt(System.currentTimeMillis() + 60000));
|
||||
rolloutManagement.checkReadyRollouts(0);
|
||||
|
||||
// rollout should not have been started
|
||||
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
|
||||
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.READY);
|
||||
|
||||
// schedule to now
|
||||
rolloutManagement.updateRollout(
|
||||
entityFactory.rollout().update(myRollout.getId()).startAt(System.currentTimeMillis()));
|
||||
rolloutManagement.checkReadyRollouts(0);
|
||||
|
||||
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
|
||||
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.STARTING);
|
||||
|
||||
// Run here, because scheduler is disabled during tests
|
||||
rolloutManagement.checkStartingRollouts(0);
|
||||
|
||||
final SuccessConditionRolloutStatus conditionRolloutTargetCount = new SuccessConditionRolloutStatus(
|
||||
RolloutStatus.RUNNING);
|
||||
assertThat(MultipleInvokeHelper.doWithTimeout(new RolloutStatusCallable(myRollout.getId()),
|
||||
conditionRolloutTargetCount, 15000, 500)).as("Rollout status").isNotNull();
|
||||
|
||||
myRollout = rolloutManagement.findRolloutById(myRollout.getId());
|
||||
assertThat(myRollout.getStatus()).isEqualTo(RolloutStatus.RUNNING);
|
||||
final Map<TotalTargetCountStatus.Status, Long> expectedTargetCountStatus = createInitStatusMap();
|
||||
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.RUNNING, 100L);
|
||||
expectedTargetCountStatus.put(TotalTargetCountStatus.Status.SCHEDULED, 400L);
|
||||
validateRolloutActionStatus(myRollout.getId(), expectedTargetCountStatus);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verify the creation of a Rollout with a groups definition.")
|
||||
public void createRolloutWithGroupDefinition() throws Exception {
|
||||
|
||||
@@ -21,7 +21,6 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.validation.ConstraintViolationException;
|
||||
|
||||
@@ -49,6 +48,7 @@ import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule;
|
||||
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
@@ -495,7 +495,7 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
firstList = firstList.stream()
|
||||
.map(t -> targetManagement.updateTarget(
|
||||
entityFactory.target().update(t.getControllerId()).name(t.getName().concat("\tchanged"))))
|
||||
.collect(Collectors.toList());
|
||||
.collect(toList());
|
||||
|
||||
// verify that all entries are found
|
||||
_founds: for (final Target foundTarget : allFound) {
|
||||
@@ -706,14 +706,14 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
toggleTagAssignment(targAs, targTagA);
|
||||
|
||||
assertThat(targetManagement.findTargetsByControllerIDsWithTags(
|
||||
targAs.stream().map(Target::getControllerId).collect(Collectors.toList()))).as("Target count is wrong")
|
||||
targAs.stream().map(Target::getControllerId).collect(toList()))).as("Target count is wrong")
|
||||
.hasSize(25);
|
||||
|
||||
// no lazy loading exception and tag correctly assigned
|
||||
assertThat(targetManagement
|
||||
.findTargetsByControllerIDsWithTags(
|
||||
targAs.stream().map(Target::getControllerId).collect(Collectors.toList()))
|
||||
.stream().map(target -> target.getTags().contains(targTagA)).collect(Collectors.toList()))
|
||||
targAs.stream().map(Target::getControllerId).collect(toList()))
|
||||
.stream().map(target -> target.getTags().contains(targTagA)).collect(toList()))
|
||||
.as("Tags not correctly assigned").containsOnly(true);
|
||||
}
|
||||
|
||||
@@ -755,4 +755,23 @@ public class TargetManagementTest extends AbstractJpaIntegrationTest {
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Test that RSQL filter finds targets with tags or specific ids.")
|
||||
public void findTargetsWithTagOrId() {
|
||||
final String rsqlFilter = "tag==Targ-A-Tag,id==target-id-B-00001,id==target-id-B-00008";
|
||||
final TargetTag targTagA = tagManagement.createTargetTag(entityFactory.tag().create().name("Targ-A-Tag"));
|
||||
final List<String> targAs = testdataFactory.createTargets(25, "target-id-A", "first description").stream()
|
||||
.map(Target::getControllerId).collect(toList());
|
||||
targetManagement.toggleTagAssignment(targAs, targTagA.getName());
|
||||
|
||||
testdataFactory.createTargets(25, "target-id-B", "first description");
|
||||
|
||||
Page<Target> foundTargets = targetManagement.findTargetsAll(rsqlFilter, new PageRequest(0, 100));
|
||||
|
||||
assertThat(targetManagement.findTargetsAll(new PageRequest(0, 100)).getNumberOfElements()).as("Total targets")
|
||||
.isEqualTo(50);
|
||||
assertThat(foundTargets.getTotalElements()).as("Targets in RSQL filter").isEqualTo(27L);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,6 +253,11 @@
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.gwtd3</groupId>
|
||||
<artifactId>gwt-d3-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Test -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.hawkbit</groupId>
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
<inherits name="org.eclipse.hawkbit.ui.customrenderers.CustomRendererWidgetSet" />
|
||||
<inherits name="org.eclipse.hawkbit.ui.filtermanagement.TextFieldSuggestionBox" />
|
||||
<inherits name="org.eclipse.hawkbit.ui.rollout.groupschart.GroupsPieChart" />
|
||||
<inherits name="org.eclipse.hawkbit.ui.dd.CustomAcceptCriteria" />
|
||||
|
||||
|
||||
|
||||
@@ -247,6 +247,13 @@ public class CommonDialogWindow extends Window {
|
||||
saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the original values in case no value changed check is wished
|
||||
*/
|
||||
public final void clearOriginalValues() {
|
||||
orginalValues.clear();
|
||||
}
|
||||
|
||||
protected void addCloseListenerForSaveButton() {
|
||||
saveButton.addClickListener(closeClickListener);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.common.builder;
|
||||
|
||||
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
|
||||
|
||||
import com.vaadin.data.Property;
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.themes.ValoTheme;
|
||||
|
||||
/**
|
||||
* Builds ComboBox Elements with a commonly used properties.
|
||||
*/
|
||||
public class ComboBoxBuilder {
|
||||
|
||||
private Property.ValueChangeListener valueChangeListener;
|
||||
|
||||
private String id;
|
||||
|
||||
private String prompt;
|
||||
|
||||
public ComboBoxBuilder setValueChangeListener(final Property.ValueChangeListener valueChangeListener) {
|
||||
this.valueChangeListener = valueChangeListener;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ComboBoxBuilder setId(final String id) {
|
||||
this.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ComboBoxBuilder setPrompt(String prompt) {
|
||||
this.prompt = prompt;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new ComboBox
|
||||
*/
|
||||
public ComboBox buildCombBox() {
|
||||
final ComboBox targetFilter = SPUIComponentProvider.getComboBox(null, "", null, ValoTheme.COMBOBOX_SMALL, false,
|
||||
"", prompt);
|
||||
targetFilter.setImmediate(true);
|
||||
targetFilter.setPageLength(7);
|
||||
targetFilter.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME);
|
||||
targetFilter.setSizeUndefined();
|
||||
if (id != null) {
|
||||
targetFilter.setId(id);
|
||||
}
|
||||
if (valueChangeListener != null) {
|
||||
targetFilter.addValueChangeListener(valueChangeListener);
|
||||
}
|
||||
return targetFilter;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import javax.annotation.PreDestroy;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.ui.HawkbitUI;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
@@ -65,10 +66,10 @@ public class RolloutView extends VerticalLayout implements View {
|
||||
final UIEventBus eventBus, final RolloutManagement rolloutManagement,
|
||||
final RolloutGroupManagement rolloutGroupManagement, final TargetManagement targetManagement,
|
||||
final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory,
|
||||
final I18N i18n) {
|
||||
final I18N i18n, final TargetFilterQueryManagement targetFilterQueryManagement) {
|
||||
this.permChecker = permissionChecker;
|
||||
this.rolloutListView = new RolloutListView(permissionChecker, rolloutUIState, eventBus, rolloutManagement,
|
||||
targetManagement, uiNotification, uiProperties, entityFactory, i18n);
|
||||
targetManagement, uiNotification, uiProperties, entityFactory, i18n, targetFilterQueryManagement);
|
||||
this.rolloutGroupsListView = new RolloutGroupsListView(i18n, eventBus, rolloutGroupManagement, rolloutUIState,
|
||||
permissionChecker);
|
||||
this.rolloutGroupTargetsListView = new RolloutGroupTargetsListView(eventBus, i18n, rolloutUIState);
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
|
||||
Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
|
||||
All rights reserved. This program and the accompanying materials
|
||||
are made available under the terms of the Eclipse Public License v1.0
|
||||
which accompanies this distribution, and is available at
|
||||
http://www.eclipse.org/legal/epl-v10.html
|
||||
|
||||
-->
|
||||
<module>
|
||||
<inherits name="com.vaadin.DefaultWidgetSet"/>
|
||||
|
||||
<inherits name="org.eclipse.hawkbit.ui.AppWidgetSet"/>
|
||||
|
||||
<inherits name="org.vaadin.peter.contextmenu.ContextmenuWidgetset"/>
|
||||
|
||||
<inherits name="org.vaadin.tokenfield.TokenfieldWidgetset"/>
|
||||
|
||||
<inherits name="org.eclipse.hawkbit.ui.customrenderers.CustomRendererWidgetSet"/>
|
||||
|
||||
<inherits name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset"/>
|
||||
|
||||
<inherits name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset"/>
|
||||
|
||||
<inherits name="com.github.gwtd3.D3"/>
|
||||
|
||||
</module>
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.rollout.groupschart;
|
||||
|
||||
import org.eclipse.hawkbit.ui.rollout.groupschart.client.GroupsPieChartState;
|
||||
import com.vaadin.ui.AbstractComponent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Draws a pie charts for the provided groups.
|
||||
*/
|
||||
public class GroupsPieChart extends AbstractComponent {
|
||||
|
||||
private static final long serialVersionUID = 1311542227339430098L;
|
||||
|
||||
/**
|
||||
* Updates the state of the chart
|
||||
*
|
||||
* @param groupTargetCounts
|
||||
* list of target counts
|
||||
* @param totalTargetsCount
|
||||
* total count of targets that are represented by the pie
|
||||
*/
|
||||
public void setChartState(final List<Long> groupTargetCounts, final Long totalTargetsCount) {
|
||||
getState().setGroupTargetCounts(groupTargetCounts);
|
||||
getState().setTotalTargetCount(totalTargetsCount);
|
||||
markAsDirty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GroupsPieChartState getState() {
|
||||
return (GroupsPieChartState) super.getState();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.rollout.groupschart.client;
|
||||
|
||||
import com.vaadin.client.communication.StateChangeEvent;
|
||||
import org.eclipse.hawkbit.ui.rollout.groupschart.GroupsPieChart;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.user.client.ui.Widget;
|
||||
import com.vaadin.client.ui.AbstractComponentConnector;
|
||||
import com.vaadin.shared.ui.Connect;
|
||||
|
||||
/**
|
||||
* Connector between client side GroupsPieChartWidget and service side.
|
||||
*/
|
||||
@Connect(GroupsPieChart.class)
|
||||
public class GroupsPieChartConnector extends AbstractComponentConnector {
|
||||
|
||||
private static final long serialVersionUID = -2907528194018611155L;
|
||||
|
||||
@Override
|
||||
protected Widget createWidget() {
|
||||
return GWT.create(GroupsPieChartWidget.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupsPieChartWidget getWidget() {
|
||||
return (GroupsPieChartWidget) super.getWidget();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GroupsPieChartState getState() {
|
||||
return (GroupsPieChartState) super.getState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateChanged(StateChangeEvent stateChangeEvent) {
|
||||
super.onStateChanged(stateChangeEvent);
|
||||
getWidget().update(getState().getGroupTargetCounts(), getState().getTotalTargetCount());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.rollout.groupschart.client;
|
||||
|
||||
import com.vaadin.shared.AbstractComponentState;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* State to transfer for the groups pie chart between server and client.
|
||||
*/
|
||||
public class GroupsPieChartState extends AbstractComponentState {
|
||||
|
||||
private static final long serialVersionUID = 7344220498082627571L;
|
||||
|
||||
private transient List<Long> groupTargetCounts;
|
||||
|
||||
private Long totalTargetCount;
|
||||
|
||||
public List<Long> getGroupTargetCounts() {
|
||||
return groupTargetCounts;
|
||||
}
|
||||
|
||||
public void setGroupTargetCounts(List<Long> groupTargetCounts) {
|
||||
this.groupTargetCounts = groupTargetCounts;
|
||||
}
|
||||
|
||||
public Long getTotalTargetCount() {
|
||||
return totalTargetCount;
|
||||
}
|
||||
|
||||
public void setTotalTargetCount(Long totalTargetCount) {
|
||||
this.totalTargetCount = totalTargetCount;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.rollout.groupschart.client;
|
||||
|
||||
import com.github.gwtd3.api.D3;
|
||||
import com.github.gwtd3.api.arrays.Array;
|
||||
import com.github.gwtd3.api.core.Selection;
|
||||
import com.github.gwtd3.api.core.UpdateSelection;
|
||||
import com.github.gwtd3.api.core.Value;
|
||||
import com.github.gwtd3.api.functions.DatumFunction;
|
||||
import com.github.gwtd3.api.svg.Arc;
|
||||
import com.google.gwt.dom.client.BrowserEvents;
|
||||
import com.google.gwt.dom.client.Element;
|
||||
import com.google.gwt.dom.client.Style;
|
||||
import com.google.gwt.user.client.ui.DockLayoutPanel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Draws a pie chart using D3. The slices are based on the list of Longs and on
|
||||
* the total target count. The total target count represents 100% of the pie. If
|
||||
* the sum in the list of Longs is less than the total target count a slice for
|
||||
* unassigned targets will be displayed.
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("squid:TrailingCommentCheck")
|
||||
public class GroupsPieChartWidget extends DockLayoutPanel {
|
||||
|
||||
private static final String ATTR_VISIBILITY = "visibility";
|
||||
private static final String ATTR_TRANSFORM = "transform";
|
||||
|
||||
private List<Long> groupTargetCounts;
|
||||
private Long totalTargetCount;
|
||||
private long unassignedTargets;
|
||||
|
||||
private Selection svg;
|
||||
private Selection pieGroup;
|
||||
private Selection infoText;
|
||||
|
||||
private Arc arc;
|
||||
|
||||
/**
|
||||
* Initializes the pie chart
|
||||
*/
|
||||
public GroupsPieChartWidget() {
|
||||
super(Style.Unit.PX);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
|
||||
initChart();
|
||||
|
||||
draw();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the pie chart with new data
|
||||
*
|
||||
* @param groupTargetCounts
|
||||
* list of target counts
|
||||
* @param totalTargetCount
|
||||
* total count of targets that are represented by the pie
|
||||
*/
|
||||
public void update(final List<Long> groupTargetCounts, final Long totalTargetCount) {
|
||||
this.groupTargetCounts = groupTargetCounts;
|
||||
this.totalTargetCount = totalTargetCount;
|
||||
|
||||
if (groupTargetCounts != null) {
|
||||
long sum = 0;
|
||||
for (Long targetCount : groupTargetCounts) {
|
||||
sum += targetCount;
|
||||
}
|
||||
unassignedTargets = totalTargetCount - sum;
|
||||
}
|
||||
|
||||
draw();
|
||||
|
||||
}
|
||||
|
||||
private static PieArc getPie(Long count, Long total, double startAngle) {
|
||||
final Double percentage = count.doubleValue() / total.doubleValue();
|
||||
return new PieArc(startAngle, startAngle + percentage * 2 * Math.PI);
|
||||
}
|
||||
|
||||
private void draw() {
|
||||
if (svg == null || groupTargetCounts == null || totalTargetCount == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Array<Arc> dataArray = Array.create();
|
||||
|
||||
PieArc pie = getPie(unassignedTargets, totalTargetCount, 0);
|
||||
dataArray.push(pie.getArc());
|
||||
|
||||
double lastAngle = pie.getEndAngle();
|
||||
for (int i = 0; i < groupTargetCounts.size(); i++) {
|
||||
final PieArc arcEntry = getPie(groupTargetCounts.get(i), totalTargetCount, lastAngle);
|
||||
dataArray.push(arcEntry.getArc());
|
||||
lastAngle = arcEntry.getEndAngle();
|
||||
}
|
||||
|
||||
UpdateSelection pies = pieGroup.selectAll(".pie").data(dataArray);
|
||||
pies.enter().append("path").classed("pie", true).on(BrowserEvents.MOUSEOVER, new DatumFunction<Void>() {
|
||||
@Override
|
||||
public Void apply(Element context, Value d, int index) {
|
||||
Array<Double> point = arc.centroid(d.as(Arc.class), index);
|
||||
double x = point.getNumber(0);
|
||||
double y = point.getNumber(1);
|
||||
if (index == 0) {
|
||||
updateHoverText("Unassigned: " + unassignedTargets, x, y);
|
||||
} else {
|
||||
updateHoverText(index + ": " + groupTargetCounts.get(index - 1), x, y);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}).on(BrowserEvents.MOUSEOUT, new DatumFunction<Void>() {
|
||||
@Override
|
||||
public Void apply(Element context, Value d, int index) {
|
||||
infoText.attr(ATTR_VISIBILITY, "hidden");
|
||||
return null;
|
||||
}
|
||||
});
|
||||
pies.exit().remove();
|
||||
pies.attr("d", arc);
|
||||
|
||||
}
|
||||
|
||||
private void updateHoverText(final String displayText, final double x, final double y) {
|
||||
final Selection text = infoText.select("text");
|
||||
final Selection background = infoText.select("rect");
|
||||
|
||||
text.html(displayText);
|
||||
|
||||
final double textWidth = getTextWidth(text.node());
|
||||
final double textHeight = getTextHeight(text.node());
|
||||
|
||||
background.attr("width", textWidth * 1.1);
|
||||
background.attr("height", textHeight);
|
||||
|
||||
moveSelection(background, -textWidth * 1.1 / 2.0, -textHeight*0.8);
|
||||
moveSelection(infoText, x, y);
|
||||
infoText.attr(ATTR_VISIBILITY, "visible");
|
||||
}
|
||||
|
||||
private static void moveSelection(Selection sel, double x, double y) {
|
||||
sel.attr(ATTR_TRANSFORM, "translate(" + x + ", " + y + ")");
|
||||
}
|
||||
|
||||
|
||||
private static final native double getTextWidth(Element e)/*-{
|
||||
return e.getBBox().width;
|
||||
}-*/;
|
||||
|
||||
private static final native double getTextHeight(Element e)/*-{
|
||||
return e.getBBox().height;
|
||||
}-*/;
|
||||
|
||||
private void initChart() {
|
||||
arc = D3.svg().arc().innerRadius(0).outerRadius(90);
|
||||
int height = 200;
|
||||
int width = 260;
|
||||
|
||||
svg = D3.select(this).append("svg").attr("width", width).attr("height", height).append("g");
|
||||
moveSelection(svg, (float) width / 2, (float) height / 2);
|
||||
|
||||
pieGroup = svg.append("g");
|
||||
|
||||
infoText = svg.append("g").attr(ATTR_VISIBILITY, "hidden").classed("pie-info", true);
|
||||
infoText.append("rect");
|
||||
infoText.append("text").attr("text-anchor", "middle");
|
||||
|
||||
}
|
||||
|
||||
private static class PieArc {
|
||||
private double startAngle;
|
||||
|
||||
private double endAngle;
|
||||
|
||||
public PieArc(double startAngle, double endAngle) {
|
||||
this.startAngle = startAngle;
|
||||
this.endAngle = endAngle;
|
||||
}
|
||||
|
||||
public double getEndAngle() {
|
||||
return endAngle;
|
||||
}
|
||||
|
||||
public Arc getArc() {
|
||||
return Arc.constantArc().startAngle(startAngle).endAngle(endAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,20 @@
|
||||
package org.eclipse.hawkbit.ui.rollout.rollout;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
|
||||
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
@@ -26,19 +33,22 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessActi
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
|
||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
import org.eclipse.hawkbit.ui.common.CommonDialogWindow;
|
||||
import org.eclipse.hawkbit.ui.common.CommonDialogWindow.SaveDialogCloseListener;
|
||||
import org.eclipse.hawkbit.ui.common.DistributionSetIdName;
|
||||
import org.eclipse.hawkbit.ui.common.builder.ComboBoxBuilder;
|
||||
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
|
||||
import org.eclipse.hawkbit.ui.common.builder.TextAreaBuilder;
|
||||
import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
|
||||
import org.eclipse.hawkbit.ui.common.builder.WindowBuilder;
|
||||
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.TargetFilterBeanQuery;
|
||||
import org.eclipse.hawkbit.ui.management.footer.ActionTypeOptionGroupLayout;
|
||||
import org.eclipse.hawkbit.ui.management.footer.ActionTypeOptionGroupLayout.ActionTypeOption;
|
||||
import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent;
|
||||
import org.eclipse.hawkbit.ui.rollout.groupschart.GroupsPieChart;
|
||||
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
|
||||
import org.eclipse.hawkbit.ui.utils.I18N;
|
||||
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
|
||||
@@ -47,6 +57,8 @@ import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
|
||||
import org.eclipse.hawkbit.ui.utils.UINotification;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory;
|
||||
import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer;
|
||||
import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition;
|
||||
@@ -66,6 +78,7 @@ import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.GridLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.OptionGroup;
|
||||
import com.vaadin.ui.TabSheet;
|
||||
import com.vaadin.ui.TextArea;
|
||||
import com.vaadin.ui.TextField;
|
||||
import com.vaadin.ui.themes.ValoTheme;
|
||||
@@ -83,16 +96,22 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
private final ActionTypeOptionGroupLayout actionTypeOptionGroupLayout;
|
||||
|
||||
private final AutoStartOptionGroupLayout autoStartOptionGroupLayout;
|
||||
|
||||
private final transient RolloutManagement rolloutManagement;
|
||||
|
||||
private final transient TargetManagement targetManagement;
|
||||
|
||||
private final transient TargetFilterQueryManagement targetFilterQueryManagement;
|
||||
|
||||
private final UINotification uiNotification;
|
||||
|
||||
private final UiProperties uiProperties;
|
||||
private final transient UiProperties uiProperties;
|
||||
|
||||
private final transient EntityFactory entityFactory;
|
||||
|
||||
private final DefineGroupsLayout defineGroupsLayout;
|
||||
|
||||
private final I18N i18n;
|
||||
|
||||
private final transient EventBus.UIEventBus eventBus;
|
||||
@@ -117,22 +136,29 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
private CommonDialogWindow window;
|
||||
|
||||
private Boolean editRolloutEnabled;
|
||||
private boolean editRolloutEnabled;
|
||||
|
||||
private Rollout rolloutForEdit;
|
||||
private Rollout rollout;
|
||||
|
||||
private Long totalTargetsCount;
|
||||
|
||||
private Label totalTargetsLabel;
|
||||
|
||||
private TextArea targetFilterQuery;
|
||||
|
||||
private TabSheet groupsDefinitionTabs;
|
||||
|
||||
private GroupsPieChart groupsPieChart;
|
||||
|
||||
private GroupsLegendLayout groupsLegendLayout;
|
||||
|
||||
private final transient RolloutGroupConditions defaultRolloutGroupConditions;
|
||||
|
||||
private final NullValidator nullValidator = new NullValidator(null, false);
|
||||
|
||||
AddUpdateRolloutWindowLayout(final RolloutManagement rolloutManagement, final TargetManagement targetManagement,
|
||||
final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory,
|
||||
final I18N i18n, final UIEventBus eventBus) {
|
||||
final I18N i18n, final UIEventBus eventBus, final TargetFilterQueryManagement targetFilterQueryManagement) {
|
||||
this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupLayout(i18n);
|
||||
this.autoStartOptionGroupLayout = new AutoStartOptionGroupLayout(i18n);
|
||||
this.rolloutManagement = rolloutManagement;
|
||||
this.targetManagement = targetManagement;
|
||||
this.uiNotification = uiNotification;
|
||||
@@ -140,10 +166,20 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
this.entityFactory = entityFactory;
|
||||
this.i18n = i18n;
|
||||
this.eventBus = eventBus;
|
||||
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
||||
|
||||
this.defineGroupsLayout = new DefineGroupsLayout(i18n, entityFactory, rolloutManagement,
|
||||
targetFilterQueryManagement);
|
||||
|
||||
defaultRolloutGroupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
|
||||
|
||||
setSizeUndefined();
|
||||
createRequiredComponents();
|
||||
buildLayout();
|
||||
|
||||
defineGroupsLayout.setValidationListener(this::displayValidationStatus);
|
||||
defineGroupsLayout.setDefaultErrorThreshold(defaultRolloutGroupConditions.getErrorConditionExp());
|
||||
defineGroupsLayout.setDefaultTriggerThreshold(defaultRolloutGroupConditions.getSuccessConditionExp());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,27 +209,39 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
*
|
||||
* @param rolloutId
|
||||
* the rollout id
|
||||
* @param copy
|
||||
* whether the rollout should be copied
|
||||
* @return the window
|
||||
*/
|
||||
public CommonDialogWindow getWindow(final Long rolloutId) {
|
||||
window = getWindow();
|
||||
populateData(rolloutId);
|
||||
public CommonDialogWindow getWindow(final Long rolloutId, final boolean copy) {
|
||||
resetComponents();
|
||||
window = createWindow();
|
||||
populateData(rolloutId, copy);
|
||||
return window;
|
||||
}
|
||||
|
||||
public CommonDialogWindow getWindow() {
|
||||
resetComponents();
|
||||
private CommonDialogWindow createWindow() {
|
||||
return new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW).caption(i18n.get("caption.configure.rollout"))
|
||||
.content(this).layout(this).i18n(i18n)
|
||||
.helpLink(uiProperties.getLinks().getDocumentation().getRolloutView())
|
||||
.saveDialogCloseListener(new SaveOnDialogCloseListener()).buildCommonDialogWindow();
|
||||
}
|
||||
|
||||
public CommonDialogWindow getWindow() {
|
||||
resetComponents();
|
||||
window = createWindow();
|
||||
window.updateAllComponents(noOfGroups);
|
||||
window.updateAllComponents(triggerThreshold);
|
||||
window.updateAllComponents(errorThreshold);
|
||||
return window;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the field values.
|
||||
*/
|
||||
public void resetComponents() {
|
||||
editRolloutEnabled = Boolean.FALSE;
|
||||
defineGroupsLayout.resetComponents();
|
||||
editRolloutEnabled = false;
|
||||
rolloutName.clear();
|
||||
targetFilterQuery.clear();
|
||||
resetFields();
|
||||
@@ -201,22 +249,26 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
populateDistributionSet();
|
||||
populateTargetFilterQuery();
|
||||
setDefaultSaveStartGroupOption();
|
||||
totalTargetsLabel.setVisible(false);
|
||||
groupsLegendLayout.reset();
|
||||
groupSizeLabel.setVisible(false);
|
||||
noOfGroups.setVisible(true);
|
||||
removeComponent(1, 2);
|
||||
addComponent(targetFilterQueryCombo, 1, 2);
|
||||
actionTypeOptionGroupLayout.selectDefaultOption();
|
||||
autoStartOptionGroupLayout.selectDefaultOption();
|
||||
totalTargetsCount = 0L;
|
||||
rolloutForEdit = null;
|
||||
rollout = null;
|
||||
groupsDefinitionTabs.setVisible(true);
|
||||
groupsDefinitionTabs.setSelectedTab(0);
|
||||
}
|
||||
|
||||
private void resetFields() {
|
||||
rolloutName.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
|
||||
noOfGroups.clear();
|
||||
noOfGroups.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
|
||||
triggerThreshold.clear();
|
||||
triggerThreshold.setValue(defaultRolloutGroupConditions.getSuccessConditionExp());
|
||||
triggerThreshold.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
|
||||
errorThreshold.clear();
|
||||
errorThreshold.setValue(defaultRolloutGroupConditions.getErrorConditionExp());
|
||||
errorThreshold.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
|
||||
description.clear();
|
||||
description.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
|
||||
@@ -224,11 +276,13 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
private void buildLayout() {
|
||||
|
||||
setSpacing(Boolean.TRUE);
|
||||
setSpacing(true);
|
||||
setSizeUndefined();
|
||||
setRows(9);
|
||||
setColumns(3);
|
||||
setRows(7);
|
||||
setColumns(4);
|
||||
setStyleName("marginTop");
|
||||
setColumnExpandRatio(3, 1);
|
||||
setWidth(850, Unit.PIXELS);
|
||||
|
||||
addComponent(getMandatoryLabel("textfield.name"), 0, 0);
|
||||
addComponent(rolloutName, 1, 0);
|
||||
@@ -243,28 +297,19 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
targetFilterQueryCombo.addValidator(nullValidator);
|
||||
targetFilterQuery.removeValidator(nullValidator);
|
||||
|
||||
addComponent(totalTargetsLabel, 2, 2);
|
||||
addComponent(getLabel("textfield.description"), 0, 3);
|
||||
addComponent(description, 1, 3, 1, 3);
|
||||
|
||||
addComponent(getMandatoryLabel("prompt.number.of.groups"), 0, 3);
|
||||
addComponent(noOfGroups, 1, 3);
|
||||
noOfGroups.addValidator(nullValidator);
|
||||
addComponent(groupsLegendLayout, 3, 0, 3, 3);
|
||||
addComponent(groupsPieChart, 2, 0, 2, 3);
|
||||
|
||||
addComponent(groupSizeLabel, 2, 3);
|
||||
addComponent(getMandatoryLabel("caption.rollout.action.type"), 0, 4);
|
||||
addComponent(actionTypeOptionGroupLayout, 1, 4, 3, 4);
|
||||
|
||||
addComponent(getMandatoryLabel("prompt.tigger.threshold"), 0, 4);
|
||||
addComponent(triggerThreshold, 1, 4);
|
||||
triggerThreshold.addValidator(nullValidator);
|
||||
addComponent(getMandatoryLabel("caption.rollout.start.type"), 0, 5);
|
||||
addComponent(autoStartOptionGroupLayout, 1, 5, 3, 5);
|
||||
|
||||
addComponent(getPercentHintLabel(), 2, 4);
|
||||
|
||||
addComponent(getMandatoryLabel("prompt.error.threshold"), 0, 5);
|
||||
addComponent(errorThreshold, 1, 5);
|
||||
errorThreshold.addValidator(nullValidator);
|
||||
addComponent(errorThresholdOptionGroup, 2, 5);
|
||||
|
||||
addComponent(getLabel("textfield.description"), 0, 6);
|
||||
addComponent(description, 1, 6, 2, 6);
|
||||
addComponent(actionTypeOptionGroupLayout, 0, 7, 2, 7);
|
||||
addComponent(groupsDefinitionTabs, 0, 6, 3, 6);
|
||||
|
||||
rolloutName.focus();
|
||||
}
|
||||
@@ -310,15 +355,93 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
noOfGroups = createNoOfGroupsField();
|
||||
groupSizeLabel = createCountLabel();
|
||||
|
||||
triggerThreshold = createTriggerThreshold();
|
||||
errorThreshold = createErrorThreshold();
|
||||
description = createDescription();
|
||||
errorThresholdOptionGroup = createErrorThresholdOptionGroup();
|
||||
setDefaultSaveStartGroupOption();
|
||||
actionTypeOptionGroupLayout.selectDefaultOption();
|
||||
totalTargetsLabel = createCountLabel();
|
||||
autoStartOptionGroupLayout.selectDefaultOption();
|
||||
targetFilterQuery = createTargetFilterQuery();
|
||||
actionTypeOptionGroupLayout.addStyleName(SPUIStyleDefinitions.ROLLOUT_ACTION_TYPE_LAYOUT);
|
||||
autoStartOptionGroupLayout.addStyleName(SPUIStyleDefinitions.ROLLOUT_ACTION_TYPE_LAYOUT);
|
||||
|
||||
groupsDefinitionTabs = createGroupDefinitionTabs();
|
||||
|
||||
groupsPieChart = new GroupsPieChart();
|
||||
groupsPieChart.setWidth(260, Unit.PIXELS);
|
||||
groupsPieChart.setHeight(220, Unit.PIXELS);
|
||||
groupsPieChart.setStyleName(SPUIStyleDefinitions.ROLLOUT_GROUPS_CHART);
|
||||
|
||||
groupsLegendLayout = new GroupsLegendLayout(i18n);
|
||||
|
||||
}
|
||||
|
||||
private void displayValidationStatus(DefineGroupsLayout.ValidationStatus status) {
|
||||
if(status == DefineGroupsLayout.ValidationStatus.LOADING) {
|
||||
groupsLegendLayout.displayLoading();
|
||||
} else {
|
||||
validateGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private TabSheet createGroupDefinitionTabs() {
|
||||
TabSheet tabSheet = new TabSheet();
|
||||
tabSheet.setId(UIComponentIdProvider.ROLLOUT_GROUPS);
|
||||
tabSheet.setWidth(850, Unit.PIXELS);
|
||||
tabSheet.setHeight(300, Unit.PIXELS);
|
||||
tabSheet.setStyleName(SPUIStyleDefinitions.ROLLOUT_GROUPS);
|
||||
|
||||
TabSheet.Tab simpleTab = tabSheet.addTab(createSimpleGroupDefinitionTab(),
|
||||
i18n.get("caption.rollout.tabs.simple"));
|
||||
simpleTab.setId(UIComponentIdProvider.ROLLOUT_SIMPLE_TAB);
|
||||
|
||||
TabSheet.Tab advancedTab = tabSheet.addTab(defineGroupsLayout, i18n.get("caption.rollout.tabs.advanced"));
|
||||
advancedTab.setId(UIComponentIdProvider.ROLLOUT_ADVANCED_TAB);
|
||||
|
||||
tabSheet.addSelectedTabChangeListener(event -> validateGroups());
|
||||
|
||||
return tabSheet;
|
||||
}
|
||||
|
||||
private static int getPositionOfSelectedTab(final TabSheet tabSheet) {
|
||||
return tabSheet.getTabPosition(tabSheet.getTab(tabSheet.getSelectedTab()));
|
||||
}
|
||||
|
||||
private boolean isNumberOfGroups() {
|
||||
return getPositionOfSelectedTab(groupsDefinitionTabs) == 0;
|
||||
}
|
||||
|
||||
private boolean isGroupsDefinition() {
|
||||
return getPositionOfSelectedTab(groupsDefinitionTabs) == 1;
|
||||
}
|
||||
|
||||
private GridLayout createSimpleGroupDefinitionTab() {
|
||||
GridLayout layout = new GridLayout();
|
||||
layout.setSpacing(true);
|
||||
layout.setColumns(3);
|
||||
layout.setRows(4);
|
||||
layout.setStyleName("marginTop");
|
||||
|
||||
layout.addComponent(getLabel("caption.rollout.generate.groups"), 0, 0, 2, 0);
|
||||
|
||||
layout.addComponent(getMandatoryLabel("prompt.number.of.groups"), 0, 1);
|
||||
layout.addComponent(noOfGroups, 1, 1);
|
||||
noOfGroups.addValidator(nullValidator);
|
||||
layout.addComponent(groupSizeLabel, 2, 1);
|
||||
|
||||
layout.addComponent(getMandatoryLabel("prompt.tigger.threshold"), 0, 2);
|
||||
layout.addComponent(triggerThreshold, 1, 2);
|
||||
triggerThreshold.addValidator(nullValidator);
|
||||
layout.addComponent(getPercentHintLabel(), 2, 2);
|
||||
|
||||
layout.addComponent(getMandatoryLabel("prompt.error.threshold"), 0, 3);
|
||||
layout.addComponent(errorThreshold, 1, 3);
|
||||
errorThreshold.addValidator(nullValidator);
|
||||
layout.addComponent(errorThresholdOptionGroup, 2, 3);
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
private static Label createCountLabel() {
|
||||
@@ -365,35 +488,95 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
errorThreshold.getValidators();
|
||||
}
|
||||
|
||||
private void validateGroups() {
|
||||
if(editRolloutEnabled) {
|
||||
return;
|
||||
}
|
||||
if(isGroupsDefinition()) {
|
||||
List<RolloutGroupCreate> savedRolloutGroups = defineGroupsLayout.getSavedRolloutGroups();
|
||||
if(!defineGroupsLayout.isValid() || savedRolloutGroups == null || savedRolloutGroups.isEmpty()) {
|
||||
noOfGroups.clear();
|
||||
} else {
|
||||
noOfGroups.setValue(String.valueOf(savedRolloutGroups.size()));
|
||||
}
|
||||
updateGroupsChart(defineGroupsLayout.getGroupsValidation());
|
||||
}
|
||||
if(isNumberOfGroups()) {
|
||||
if(noOfGroups.isValid() && !noOfGroups.getValue().isEmpty()) {
|
||||
updateGroupsChart(Integer.parseInt(noOfGroups.getValue()));
|
||||
} else {
|
||||
updateGroupsChart(0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void updateGroupsChart(final RolloutGroupsValidation validation) {
|
||||
if(validation == null) {
|
||||
groupsPieChart.setChartState(null, null);
|
||||
return;
|
||||
}
|
||||
List<Long> targetsPerGroup = validation.getTargetsPerGroup();
|
||||
if(validation.getTotalTargets() == 0L || targetsPerGroup.isEmpty()) {
|
||||
groupsPieChart.setChartState(null, null);
|
||||
} else {
|
||||
groupsPieChart.setChartState(targetsPerGroup, validation.getTotalTargets());
|
||||
}
|
||||
|
||||
totalTargetsCount = validation.getTotalTargets();
|
||||
groupsLegendLayout.populateTotalTargets(validation.getTotalTargets());
|
||||
groupsLegendLayout.populateGroupsLegendByValidation(validation, defineGroupsLayout.getSavedRolloutGroups());
|
||||
|
||||
}
|
||||
|
||||
private void updateGroupsChart(final List<RolloutGroup> savedGroups, long totalTargetsCount) {
|
||||
List<Long> targetsPerGroup = savedGroups.stream().map(group -> (long) group.getTotalTargets())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
groupsPieChart.setChartState(targetsPerGroup, totalTargetsCount);
|
||||
groupsLegendLayout.populateGroupsLegendByGroups(savedGroups);
|
||||
}
|
||||
|
||||
private void updateGroupsChart(final int amountOfGroups) {
|
||||
if (totalTargetsCount == null || totalTargetsCount == 0L || amountOfGroups == 0) {
|
||||
groupsPieChart.setChartState(null, null);
|
||||
groupsLegendLayout.populateGroupsLegendByTargetCounts(Collections.emptyList());
|
||||
} else {
|
||||
final List<Long> groups = new ArrayList<>(amountOfGroups);
|
||||
long leftTargets = totalTargetsCount;
|
||||
for (int i = 0; i < amountOfGroups; i++) {
|
||||
float percentage = 1.0F / (amountOfGroups - i);
|
||||
long targetsInGroup = Math.round(percentage * (double) leftTargets);
|
||||
leftTargets -= targetsInGroup;
|
||||
groups.add(targetsInGroup);
|
||||
}
|
||||
|
||||
groupsPieChart.setChartState(groups, totalTargetsCount);
|
||||
groupsLegendLayout.populateGroupsLegendByTargetCounts(groups);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private ComboBox createTargetFilterQueryCombo() {
|
||||
final ComboBox targetFilter = SPUIComponentProvider.getComboBox(null, "", null, ValoTheme.COMBOBOX_SMALL, false,
|
||||
"", i18n.get("prompt.target.filter"));
|
||||
targetFilter.setImmediate(true);
|
||||
targetFilter.setPageLength(7);
|
||||
targetFilter.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME);
|
||||
targetFilter.setId(UIComponentIdProvider.ROLLOUT_TARGET_FILTER_COMBO_ID);
|
||||
targetFilter.setSizeUndefined();
|
||||
targetFilter.addValueChangeListener(this::onTargetFilterChange);
|
||||
return targetFilter;
|
||||
return new ComboBoxBuilder().setValueChangeListener(this::onTargetFilterChange)
|
||||
.setPrompt(i18n.get("prompt.target.filter")).setId(UIComponentIdProvider.ROLLOUT_TARGET_FILTER_COMBO_ID)
|
||||
.buildCombBox();
|
||||
}
|
||||
|
||||
private void onTargetFilterChange(final ValueChangeEvent event) {
|
||||
final String filterQueryString = getTargetFilterQuery();
|
||||
if (!Strings.isNullOrEmpty(filterQueryString)) {
|
||||
totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(filterQueryString);
|
||||
totalTargetsLabel.setValue(getTotalTargetMessage());
|
||||
totalTargetsLabel.setVisible(true);
|
||||
} else {
|
||||
if (Strings.isNullOrEmpty(filterQueryString)) {
|
||||
totalTargetsCount = 0L;
|
||||
totalTargetsLabel.setVisible(false);
|
||||
groupsLegendLayout.populateTotalTargets(null);
|
||||
defineGroupsLayout.setTargetFilter(null);
|
||||
} else {
|
||||
totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(filterQueryString);
|
||||
groupsLegendLayout.populateTotalTargets(totalTargetsCount);
|
||||
defineGroupsLayout.setTargetFilter(filterQueryString);
|
||||
}
|
||||
onGroupNumberChange(event);
|
||||
}
|
||||
|
||||
private String getTotalTargetMessage() {
|
||||
return new StringBuilder(i18n.get("label.target.filter.count")).append(totalTargetsCount).toString();
|
||||
}
|
||||
|
||||
private String getTargetPerGroupMessage(final String value) {
|
||||
return new StringBuilder(i18n.get("label.target.per.group")).append(value).toString();
|
||||
}
|
||||
@@ -403,6 +586,16 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
targetFilterQueryCombo.setContainerDataSource(container);
|
||||
}
|
||||
|
||||
private void populateTargetFilterQuery(final Rollout rollout) {
|
||||
final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement
|
||||
.findTargetFilterQueryByQuery(new PageRequest(0, 1), rollout.getTargetFilterQuery());
|
||||
if(filterQueries.getTotalElements() > 0) {
|
||||
final TargetFilterQuery filterQuery = filterQueries.getContent().get(0);
|
||||
targetFilterQueryCombo.setValue(filterQuery.getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Container createTargetFilterComboContainer() {
|
||||
final BeanQueryFactory<TargetFilterBeanQuery> targetFilterQF = new BeanQueryFactory<>(
|
||||
TargetFilterBeanQuery.class);
|
||||
@@ -412,19 +605,32 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
}
|
||||
|
||||
private void editRollout() {
|
||||
if (rolloutForEdit == null) {
|
||||
if (rollout == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Rollout updatedRollout = rolloutManagement.updateRollout(entityFactory.rollout()
|
||||
.update(rolloutForEdit.getId()).name(rolloutName.getValue()).description(description.getValue()));
|
||||
uiNotification.displaySuccess(i18n.get("message.update.success", new Object[] { updatedRollout.getName() }));
|
||||
final DistributionSetIdName distributionSetIdName = (DistributionSetIdName) distributionSet.getValue();
|
||||
|
||||
RolloutUpdate rolloutUpdate = entityFactory.rollout().update(rollout.getId()).name(rolloutName.getValue())
|
||||
.description(description.getValue()).set(distributionSetIdName.getId()).actionType(getActionType())
|
||||
.forcedTime(getForcedTimeStamp());
|
||||
|
||||
if (AutoStartOptionGroupLayout.AutoStartOption.AUTO_START.equals(getAutoStartOption())) {
|
||||
rolloutUpdate.startAt(System.currentTimeMillis());
|
||||
}
|
||||
if (AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())) {
|
||||
rolloutUpdate.startAt(getScheduledStartTime());
|
||||
}
|
||||
|
||||
final Rollout updatedRollout = rolloutManagement.updateRollout(rolloutUpdate);
|
||||
|
||||
uiNotification.displaySuccess(i18n.get("message.update.success", updatedRollout.getName()));
|
||||
eventBus.publish(this, RolloutEvent.UPDATE_ROLLOUT);
|
||||
}
|
||||
|
||||
private boolean duplicateCheckForEdit() {
|
||||
final String rolloutNameVal = getRolloutName();
|
||||
if (!rolloutForEdit.getName().equals(rolloutNameVal)
|
||||
if (!rollout.getName().equals(rolloutNameVal)
|
||||
&& rolloutManagement.findRolloutByName(rolloutNameVal) != null) {
|
||||
uiNotification.displayValidationError(i18n.get("message.rollout.duplicate.check", rolloutNameVal));
|
||||
return false;
|
||||
@@ -433,10 +639,19 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
}
|
||||
|
||||
private long getForcedTimeStamp() {
|
||||
return (((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup()
|
||||
.getValue()) == ActionTypeOption.AUTO_FORCED)
|
||||
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
|
||||
: RepositoryModelConstants.NO_FORCE_TIME;
|
||||
return ActionTypeOption.AUTO_FORCED.equals(actionTypeOptionGroupLayout.getActionTypeOptionGroup().getValue())
|
||||
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
|
||||
: RepositoryModelConstants.NO_FORCE_TIME;
|
||||
}
|
||||
|
||||
private Long getScheduledStartTime() {
|
||||
return AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())
|
||||
? autoStartOptionGroupLayout.getStartAtDateField().getValue().getTime() : null;
|
||||
}
|
||||
|
||||
private AutoStartOptionGroupLayout.AutoStartOption getAutoStartOption() {
|
||||
return (AutoStartOptionGroupLayout.AutoStartOption) autoStartOptionGroupLayout.getAutoStartOptionGroup()
|
||||
.getValue();
|
||||
}
|
||||
|
||||
private ActionType getActionType() {
|
||||
@@ -446,26 +661,40 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
private void createRollout() {
|
||||
final Rollout rolloutToCreate = saveRollout();
|
||||
uiNotification.displaySuccess(i18n.get("message.save.success", new Object[] { rolloutToCreate.getName() }));
|
||||
uiNotification.displaySuccess(i18n.get("message.save.success", rolloutToCreate.getName()));
|
||||
}
|
||||
|
||||
private Rollout saveRollout() {
|
||||
|
||||
final int amountGroup = Integer.parseInt(noOfGroups.getValue());
|
||||
final int errorThresoldPercent = getErrorThresoldPercentage(amountGroup);
|
||||
|
||||
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
|
||||
.successAction(RolloutGroupSuccessAction.NEXTGROUP, null)
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue())
|
||||
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, String.valueOf(errorThresoldPercent))
|
||||
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
|
||||
|
||||
final DistributionSetIdName distributionSetIdName = (DistributionSetIdName) distributionSet.getValue();
|
||||
|
||||
return rolloutManagement.createRollout(entityFactory.rollout().create().name(rolloutName.getValue())
|
||||
final int amountGroup = Integer.parseInt(noOfGroups.getValue());
|
||||
final int errorThresholdPercent = getErrorThresholdPercentage(amountGroup);
|
||||
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder()
|
||||
.successAction(RolloutGroupSuccessAction.NEXTGROUP, null)
|
||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue())
|
||||
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, String.valueOf(errorThresholdPercent))
|
||||
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
|
||||
|
||||
final RolloutCreate rolloutCreate = entityFactory.rollout().create().name(rolloutName.getValue())
|
||||
.description(description.getValue()).set(distributionSetIdName.getId())
|
||||
.targetFilterQuery(getTargetFilterQuery()).actionType(getActionType()).forcedTime(getForcedTimeStamp()),
|
||||
amountGroup, conditions);
|
||||
.targetFilterQuery(getTargetFilterQuery()).actionType(getActionType()).forcedTime(getForcedTimeStamp());
|
||||
|
||||
if (AutoStartOptionGroupLayout.AutoStartOption.AUTO_START.equals(getAutoStartOption())) {
|
||||
rolloutCreate.startAt(System.currentTimeMillis());
|
||||
}
|
||||
if (AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())) {
|
||||
rolloutCreate.startAt(getScheduledStartTime());
|
||||
}
|
||||
|
||||
if (isNumberOfGroups()) {
|
||||
return rolloutManagement.createRollout(rolloutCreate, amountGroup, conditions);
|
||||
} else if (isGroupsDefinition()) {
|
||||
List<RolloutGroupCreate> groups = defineGroupsLayout.getSavedRolloutGroups();
|
||||
return rolloutManagement.createRollout(rolloutCreate, groups, conditions);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Either of the Tabs must be selected");
|
||||
}
|
||||
|
||||
private String getTargetFilterQuery() {
|
||||
@@ -478,7 +707,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
return null;
|
||||
}
|
||||
|
||||
private int getErrorThresoldPercentage(final int amountGroup) {
|
||||
private int getErrorThresholdPercentage(final int amountGroup) {
|
||||
int errorThresoldPercent = Integer.parseInt(errorThreshold.getValue());
|
||||
if (errorThresholdOptionGroup.getValue().equals(ERRORTHRESOLDOPTIONS.COUNT.getValue())) {
|
||||
final int groupSize = (int) Math.ceil((double) totalTargetsCount / (double) amountGroup);
|
||||
@@ -491,7 +720,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
private boolean duplicateCheck() {
|
||||
if (rolloutManagement.findRolloutByName(getRolloutName()) != null) {
|
||||
uiNotification.displayValidationError(
|
||||
i18n.get("message.rollout.duplicate.check", new Object[] { getRolloutName() }));
|
||||
i18n.get("message.rollout.duplicate.check", getRolloutName()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -515,6 +744,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
UIComponentIdProvider.ROLLOUT_ERROR_THRESOLD_ID);
|
||||
errorField.addValidator(new ThresholdFieldValidator());
|
||||
errorField.setMaxLength(7);
|
||||
errorField.setValue(defaultRolloutGroupConditions.getErrorConditionExp());
|
||||
return errorField;
|
||||
}
|
||||
|
||||
@@ -522,6 +752,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
final TextField thresholdField = createIntegerTextField("prompt.tigger.threshold",
|
||||
UIComponentIdProvider.ROLLOUT_TRIGGER_THRESOLD_ID);
|
||||
thresholdField.addValidator(new ThresholdFieldValidator());
|
||||
thresholdField.setValue(defaultRolloutGroupConditions.getSuccessConditionExp());
|
||||
return thresholdField;
|
||||
}
|
||||
|
||||
@@ -535,23 +766,25 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
}
|
||||
|
||||
private void onGroupNumberChange(final ValueChangeEvent event) {
|
||||
if (event.getProperty().getValue() != null && noOfGroups.isValid()) {
|
||||
if(editRolloutEnabled) {
|
||||
return;
|
||||
}
|
||||
if (event.getProperty().getValue() != null && noOfGroups.isValid() && totalTargetsCount != null
|
||||
&& isNumberOfGroups()) {
|
||||
groupSizeLabel.setValue(getTargetPerGroupMessage(String.valueOf(getGroupSize())));
|
||||
groupSizeLabel.setVisible(true);
|
||||
updateGroupsChart(Integer.parseInt(noOfGroups.getValue()));
|
||||
} else {
|
||||
groupSizeLabel.setVisible(false);
|
||||
if(isNumberOfGroups()) {
|
||||
updateGroupsChart(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ComboBox createDistributionSetCombo() {
|
||||
final ComboBox dsSet = SPUIComponentProvider.getComboBox(null, "", null, ValoTheme.COMBOBOX_SMALL, false, "",
|
||||
i18n.get("prompt.distribution.set"));
|
||||
dsSet.setImmediate(true);
|
||||
dsSet.setPageLength(7);
|
||||
dsSet.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME);
|
||||
dsSet.setId(UIComponentIdProvider.ROLLOUT_DS_ID);
|
||||
dsSet.setSizeUndefined();
|
||||
return dsSet;
|
||||
return new ComboBoxBuilder().setPrompt(i18n.get("prompt.distribution.set"))
|
||||
.setId(UIComponentIdProvider.ROLLOUT_DS_ID).buildCombBox();
|
||||
}
|
||||
|
||||
private void populateDistributionSet() {
|
||||
@@ -618,12 +851,12 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
}
|
||||
|
||||
class GroupNumberValidator implements Validator {
|
||||
private static final long serialVersionUID = 9049939751976326550L;
|
||||
private static final long serialVersionUID = 9043919751971326521L;
|
||||
|
||||
@Override
|
||||
public void validate(final Object value) {
|
||||
if (value != null) {
|
||||
new IntegerRangeValidator(i18n.get(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 0, 500), 0, 500)
|
||||
new IntegerRangeValidator(i18n.get(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 1, 500), 1, 500)
|
||||
.validate(Integer.valueOf(value.toString()));
|
||||
}
|
||||
}
|
||||
@@ -636,31 +869,47 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
* @param rolloutId
|
||||
* rollout id
|
||||
*/
|
||||
private void populateData(final Long rolloutId) {
|
||||
private void populateData(final Long rolloutId, final boolean copy) {
|
||||
if (rolloutId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
editRolloutEnabled = Boolean.TRUE;
|
||||
rolloutForEdit = rolloutManagement.findRolloutById(rolloutId);
|
||||
rolloutName.setValue(rolloutForEdit.getName());
|
||||
description.setValue(rolloutForEdit.getDescription());
|
||||
distributionSet.setValue(DistributionSetIdName.generate(rolloutForEdit.getDistributionSet()));
|
||||
final List<RolloutGroup> rolloutGroups = rolloutForEdit.getRolloutGroups();
|
||||
setThresholdValues(rolloutGroups);
|
||||
setActionType(rolloutForEdit);
|
||||
disableRequiredFieldsOnEdit();
|
||||
targetFilterQuery.setValue(rolloutForEdit.getTargetFilterQuery());
|
||||
removeComponent(1, 2);
|
||||
targetFilterQueryCombo.removeValidator(nullValidator);
|
||||
addComponent(targetFilterQuery, 1, 2);
|
||||
targetFilterQuery.addValidator(nullValidator);
|
||||
rollout = rolloutManagement.findRolloutById(rolloutId);
|
||||
description.setValue(rollout.getDescription());
|
||||
distributionSet.setValue(DistributionSetIdName.generate(rollout.getDistributionSet()));
|
||||
setActionType(rollout);
|
||||
setAutoStartType(rollout);
|
||||
|
||||
totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(rolloutForEdit.getTargetFilterQuery());
|
||||
totalTargetsLabel.setValue(getTotalTargetMessage());
|
||||
totalTargetsLabel.setVisible(true);
|
||||
if (copy) {
|
||||
rolloutName.setValue(i18n.get("textfield.rollout.copied.name", rollout.getName()));
|
||||
populateTargetFilterQuery(rollout);
|
||||
|
||||
window.setOrginaleValues();
|
||||
defineGroupsLayout.populateByRollout(rollout);
|
||||
groupsDefinitionTabs.setSelectedTab(1);
|
||||
|
||||
window.clearOriginalValues();
|
||||
|
||||
} else {
|
||||
editRolloutEnabled = true;
|
||||
if (rollout.getStatus() != Rollout.RolloutStatus.READY) {
|
||||
disableRequiredFieldsOnEdit();
|
||||
}
|
||||
rolloutName.setValue(rollout.getName());
|
||||
groupsDefinitionTabs.setVisible(false);
|
||||
|
||||
targetFilterQuery.setValue(rollout.getTargetFilterQuery());
|
||||
removeComponent(1, 2);
|
||||
targetFilterQueryCombo.removeValidator(nullValidator);
|
||||
addComponent(targetFilterQuery, 1, 2);
|
||||
targetFilterQuery.addValidator(nullValidator);
|
||||
|
||||
window.setOrginaleValues();
|
||||
|
||||
updateGroupsChart(rollout.getRolloutGroups(), rollout.getTotalTargets());
|
||||
}
|
||||
|
||||
totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery());
|
||||
groupsLegendLayout.populateTotalTargets(totalTargetsCount);
|
||||
}
|
||||
|
||||
private void disableRequiredFieldsOnEdit() {
|
||||
@@ -668,9 +917,11 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
distributionSet.setEnabled(false);
|
||||
errorThreshold.setEnabled(false);
|
||||
triggerThreshold.setEnabled(false);
|
||||
actionTypeOptionGroupLayout.getActionTypeOptionGroup().setEnabled(false);
|
||||
errorThresholdOptionGroup.setEnabled(false);
|
||||
actionTypeOptionGroupLayout.getActionTypeOptionGroup().setEnabled(false);
|
||||
actionTypeOptionGroupLayout.addStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT);
|
||||
autoStartOptionGroupLayout.getAutoStartOptionGroup().setEnabled(false);
|
||||
autoStartOptionGroupLayout.addStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT);
|
||||
}
|
||||
|
||||
private void enableFields() {
|
||||
@@ -679,6 +930,8 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
triggerThreshold.setEnabled(true);
|
||||
actionTypeOptionGroupLayout.getActionTypeOptionGroup().setEnabled(true);
|
||||
actionTypeOptionGroupLayout.removeStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT);
|
||||
autoStartOptionGroupLayout.getAutoStartOptionGroup().setEnabled(true);
|
||||
autoStartOptionGroupLayout.removeStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT);
|
||||
noOfGroups.setEnabled(true);
|
||||
targetFilterQueryCombo.setEnabled(true);
|
||||
errorThresholdOptionGroup.setEnabled(true);
|
||||
@@ -697,39 +950,22 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param rolloutGroups
|
||||
*/
|
||||
private void setThresholdValues(final List<RolloutGroup> rolloutGroups) {
|
||||
if (rolloutGroups != null && !rolloutGroups.isEmpty()) {
|
||||
errorThreshold.setValue(rolloutGroups.get(0).getErrorConditionExp());
|
||||
triggerThreshold.setValue(rolloutGroups.get(0).getSuccessConditionExp());
|
||||
noOfGroups.setValue(String.valueOf(rolloutGroups.size()));
|
||||
private void setAutoStartType(final Rollout rollout) {
|
||||
if (rollout.getStartAt() == null) {
|
||||
autoStartOptionGroupLayout.getAutoStartOptionGroup()
|
||||
.setValue(AutoStartOptionGroupLayout.AutoStartOption.MANUAL);
|
||||
} else if (rollout.getStartAt() < System.currentTimeMillis()) {
|
||||
autoStartOptionGroupLayout.getAutoStartOptionGroup()
|
||||
.setValue(AutoStartOptionGroupLayout.AutoStartOption.AUTO_START);
|
||||
autoStartOptionGroupLayout.getStartAtDateField().setValue(new Date(rollout.getStartAt()));
|
||||
} else {
|
||||
errorThreshold.setValue("0");
|
||||
triggerThreshold.setValue("0");
|
||||
noOfGroups.setValue("0");
|
||||
autoStartOptionGroupLayout.getAutoStartOptionGroup()
|
||||
.setValue(AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED);
|
||||
autoStartOptionGroupLayout.getStartAtDateField().setValue(new Date(rollout.getStartAt()));
|
||||
}
|
||||
}
|
||||
|
||||
enum SAVESTARTOPTIONS {
|
||||
SAVE("Save"), START("Start");
|
||||
|
||||
String value;
|
||||
|
||||
SAVESTARTOPTIONS(final String val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the value
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
enum ERRORTHRESOLDOPTIONS {
|
||||
private enum ERRORTHRESOLDOPTIONS {
|
||||
PERCENT("%"), COUNT("Count");
|
||||
|
||||
String value;
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.rollout.rollout;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.eclipse.hawkbit.ui.utils.I18N;
|
||||
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
|
||||
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
|
||||
import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroup;
|
||||
import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroupItemComponent;
|
||||
|
||||
import com.vaadin.data.Property.ValueChangeEvent;
|
||||
import com.vaadin.data.Property.ValueChangeListener;
|
||||
import com.vaadin.server.FontAwesome;
|
||||
import com.vaadin.shared.ui.datefield.Resolution;
|
||||
import com.vaadin.ui.DateField;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.themes.ValoTheme;
|
||||
|
||||
/**
|
||||
* Rollout start types options layout
|
||||
*/
|
||||
public class AutoStartOptionGroupLayout extends HorizontalLayout {
|
||||
|
||||
private static final long serialVersionUID = -8460459258964093525L;
|
||||
private static final String STYLE_DIST_WINDOW_AUTO_START = "dist-window-actiontype";
|
||||
|
||||
private final I18N i18n;
|
||||
|
||||
private FlexibleOptionGroup autoStartOptionGroup;
|
||||
|
||||
private DateField startAtDateField;
|
||||
|
||||
/**
|
||||
* Instantiates the auto start options layout
|
||||
*
|
||||
* @param i18n
|
||||
* the internationalization helper
|
||||
*/
|
||||
public AutoStartOptionGroupLayout(final I18N i18n) {
|
||||
this.i18n = i18n;
|
||||
|
||||
createOptionGroup();
|
||||
addValueChangeListener();
|
||||
setStyleName("dist-window-actiontype-horz-layout");
|
||||
setSizeUndefined();
|
||||
}
|
||||
|
||||
private void addValueChangeListener() {
|
||||
autoStartOptionGroup.addValueChangeListener(new ValueChangeListener() {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public void valueChange(final ValueChangeEvent event) {
|
||||
if (event.getProperty().getValue().equals(AutoStartOption.SCHEDULED)) {
|
||||
startAtDateField.setEnabled(true);
|
||||
startAtDateField.setRequired(true);
|
||||
} else {
|
||||
startAtDateField.setEnabled(false);
|
||||
startAtDateField.setRequired(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createOptionGroup() {
|
||||
autoStartOptionGroup = new FlexibleOptionGroup();
|
||||
autoStartOptionGroup.addItem(AutoStartOption.MANUAL);
|
||||
autoStartOptionGroup.addItem(AutoStartOption.AUTO_START);
|
||||
autoStartOptionGroup.addItem(AutoStartOption.SCHEDULED);
|
||||
selectDefaultOption();
|
||||
|
||||
final FlexibleOptionGroupItemComponent manualItem = autoStartOptionGroup
|
||||
.getItemComponent(AutoStartOption.MANUAL);
|
||||
manualItem.setStyleName(STYLE_DIST_WINDOW_AUTO_START);
|
||||
// set Id for Forced radio button.
|
||||
manualItem.setId(UIComponentIdProvider.ROLLOUT_START_MANUAL_ID);
|
||||
addComponent(manualItem);
|
||||
final Label manualLabel = new Label();
|
||||
manualLabel.setStyleName("statusIconPending");
|
||||
manualLabel.setIcon(FontAwesome.HAND_PAPER_O);
|
||||
manualLabel.setCaption(i18n.get("caption.rollout.start.manual"));
|
||||
manualLabel.setDescription(i18n.get("caption.rollout.start.manual.desc"));
|
||||
manualLabel.setStyleName("padding-right-style");
|
||||
addComponent(manualLabel);
|
||||
|
||||
final FlexibleOptionGroupItemComponent autoStartItem = autoStartOptionGroup
|
||||
.getItemComponent(AutoStartOption.AUTO_START);
|
||||
autoStartItem.setId(UIComponentIdProvider.ROLLOUT_START_AUTO_ID);
|
||||
autoStartItem.setStyleName(STYLE_DIST_WINDOW_AUTO_START);
|
||||
addComponent(autoStartItem);
|
||||
final Label autoStartLabel = new Label();
|
||||
autoStartLabel.setSizeFull();
|
||||
autoStartLabel.setIcon(FontAwesome.PLAY);
|
||||
autoStartLabel.setCaption(i18n.get("caption.rollout.start.auto"));
|
||||
autoStartLabel.setDescription(i18n.get("caption.rollout.start.auto.desc"));
|
||||
autoStartLabel.setStyleName("padding-right-style");
|
||||
addComponent(autoStartLabel);
|
||||
|
||||
final FlexibleOptionGroupItemComponent scheduledItem = autoStartOptionGroup
|
||||
.getItemComponent(AutoStartOption.SCHEDULED);
|
||||
scheduledItem.setStyleName(STYLE_DIST_WINDOW_AUTO_START);
|
||||
// setted Id for Time Forced radio button.
|
||||
scheduledItem.setId(UIComponentIdProvider.ROLLOUT_START_SCHEDULED_ID);
|
||||
addComponent(scheduledItem);
|
||||
final Label scheduledLabel = new Label();
|
||||
scheduledLabel.setStyleName("statusIconPending");
|
||||
scheduledLabel.setIcon(FontAwesome.CLOCK_O);
|
||||
scheduledLabel.setCaption(i18n.get("caption.rollout.start.scheduled"));
|
||||
scheduledLabel.setDescription(i18n.get("caption.rollout.start.scheduled.desc"));
|
||||
scheduledLabel.setStyleName(STYLE_DIST_WINDOW_AUTO_START);
|
||||
addComponent(scheduledLabel);
|
||||
|
||||
startAtDateField = new DateField();
|
||||
startAtDateField.setInvalidAllowed(false);
|
||||
startAtDateField.setInvalidCommitted(false);
|
||||
startAtDateField.setEnabled(false);
|
||||
startAtDateField.setStyleName("dist-window-forcedtime");
|
||||
|
||||
final TimeZone tz = SPDateTimeUtil.getBrowserTimeZone();
|
||||
startAtDateField.setValue(
|
||||
Date.from(LocalDateTime.now().plusMinutes(30).atZone(SPDateTimeUtil.getTimeZoneId(tz)).toInstant()));
|
||||
startAtDateField.setImmediate(true);
|
||||
startAtDateField.setTimeZone(tz);
|
||||
startAtDateField.setLocale(i18n.getLocale());
|
||||
startAtDateField.setResolution(Resolution.MINUTE);
|
||||
startAtDateField.addStyleName(ValoTheme.DATEFIELD_SMALL);
|
||||
addComponent(startAtDateField);
|
||||
}
|
||||
|
||||
/**
|
||||
* To Set Default option for save.
|
||||
*/
|
||||
|
||||
public void selectDefaultOption() {
|
||||
autoStartOptionGroup.select(AutoStartOption.MANUAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollout start options
|
||||
*/
|
||||
public enum AutoStartOption {
|
||||
MANUAL, AUTO_START, SCHEDULED;
|
||||
|
||||
}
|
||||
|
||||
public FlexibleOptionGroup getAutoStartOptionGroup() {
|
||||
return autoStartOptionGroup;
|
||||
}
|
||||
|
||||
public DateField getStartAtDateField() {
|
||||
return startAtDateField;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.rollout.rollout;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.vaadin.ui.UI;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
|
||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||
import org.eclipse.hawkbit.ui.common.builder.ComboBoxBuilder;
|
||||
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
|
||||
import org.eclipse.hawkbit.ui.common.builder.TextAreaBuilder;
|
||||
import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
|
||||
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
|
||||
import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorderWithIcon;
|
||||
import org.eclipse.hawkbit.ui.filtermanagement.TargetFilterBeanQuery;
|
||||
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
|
||||
import org.eclipse.hawkbit.ui.utils.I18N;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
|
||||
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.util.concurrent.ListenableFuture;
|
||||
import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory;
|
||||
import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer;
|
||||
import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition;
|
||||
|
||||
import com.vaadin.data.Container;
|
||||
import com.vaadin.data.Item;
|
||||
import com.vaadin.data.Validator;
|
||||
import com.vaadin.data.util.converter.StringToFloatConverter;
|
||||
import com.vaadin.data.util.converter.StringToIntegerConverter;
|
||||
import com.vaadin.data.validator.FloatRangeValidator;
|
||||
import com.vaadin.data.validator.IntegerRangeValidator;
|
||||
import com.vaadin.data.validator.StringLengthValidator;
|
||||
import com.vaadin.server.FontAwesome;
|
||||
import com.vaadin.server.UserError;
|
||||
import com.vaadin.ui.Button;
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.GridLayout;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.TextArea;
|
||||
import com.vaadin.ui.TextField;
|
||||
|
||||
/**
|
||||
* Define groups for a Rollout
|
||||
*/
|
||||
public class DefineGroupsLayout extends GridLayout {
|
||||
|
||||
private static final long serialVersionUID = 2939193468001472916L;
|
||||
|
||||
private I18N i18n;
|
||||
|
||||
private transient EntityFactory entityFactory;
|
||||
|
||||
private transient RolloutManagement rolloutManagement;
|
||||
|
||||
private transient TargetFilterQueryManagement targetFilterQueryManagement;
|
||||
|
||||
private String defaultTriggerThreshold;
|
||||
|
||||
private String defaultErrorThreshold;
|
||||
|
||||
private String targetFilter;
|
||||
|
||||
private transient List<GroupRow> groupRows;
|
||||
|
||||
private int groupsCount;
|
||||
|
||||
private transient List<RolloutGroupCreate> savedRolloutGroups;
|
||||
|
||||
private transient ValidationListener validationListener;
|
||||
|
||||
private ValidationStatus validationStatus = ValidationStatus.VALID;
|
||||
|
||||
private transient RolloutGroupsValidation groupsValidation;
|
||||
|
||||
private transient ListenableFuture<RolloutGroupsValidation> runningValidation;
|
||||
|
||||
private boolean validationRequested;
|
||||
|
||||
DefineGroupsLayout(I18N i18n, EntityFactory entityFactory, RolloutManagement rolloutManagement,
|
||||
TargetFilterQueryManagement targetFilterQueryManagement) {
|
||||
this.i18n = i18n;
|
||||
this.entityFactory = entityFactory;
|
||||
this.rolloutManagement = rolloutManagement;
|
||||
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
||||
|
||||
groupRows = new ArrayList<>(10);
|
||||
setSizeUndefined();
|
||||
buildLayout();
|
||||
|
||||
}
|
||||
|
||||
private void buildLayout() {
|
||||
|
||||
setSpacing(Boolean.TRUE);
|
||||
setSizeUndefined();
|
||||
setRows(3);
|
||||
setColumns(6);
|
||||
setStyleName("marginTop");
|
||||
|
||||
addComponent(getLabel("caption.rollout.group.definition.desc"), 0, 0, 5, 0);
|
||||
|
||||
final int headerRow = 1;
|
||||
addComponent(getLabel("header.name"), 0, headerRow);
|
||||
addComponent(getLabel("header.target.filter.query"), 1, headerRow);
|
||||
addComponent(getLabel("header.target.percentage"), 2, headerRow);
|
||||
addComponent(getLabel("header.rolloutgroup.threshold"), 3, headerRow);
|
||||
addComponent(getLabel("header.rolloutgroup.threshold.error"), 4, headerRow);
|
||||
|
||||
addComponent(createAddButton(), 0, 2, 5, 2);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param targetFilter
|
||||
* the target filter which is required for verification
|
||||
*/
|
||||
public void setTargetFilter(final String targetFilter) {
|
||||
this.targetFilter = targetFilter;
|
||||
updateValidation();
|
||||
}
|
||||
|
||||
private int addRow() {
|
||||
final int insertIndex = getRows() - 1;
|
||||
insertRow(insertIndex);
|
||||
return insertIndex;
|
||||
}
|
||||
|
||||
private Label getLabel(final String key) {
|
||||
return new LabelBuilder().name(i18n.get(key)).buildLabel();
|
||||
}
|
||||
|
||||
private Button createAddButton() {
|
||||
Button button = SPUIComponentProvider.getButton(UIComponentIdProvider.ROLLOUT_GROUP_ADD_ID,
|
||||
i18n.get("button.rollout.add.group"), "", "", true, FontAwesome.PLUS,
|
||||
SPUIButtonStyleNoBorderWithIcon.class);
|
||||
button.setSizeUndefined();
|
||||
button.addStyleName("default-color");
|
||||
button.setEnabled(true);
|
||||
button.setVisible(true);
|
||||
button.addClickListener(event -> addGroupRow());
|
||||
return button;
|
||||
}
|
||||
|
||||
private GroupRow addGroupRow() {
|
||||
int rowIndex = addRow();
|
||||
GroupRow groupRow = new GroupRow();
|
||||
groupRow.addToGridRow(this, rowIndex);
|
||||
groupRows.add(groupRow);
|
||||
updateValidation();
|
||||
return groupRow;
|
||||
}
|
||||
|
||||
public List<RolloutGroupCreate> getSavedRolloutGroups() {
|
||||
return savedRolloutGroups;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the validation instance if was already validated
|
||||
*/
|
||||
public RolloutGroupsValidation getGroupsValidation() {
|
||||
return groupsValidation;
|
||||
}
|
||||
|
||||
private void removeAllRows() {
|
||||
for (int i = getRows() - 2; i > 1; i--) {
|
||||
removeRow(i);
|
||||
}
|
||||
|
||||
groupRows.clear();
|
||||
}
|
||||
|
||||
public void setDefaultTriggerThreshold(final String defaultTriggerThreshold) {
|
||||
this.defaultTriggerThreshold = defaultTriggerThreshold;
|
||||
}
|
||||
|
||||
public void setDefaultErrorThreshold(final String defaultErrorThreshold) {
|
||||
this.defaultErrorThreshold = defaultErrorThreshold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the field values.
|
||||
*/
|
||||
public void resetComponents() {
|
||||
|
||||
validationStatus = ValidationStatus.VALID;
|
||||
groupsCount = 0;
|
||||
|
||||
removeAllRows();
|
||||
addGroupRow();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate groups by rollout
|
||||
*
|
||||
* @param rollout
|
||||
* the rollout
|
||||
*/
|
||||
public void populateByRollout(final Rollout rollout) {
|
||||
if (rollout == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
removeAllRows();
|
||||
|
||||
final List<RolloutGroup> groups = rollout.getRolloutGroups();
|
||||
for (final RolloutGroup group : groups) {
|
||||
final GroupRow groupRow = addGroupRow();
|
||||
groupRow.populateByGroup(group);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the groups definition form is valid
|
||||
*/
|
||||
public boolean isValid() {
|
||||
if (groupRows.isEmpty() || validationStatus != ValidationStatus.VALID) {
|
||||
return false;
|
||||
}
|
||||
return groupRows.stream().allMatch(GroupRow::isValid);
|
||||
}
|
||||
|
||||
private void updateValidation() {
|
||||
validationStatus = ValidationStatus.VALID;
|
||||
if (isValid()) {
|
||||
setValidationStatus(ValidationStatus.LOADING);
|
||||
savedRolloutGroups = getGroupsFromRows();
|
||||
validateRemainingTargets();
|
||||
|
||||
} else {
|
||||
resetRemainingTargetsError();
|
||||
setValidationStatus(ValidationStatus.INVALID);
|
||||
}
|
||||
}
|
||||
|
||||
private void setValidationStatus(final ValidationStatus status) {
|
||||
validationStatus = status;
|
||||
if (validationListener != null) {
|
||||
validationListener.validation(status);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetRemainingTargetsError() {
|
||||
groupRows.forEach(GroupRow::hideLastGroupError);
|
||||
}
|
||||
|
||||
private void validateRemainingTargets() {
|
||||
resetRemainingTargetsError();
|
||||
if (targetFilter == null || (runningValidation != null && !runningValidation.isDone())) {
|
||||
validationRequested = true;
|
||||
return;
|
||||
}
|
||||
|
||||
validationRequested = false;
|
||||
|
||||
final UI ui = UI.getCurrent();
|
||||
|
||||
runningValidation = rolloutManagement
|
||||
.validateTargetsInGroups(savedRolloutGroups, targetFilter, System.currentTimeMillis());
|
||||
runningValidation.addCallback(validation -> ui.access(() -> this.setGroupsValidation(validation)),
|
||||
throwable -> ui.access(() -> this.setGroupsValidation(null)));
|
||||
|
||||
}
|
||||
|
||||
private void setGroupsValidation(RolloutGroupsValidation validation) {
|
||||
groupsValidation = validation;
|
||||
|
||||
if(validationRequested) {
|
||||
validateRemainingTargets();
|
||||
return;
|
||||
}
|
||||
|
||||
final GroupRow lastRow = groupRows.get(groupRows.size() - 1);
|
||||
if (groupsValidation != null && groupsValidation.isValid() && validationStatus != ValidationStatus.INVALID) {
|
||||
lastRow.hideLastGroupError();
|
||||
setValidationStatus(ValidationStatus.VALID);
|
||||
|
||||
} else {
|
||||
lastRow.markWithLastGroupError();
|
||||
setValidationStatus(ValidationStatus.INVALID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<RolloutGroupCreate> getGroupsFromRows() {
|
||||
return groupRows.stream().map(GroupRow::getGroupEntity).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public void setValidationListener(final ValidationListener validationListener) {
|
||||
this.validationListener = validationListener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of the groups validation
|
||||
*/
|
||||
public enum ValidationStatus {
|
||||
VALID,
|
||||
INVALID,
|
||||
LOADING
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the interface and set the instance with setValidationListener
|
||||
* to receive updates for any changes within the group rows.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ValidationListener {
|
||||
/**
|
||||
* Is called after user input
|
||||
*
|
||||
* @param isValid
|
||||
* whether the input of the group rows is valid
|
||||
*/
|
||||
void validation(ValidationStatus isValid);
|
||||
}
|
||||
|
||||
|
||||
private class GroupRow {
|
||||
|
||||
private TextField groupName;
|
||||
|
||||
private ComboBox targetFilterQueryCombo;
|
||||
|
||||
private TextArea targetFilterQuery;
|
||||
|
||||
private TextField targetPercentage;
|
||||
|
||||
private TextField triggerThreshold;
|
||||
|
||||
private TextField errorThreshold;
|
||||
|
||||
private HorizontalLayout optionsLayout;
|
||||
|
||||
private boolean populated;
|
||||
|
||||
private boolean initialized;
|
||||
|
||||
public GroupRow() {
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
groupsCount += 1;
|
||||
groupName = createTextField("textfield.name", UIComponentIdProvider.ROLLOUT_GROUP_LIST_GRID_ID);
|
||||
groupName.setValue(i18n.get("textfield.rollout.group.default.name", groupsCount));
|
||||
groupName.setStyleName("rollout-group-name");
|
||||
|
||||
targetFilterQueryCombo = createTargetFilterQueryCombo();
|
||||
populateTargetFilterQuery();
|
||||
|
||||
targetFilterQuery = createTargetFilterQuery();
|
||||
|
||||
targetPercentage = createPercentageWithDecimalsField("textfield.target.percentage",
|
||||
UIComponentIdProvider.ROLLOUT_GROUP_TARGET_PERC_ID);
|
||||
targetPercentage.setValue("100");
|
||||
|
||||
triggerThreshold = createPercentageField("prompt.tigger.threshold",
|
||||
UIComponentIdProvider.ROLLOUT_TRIGGER_THRESOLD_ID);
|
||||
triggerThreshold.setValue(defaultTriggerThreshold);
|
||||
|
||||
errorThreshold = createPercentageField("prompt.error.threshold",
|
||||
UIComponentIdProvider.ROLLOUT_ERROR_THRESOLD_ID);
|
||||
errorThreshold.setValue(defaultErrorThreshold);
|
||||
|
||||
optionsLayout = new HorizontalLayout();
|
||||
optionsLayout.addComponent(createRemoveButton());
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
private TextField createTextField(final String in18Key, final String id) {
|
||||
final TextField textField = new TextFieldBuilder().prompt(i18n.get(in18Key)).immediate(true).id(id)
|
||||
.buildTextComponent();
|
||||
textField.setSizeUndefined();
|
||||
textField.addValidator(
|
||||
new StringLengthValidator(i18n.get("message.rollout.group.name.invalid"), 1, 64, false));
|
||||
textField.addValueChangeListener(event -> valueChanged());
|
||||
return textField;
|
||||
}
|
||||
|
||||
private TextField createPercentageField(final String in18Key, final String id) {
|
||||
final TextField textField = new TextFieldBuilder().prompt(i18n.get(in18Key)).immediate(true).id(id)
|
||||
.buildTextComponent();
|
||||
textField.setWidth(80, Unit.PIXELS);
|
||||
textField.setNullRepresentation("");
|
||||
textField.setConverter(new StringToIntegerConverter());
|
||||
textField.addValidator(this::validateMandatoryPercentage);
|
||||
textField.addValueChangeListener(event -> valueChanged());
|
||||
return textField;
|
||||
}
|
||||
|
||||
private TextField createPercentageWithDecimalsField(final String in18Key, final String id) {
|
||||
final TextField textField = createPercentageField(in18Key, id);
|
||||
textField.setConverter(new StringToFloatConverter());
|
||||
return textField;
|
||||
}
|
||||
|
||||
private void removeGroupRow(final GroupRow groupRow) {
|
||||
groupRows.remove(groupRow);
|
||||
updateValidation();
|
||||
}
|
||||
|
||||
private void validateMandatoryPercentage(final Object value) {
|
||||
if (value != null) {
|
||||
final String message = i18n.get("message.rollout.field.value.range", 0, 100);
|
||||
if (value instanceof Float) {
|
||||
new FloatRangeValidator(message, 0F, 100F).validate(value);
|
||||
}
|
||||
if (value instanceof Integer) {
|
||||
new IntegerRangeValidator(message, 0, 100).validate(value);
|
||||
}
|
||||
} else {
|
||||
throw new Validator.EmptyValueException(i18n.get("message.enter.number"));
|
||||
}
|
||||
}
|
||||
|
||||
private void valueChanged() {
|
||||
if (initialized) {
|
||||
updateValidation();
|
||||
}
|
||||
}
|
||||
|
||||
private ComboBox createTargetFilterQueryCombo() {
|
||||
return new ComboBoxBuilder().setId(UIComponentIdProvider.ROLLOUT_TARGET_FILTER_COMBO_ID)
|
||||
.setPrompt(i18n.get("prompt.target.filter")).setValueChangeListener(event -> valueChanged())
|
||||
.buildCombBox();
|
||||
}
|
||||
|
||||
private TextArea createTargetFilterQuery() {
|
||||
final TextArea filterField = new TextAreaBuilder().style("text-area-style")
|
||||
.id(UIComponentIdProvider.ROLLOUT_TARGET_FILTER_QUERY_FIELD)
|
||||
.maxLengthAllowed(SPUILabelDefinitions.TARGET_FILTER_QUERY_TEXT_FIELD_LENGTH).buildTextComponent();
|
||||
|
||||
filterField.setNullRepresentation(StringUtils.EMPTY);
|
||||
filterField.setEnabled(false);
|
||||
filterField.setSizeUndefined();
|
||||
return filterField;
|
||||
}
|
||||
|
||||
private void populateTargetFilterQuery() {
|
||||
final Container container = createTargetFilterComboContainer();
|
||||
targetFilterQueryCombo.setContainerDataSource(container);
|
||||
}
|
||||
|
||||
private void populateTargetFilterQuery(final RolloutGroup group) {
|
||||
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
|
||||
targetFilterQueryCombo.setValue(null);
|
||||
} else {
|
||||
final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement
|
||||
.findTargetFilterQueryByQuery(new PageRequest(0, 1), group.getTargetFilterQuery());
|
||||
if (filterQueries.getTotalElements() > 0) {
|
||||
final TargetFilterQuery filterQuery = filterQueries.getContent().get(0);
|
||||
targetFilterQueryCombo.setValue(filterQuery.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Container createTargetFilterComboContainer() {
|
||||
final BeanQueryFactory<TargetFilterBeanQuery> targetFilterQF = new BeanQueryFactory<>(
|
||||
TargetFilterBeanQuery.class);
|
||||
return new LazyQueryContainer(
|
||||
new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, SPUILabelDefinitions.VAR_NAME),
|
||||
targetFilterQF);
|
||||
}
|
||||
|
||||
private Button createRemoveButton() {
|
||||
Button button = SPUIComponentProvider.getButton(UIComponentIdProvider.ROLLOUT_GROUP_REMOVE_ID, "", "", "",
|
||||
true, FontAwesome.MINUS, SPUIButtonStyleNoBorderWithIcon.class);
|
||||
button.setSizeUndefined();
|
||||
button.addStyleName("default-color");
|
||||
button.setEnabled(true);
|
||||
button.setVisible(true);
|
||||
button.addClickListener(event -> onRemove());
|
||||
return button;
|
||||
}
|
||||
|
||||
private void onRemove() {
|
||||
int index = findRowIndexFor(groupName, 0);
|
||||
if (index != -1) {
|
||||
removeRow(index);
|
||||
}
|
||||
removeGroupRow(this);
|
||||
}
|
||||
|
||||
private int findRowIndexFor(final Component component, final int col) {
|
||||
final int rows = getRows();
|
||||
for (int i = 0; i < rows; i++) {
|
||||
Component rowComponent = getComponent(col, i);
|
||||
if (component.equals(rowComponent)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private String getTargetFilterQuery() {
|
||||
if (null != targetFilterQueryCombo.getValue()
|
||||
&& HawkbitCommonUtil.trimAndNullIfEmpty((String) targetFilterQueryCombo.getValue()) != null) {
|
||||
final Item filterItem = targetFilterQueryCombo.getContainerDataSource()
|
||||
.getItem(targetFilterQueryCombo.getValue());
|
||||
return (String) filterItem.getItemProperty("query").getValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this group row to a grid layout
|
||||
*
|
||||
* @param layout
|
||||
* the grid layout
|
||||
* @param rowIndex
|
||||
* the row of the grid layout
|
||||
*/
|
||||
public void addToGridRow(GridLayout layout, int rowIndex) {
|
||||
layout.addComponent(groupName, 0, rowIndex);
|
||||
if (populated) {
|
||||
layout.addComponent(targetFilterQuery, 1, rowIndex);
|
||||
} else {
|
||||
layout.addComponent(targetFilterQueryCombo, 1, rowIndex);
|
||||
}
|
||||
layout.addComponent(targetPercentage, 2, rowIndex);
|
||||
layout.addComponent(triggerThreshold, 3, rowIndex);
|
||||
layout.addComponent(errorThreshold, 4, rowIndex);
|
||||
layout.addComponent(optionsLayout, 5, rowIndex);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a group definition from this group row
|
||||
*
|
||||
* @return the RolloutGroupCreate definition
|
||||
*/
|
||||
public RolloutGroupCreate getGroupEntity() {
|
||||
final RolloutGroupConditionBuilder conditionBuilder = new RolloutGroupConditionBuilder()
|
||||
.successAction(RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP, null)
|
||||
.successCondition(RolloutGroup.RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue());
|
||||
if (StringUtils.isNotEmpty(errorThreshold.getValue())) {
|
||||
conditionBuilder
|
||||
.errorCondition(RolloutGroup.RolloutGroupErrorCondition.THRESHOLD, errorThreshold.getValue())
|
||||
.errorAction(RolloutGroup.RolloutGroupErrorAction.PAUSE, null);
|
||||
}
|
||||
String percentageString = targetPercentage.getValue().replace(",", ".");
|
||||
Float percentage = Float.parseFloat(percentageString);
|
||||
|
||||
return entityFactory.rolloutGroup().create().name(groupName.getValue()).description(groupName.getValue())
|
||||
.targetFilterQuery(getTargetFilterQuery()).targetPercentage(percentage)
|
||||
.conditions(conditionBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the row with the data from the provided groups.
|
||||
*
|
||||
* @param group
|
||||
* the data source
|
||||
*/
|
||||
public void populateByGroup(final RolloutGroup group) {
|
||||
|
||||
groupName.setValue(group.getName());
|
||||
targetFilterQuery.setValue(group.getTargetFilterQuery());
|
||||
populateTargetFilterQuery(group);
|
||||
|
||||
targetPercentage.setValue(String.format("%.2f", group.getTargetPercentage()));
|
||||
triggerThreshold.setValue(group.getSuccessConditionExp());
|
||||
errorThreshold.setValue(group.getErrorConditionExp());
|
||||
|
||||
populated = true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return whether the data entered in this row is valid
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return StringUtils.isNotEmpty(groupName.getValue()) && targetPercentage.isValid()
|
||||
&& triggerThreshold.isValid() && errorThreshold.isValid();
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays an error for the row
|
||||
*/
|
||||
public void markWithLastGroupError() {
|
||||
targetPercentage.setComponentError(new UserError(i18n.get("message.rollout.remaining.targets.error")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides an error of the row
|
||||
*/
|
||||
public void hideLastGroupError() {
|
||||
targetPercentage.setComponentError(null);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
|
||||
*
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.rollout.rollout;
|
||||
|
||||
import com.vaadin.ui.Component;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
|
||||
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
|
||||
import org.eclipse.hawkbit.ui.utils.I18N;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Displays a legend for the Groups of a Rollout with the count of targets in
|
||||
* each group. On top of the group list, the total targets are displayed. If
|
||||
* there are unassigned targets, they get display on top of the groups list.
|
||||
*/
|
||||
public class GroupsLegendLayout extends VerticalLayout {
|
||||
|
||||
private static final long serialVersionUID = 5483206203739308677L;
|
||||
|
||||
private final I18N i18n;
|
||||
|
||||
private Label totalTargetsLabel;
|
||||
|
||||
private Label loadingLabel;
|
||||
|
||||
private Label unassignedTargetsLabel;
|
||||
|
||||
private VerticalLayout groupsLegend;
|
||||
|
||||
/**
|
||||
* Initializes a new GroupsLegendLayout
|
||||
*/
|
||||
GroupsLegendLayout(final I18N i18n) {
|
||||
this.i18n = i18n;
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
|
||||
totalTargetsLabel = createTotalTargetsLabel();
|
||||
unassignedTargetsLabel = createUnassignedTargetsLabel();
|
||||
loadingLabel = createLoadingLabel();
|
||||
loadingLabel.setVisible(false);
|
||||
|
||||
groupsLegend = new VerticalLayout();
|
||||
groupsLegend.setStyleName("groups-legend");
|
||||
|
||||
addComponent(totalTargetsLabel);
|
||||
addComponent(loadingLabel);
|
||||
addComponent(unassignedTargetsLabel);
|
||||
addComponent(groupsLegend);
|
||||
for (int i = 0; i < 8; i++) {
|
||||
groupsLegend.addComponent(createGroupTargetsLabel());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the display of the legend and total targets.
|
||||
*/
|
||||
public void reset() {
|
||||
totalTargetsLabel.setVisible(false);
|
||||
populateGroupsLegendByTargetCounts(Collections.emptyList());
|
||||
}
|
||||
|
||||
private static Label createTotalTargetsLabel() {
|
||||
final Label label = new LabelBuilder().visible(false).name("").buildLabel();
|
||||
label.addStyleName("rollout-target-count-title");
|
||||
label.setImmediate(true);
|
||||
label.setSizeUndefined();
|
||||
return label;
|
||||
}
|
||||
|
||||
private Label createLoadingLabel() {
|
||||
final Label label = new LabelBuilder().visible(false).name("").buildLabel();
|
||||
label.addStyleName("rollout-target-count-loading");
|
||||
label.setImmediate(true);
|
||||
label.setSizeUndefined();
|
||||
label.setValue(i18n.get("label.rollout.calculating"));
|
||||
return label;
|
||||
}
|
||||
|
||||
private static Label createUnassignedTargetsLabel() {
|
||||
final Label label = new LabelBuilder().visible(false).name("").buildLabel();
|
||||
label.addStyleName("rollout-group-unassigned");
|
||||
label.setSizeUndefined();
|
||||
return label;
|
||||
}
|
||||
|
||||
private static Label createGroupTargetsLabel() {
|
||||
final Label label = new LabelBuilder().visible(false).name("").buildLabel();
|
||||
label.addStyleName("rollout-group-count");
|
||||
label.setSizeUndefined();
|
||||
return label;
|
||||
}
|
||||
|
||||
private String getTotalTargetMessage(final long totalTargetsCount) {
|
||||
return i18n.get("label.target.filter.count") + totalTargetsCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display an indication that the legend is being calculated.
|
||||
* When the loading process is done one of the populate methods should be called.
|
||||
*/
|
||||
public void displayLoading() {
|
||||
populateGroupsLegendByTargetCounts(Collections.emptyList());
|
||||
loadingLabel.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the total targets or hides the label when null is supplied.
|
||||
*
|
||||
* @param totalTargets
|
||||
* null to hide the label or a count to be displayed as total
|
||||
* targets message
|
||||
*/
|
||||
public void populateTotalTargets(Long totalTargets) {
|
||||
if (totalTargets == null) {
|
||||
totalTargetsLabel.setVisible(false);
|
||||
} else {
|
||||
totalTargetsLabel.setVisible(true);
|
||||
totalTargetsLabel.setValue(getTotalTargetMessage(totalTargets));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the legend based on a list of anonymous groups. They can't have
|
||||
* unassigned targets.
|
||||
*
|
||||
* @param targetsPerGroup
|
||||
* list of target counts
|
||||
*/
|
||||
public void populateGroupsLegendByTargetCounts(final List<Long> targetsPerGroup) {
|
||||
loadingLabel.setVisible(false);
|
||||
|
||||
for (int i = 0; i < groupsLegend.getComponentCount(); i++) {
|
||||
final Component component = groupsLegend.getComponent(i);
|
||||
final Label label = (Label) component;
|
||||
if (targetsPerGroup.size() > i) {
|
||||
final Long targetCount = targetsPerGroup.get(i);
|
||||
label.setValue(
|
||||
getTargetsInGroupMessage(targetCount, i18n.get("textfield.rollout.group.default.name", i + 1)));
|
||||
label.setVisible(true);
|
||||
} else {
|
||||
label.setValue("");
|
||||
label.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
unassignedTargetsLabel.setValue("");
|
||||
unassignedTargetsLabel.setVisible(false);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the legend based on a groups validation and a list of groups
|
||||
* that is used for resolving their names. Positions of the groups in the
|
||||
* groups list and the validation need to be in correct order. Can have
|
||||
* unassigned targets that are displayed on top of the groups list which
|
||||
* results in one group less to be displayed
|
||||
*
|
||||
* @param validation
|
||||
* A rollout validation object that contains a list of target
|
||||
* counts and the total targets
|
||||
* @param groups
|
||||
* List of groups with their name
|
||||
*/
|
||||
public void populateGroupsLegendByValidation(final RolloutGroupsValidation validation, final List<RolloutGroupCreate> groups) {
|
||||
loadingLabel.setVisible(false);
|
||||
if (validation == null) {
|
||||
return;
|
||||
}
|
||||
List<Long> targetsPerGroup = validation.getTargetsPerGroup();
|
||||
final long unassigned = validation.getTotalTargets() - validation.getTargetsInGroups();
|
||||
final int labelsToUpdate = (unassigned > 0) ? (groupsLegend.getComponentCount() - 1)
|
||||
: groupsLegend.getComponentCount();
|
||||
for (int i = 0; i < groupsLegend.getComponentCount(); i++) {
|
||||
final Component component = groupsLegend.getComponent(i);
|
||||
final Label label = (Label) component;
|
||||
if (targetsPerGroup.size() > i && groups.size() > i && labelsToUpdate > i) {
|
||||
final Long targetCount = targetsPerGroup.get(i);
|
||||
final String groupName = groups.get(i).build().getName();
|
||||
|
||||
label.setValue(getTargetsInGroupMessage(targetCount, groupName));
|
||||
label.setVisible(true);
|
||||
} else {
|
||||
label.setValue("");
|
||||
label.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (unassigned > 0) {
|
||||
unassignedTargetsLabel.setValue(getTargetsInGroupMessage(unassigned, "Unassigned"));
|
||||
unassignedTargetsLabel.setVisible(true);
|
||||
} else {
|
||||
unassignedTargetsLabel.setValue("");
|
||||
unassignedTargetsLabel.setVisible(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the legend based on a list of groups.
|
||||
*
|
||||
* @param groups
|
||||
* List of groups with their name
|
||||
*/
|
||||
public void populateGroupsLegendByGroups(final List<RolloutGroup> groups) {
|
||||
loadingLabel.setVisible(false);
|
||||
|
||||
for (int i = 0; i < groupsLegend.getComponentCount(); i++) {
|
||||
final Component component = groupsLegend.getComponent(i);
|
||||
final Label label = (Label) component;
|
||||
if (groups.size() > i) {
|
||||
final int targetCount = groups.get(i).getTotalTargets();
|
||||
final String groupName = groups.get(i).getName();
|
||||
|
||||
label.setValue(getTargetsInGroupMessage((long) targetCount, groupName));
|
||||
label.setVisible(true);
|
||||
} else {
|
||||
label.setValue("");
|
||||
label.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private String getTargetsInGroupMessage(final Long targets, final String groupName) {
|
||||
return i18n.get("label.rollout.targets.in.group", targets, groupName);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -37,6 +37,7 @@ import java.util.Set;
|
||||
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
@@ -87,6 +88,8 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
|
||||
private static final String UPDATE_OPTION = "Update";
|
||||
|
||||
private static final String COPY_OPTION = "Copy";
|
||||
|
||||
private static final String PAUSE_OPTION = "Pause";
|
||||
|
||||
private static final String RUN_OPTION = "Run";
|
||||
@@ -130,11 +133,12 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
RolloutListGrid(final I18N i18n, final UIEventBus eventBus, final RolloutManagement rolloutManagement,
|
||||
final UINotification uiNotification, final RolloutUIState rolloutUIState,
|
||||
final SpPermissionChecker permissionChecker, final TargetManagement targetManagement,
|
||||
final EntityFactory entityFactory, final UiProperties uiProperties) {
|
||||
final EntityFactory entityFactory, final UiProperties uiProperties,
|
||||
final TargetFilterQueryManagement targetFilterQueryManagement) {
|
||||
super(i18n, eventBus, permissionChecker);
|
||||
this.rolloutManagement = rolloutManagement;
|
||||
this.addUpdateRolloutWindow = new AddUpdateRolloutWindowLayout(rolloutManagement, targetManagement,
|
||||
uiNotification, uiProperties, entityFactory, i18n, eventBus);
|
||||
uiNotification, uiProperties, entityFactory, i18n, eventBus, targetFilterQueryManagement);
|
||||
this.uiNotification = uiNotification;
|
||||
this.rolloutUIState = rolloutUIState;
|
||||
}
|
||||
@@ -230,6 +234,10 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
rolloutGridContainer.addContainerProperty(UPDATE_OPTION, String.class, FontAwesome.EDIT.getHtml(), false,
|
||||
false);
|
||||
}
|
||||
if (permissionChecker.hasRolloutCreatePermission()) {
|
||||
rolloutGridContainer.addContainerProperty(COPY_OPTION, String.class, FontAwesome.COPY.getHtml(), false,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -262,6 +270,10 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
} else {
|
||||
getColumn(PAUSE_OPTION).setMaximumWidth(60);
|
||||
}
|
||||
if (permissionChecker.hasRolloutCreatePermission()) {
|
||||
getColumn(COPY_OPTION).setMinimumWidth(25);
|
||||
getColumn(COPY_OPTION).setMaximumWidth(25);
|
||||
}
|
||||
|
||||
getColumn(VAR_TOTAL_TARGETS_COUNT_STATUS).setMinimumWidth(280);
|
||||
}
|
||||
@@ -289,10 +301,13 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
if (permissionChecker.hasRolloutUpdatePermission()) {
|
||||
getColumn(UPDATE_OPTION).setHeaderCaption(i18n.get("header.action.update"));
|
||||
}
|
||||
if (permissionChecker.hasRolloutCreatePermission()) {
|
||||
getColumn(COPY_OPTION).setHeaderCaption(i18n.get("header.action.copy"));
|
||||
}
|
||||
|
||||
HeaderCell join;
|
||||
if (permissionChecker.hasRolloutUpdatePermission()) {
|
||||
join = getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION, UPDATE_OPTION);
|
||||
join = getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION, UPDATE_OPTION, COPY_OPTION);
|
||||
} else {
|
||||
join = getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION);
|
||||
}
|
||||
@@ -323,6 +338,9 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
if (permissionChecker.hasRolloutUpdatePermission()) {
|
||||
columnList.add(UPDATE_OPTION);
|
||||
}
|
||||
if (permissionChecker.hasRolloutCreatePermission()) {
|
||||
columnList.add(COPY_OPTION);
|
||||
}
|
||||
|
||||
columnList.add(VAR_CREATED_DATE);
|
||||
columnList.add(VAR_CREATED_USER);
|
||||
@@ -377,6 +395,10 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
getColumn(UPDATE_OPTION)
|
||||
.setRenderer(new HtmlButtonRenderer(clickEvent -> updateRollout((Long) clickEvent.getItemId())));
|
||||
}
|
||||
if (permissionChecker.hasRolloutCreatePermission()) {
|
||||
getColumn(COPY_OPTION)
|
||||
.setRenderer(new HtmlButtonRenderer(clickEvent -> copyRollout((Long) clickEvent.getItemId())));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -430,12 +452,19 @@ public class RolloutListGrid extends AbstractGrid {
|
||||
}
|
||||
|
||||
private void updateRollout(final Long rolloutId) {
|
||||
final CommonDialogWindow addTargetWindow = addUpdateRolloutWindow.getWindow(rolloutId);
|
||||
final CommonDialogWindow addTargetWindow = addUpdateRolloutWindow.getWindow(rolloutId, false);
|
||||
addTargetWindow.setCaption(i18n.get("caption.update.rollout"));
|
||||
UI.getCurrent().addWindow(addTargetWindow);
|
||||
addTargetWindow.setVisible(Boolean.TRUE);
|
||||
}
|
||||
|
||||
private void copyRollout(final Long rolloutId) {
|
||||
final CommonDialogWindow addTargetWindow = addUpdateRolloutWindow.getWindow(rolloutId, true);
|
||||
addTargetWindow.setCaption(i18n.get("caption.create.rollout"));
|
||||
UI.getCurrent().addWindow(addTargetWindow);
|
||||
addTargetWindow.setVisible(Boolean.TRUE);
|
||||
}
|
||||
|
||||
private String getDescription(final CellReference cell) {
|
||||
|
||||
String description = null;
|
||||
|
||||
@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.ui.rollout.rollout;
|
||||
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
@@ -43,11 +44,12 @@ public class RolloutListHeader extends AbstractGridHeader {
|
||||
RolloutListHeader(final SpPermissionChecker permissionChecker, final RolloutUIState rolloutUIState,
|
||||
final UIEventBus eventBus, final RolloutManagement rolloutManagement,
|
||||
final TargetManagement targetManagement, final UINotification uiNotification,
|
||||
final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n) {
|
||||
final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n,
|
||||
final TargetFilterQueryManagement targetFilterQueryManagement) {
|
||||
super(permissionChecker, rolloutUIState, i18n);
|
||||
this.eventBus = eventBus;
|
||||
this.addUpdateRolloutWindow = new AddUpdateRolloutWindowLayout(rolloutManagement, targetManagement,
|
||||
uiNotification, uiProperties, entityFactory, i18n, eventBus);
|
||||
uiNotification, uiProperties, entityFactory, i18n, eventBus, targetFilterQueryManagement);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.ui.rollout.rollout;
|
||||
|
||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
@@ -31,11 +32,12 @@ public class RolloutListView extends AbstractGridLayout {
|
||||
public RolloutListView(final SpPermissionChecker permissionChecker, final RolloutUIState rolloutUIState,
|
||||
final UIEventBus eventBus, final RolloutManagement rolloutManagement,
|
||||
final TargetManagement targetManagement, final UINotification uiNotification,
|
||||
final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n) {
|
||||
final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n,
|
||||
final TargetFilterQueryManagement targetFilterQueryManagement) {
|
||||
super(new RolloutListHeader(permissionChecker, rolloutUIState, eventBus, rolloutManagement, targetManagement,
|
||||
uiNotification, uiProperties, entityFactory, i18n),
|
||||
uiNotification, uiProperties, entityFactory, i18n, targetFilterQueryManagement),
|
||||
new RolloutListGrid(i18n, eventBus, rolloutManagement, uiNotification, rolloutUIState,
|
||||
permissionChecker, targetManagement, entityFactory, uiProperties));
|
||||
permissionChecker, targetManagement, entityFactory, uiProperties, targetFilterQueryManagement));
|
||||
|
||||
buildLayout();
|
||||
}
|
||||
|
||||
@@ -212,6 +212,14 @@ public final class SPUIStyleDefinitions {
|
||||
* Query validator icon -error style.
|
||||
*/
|
||||
public static final String ERROR_ICON = "error-icon";
|
||||
/**
|
||||
* Rollout groups
|
||||
*/
|
||||
public static final String ROLLOUT_GROUPS = "rollout-groups";
|
||||
/**
|
||||
* Rollout groups pie chart
|
||||
*/
|
||||
public static final String ROLLOUT_GROUPS_CHART = "groups-pie-chart";
|
||||
/**
|
||||
* Rollout action type layout style.
|
||||
*/
|
||||
|
||||
@@ -174,6 +174,21 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
public static final String ACTION_DETAILS_SOFT_ID = "action.details.soft.group";
|
||||
|
||||
/**
|
||||
* Start type of rollout manual radio button
|
||||
*/
|
||||
public static final String ROLLOUT_START_MANUAL_ID = "rollout.start.manual.radio";
|
||||
|
||||
/**
|
||||
* Start type of rollout auto start radio button
|
||||
*/
|
||||
public static final String ROLLOUT_START_AUTO_ID = "rollout.start.auto.radio";
|
||||
|
||||
/**
|
||||
* Start type of rollout scheduled start radio button
|
||||
*/
|
||||
public static final String ROLLOUT_START_SCHEDULED_ID = "rollout.start.scheduled.start.radio";
|
||||
|
||||
/**
|
||||
* ID - Label.
|
||||
*/
|
||||
@@ -723,6 +738,21 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
public static final String ROLLOUT_ERROR_THRESOLD_ID = "rollout.error.thresold.id";
|
||||
|
||||
/**
|
||||
* Rollout group target percentage id.
|
||||
*/
|
||||
public static final String ROLLOUT_GROUP_TARGET_PERC_ID = "rollout.group.target.percentage.id";
|
||||
|
||||
/**
|
||||
* Rollout group add button id
|
||||
*/
|
||||
public static final String ROLLOUT_GROUP_ADD_ID = "rollout.group.add.id";
|
||||
|
||||
/**
|
||||
* Rollout group remove button id
|
||||
*/
|
||||
public static final String ROLLOUT_GROUP_REMOVE_ID = "rollout.group.remove.id";
|
||||
|
||||
/**
|
||||
* Rollout distribution set combo id.
|
||||
*/
|
||||
@@ -735,7 +765,18 @@ public final class UIComponentIdProvider {
|
||||
* Rollout target filter query combo id.
|
||||
*/
|
||||
public static final String ROLLOUT_TARGET_FILTER_COMBO_ID = "rollout.target.filter.combo.id";
|
||||
|
||||
/**
|
||||
* Rollout groups id
|
||||
*/
|
||||
public static final String ROLLOUT_GROUPS = "rollout.groups";
|
||||
/**
|
||||
* Rollout simple tab id
|
||||
*/
|
||||
public static final String ROLLOUT_SIMPLE_TAB = "rollout.create.tabs.simple";
|
||||
/**
|
||||
* Rollout advanced tab id
|
||||
*/
|
||||
public static final String ROLLOUT_ADVANCED_TAB = "rollout.create.tabs.advanced";
|
||||
/**
|
||||
* Rollout action button id.
|
||||
*/
|
||||
@@ -752,10 +793,15 @@ public final class UIComponentIdProvider {
|
||||
public static final String ROLLOUT_PAUSE_BUTTON_ID = ROLLOUT_ACTION_ID + ".10";
|
||||
|
||||
/**
|
||||
* Rollout resume button id.
|
||||
* Rollout update button id.
|
||||
*/
|
||||
public static final String ROLLOUT_UPDATE_BUTTON_ID = ROLLOUT_ACTION_ID + ".11";
|
||||
|
||||
/**
|
||||
* Rollout copy button id.
|
||||
*/
|
||||
public static final String ROLLOUT_COPY_BUTTON_ID = ROLLOUT_ACTION_ID + ".12";
|
||||
|
||||
/**
|
||||
* Rollout status label id.
|
||||
*/
|
||||
@@ -771,6 +817,16 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
public static final String ROLLOUT_ERROR_THRESOLD_OPTION_ID = "rollout.error.thresold.option.id";
|
||||
|
||||
/**
|
||||
* Rollout groups options id.
|
||||
*/
|
||||
public static final String ROLLOUT_GROUPS_OPTION_ID = "rollout.groups.option.id";
|
||||
|
||||
/**
|
||||
* Rollout groups definition button
|
||||
*/
|
||||
public static final String ROLLOUT_GROUPS_DEF_BUTTON_ID = "rollout.groups.definition.button.id";
|
||||
|
||||
/**
|
||||
* Rollout target filter query value text area id.
|
||||
*/
|
||||
|
||||
@@ -7,95 +7,207 @@
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*/
|
||||
@mixin rollout {
|
||||
.rollout-option-group{
|
||||
font-size:12px;
|
||||
font-weight:400;
|
||||
margin-left:8px;
|
||||
}
|
||||
|
||||
.rollout-action-type-layout {
|
||||
.v-caption-padding-right-style{
|
||||
padding-right:0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.v-context-menu .v-context-menu-item-basic-icon-container{
|
||||
height:0px !important;
|
||||
width:0px !important;
|
||||
}
|
||||
|
||||
.v-context-menu .v-context-menu-item-basic{
|
||||
background-color: #feffff !important;
|
||||
border-radius: 4px;
|
||||
font-family : $app-font-family;
|
||||
font-size : $app-text-font-size;
|
||||
font-weight : normal;
|
||||
font-style : normal;
|
||||
}
|
||||
|
||||
|
||||
.v-context-menu{
|
||||
background-color: #feffff !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.v-context-menu .v-context-menu-item-basic:focus, .v-context-menu .v-context-menu-item-basic-submenu:focus, .v-context-menu .v-context-menu-item-basic-open {
|
||||
@include valo-gradient($color: $hawkbit-primary-color);
|
||||
background-color: $hawkbit-primary-color !important;
|
||||
color: #e8eef3;
|
||||
height: 30px;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.disable-action-type-layout{
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.action-type-padding{
|
||||
padding: 0 0px !important;
|
||||
}
|
||||
|
||||
.rollout-caption-links{
|
||||
font-weight: 400;
|
||||
height: 25px ;
|
||||
padding: 0px 4px ;
|
||||
}
|
||||
|
||||
.rollout-target-count-message{
|
||||
color: $info-message-color-grey;
|
||||
}
|
||||
|
||||
.rollout-table{
|
||||
.v-table-cell-wrapper {
|
||||
cursor: default;
|
||||
.rollout-option-group {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.rollout-action-type-layout {
|
||||
.v-caption-padding-right-style {
|
||||
padding-right: 0px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.v-context-menu .v-context-menu-item-basic-icon-container {
|
||||
height: 0px !important;
|
||||
width: 0px !important;
|
||||
}
|
||||
|
||||
.v-context-menu .v-context-menu-item-basic {
|
||||
background-color: #feffff !important;
|
||||
border-radius: 4px;
|
||||
font-family: $app-font-family;
|
||||
font-size: $app-text-font-size;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.v-context-menu {
|
||||
background-color: #feffff !important;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.v-context-menu .v-context-menu-item-basic:focus, .v-context-menu .v-context-menu-item-basic-submenu:focus, .v-context-menu .v-context-menu-item-basic-open {
|
||||
@include valo-gradient($color: $hawkbit-primary-color);
|
||||
background-color: $hawkbit-primary-color !important;
|
||||
color: #e8eef3;
|
||||
height: 30px;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.disable-action-type-layout {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.action-type-padding {
|
||||
padding: 0 0px !important;
|
||||
}
|
||||
|
||||
.rollout-caption-links {
|
||||
font-weight: 400;
|
||||
height: 25px;
|
||||
padding: 0px 4px;
|
||||
}
|
||||
|
||||
.rollout-target-count-message {
|
||||
color: $info-message-color-grey;
|
||||
}
|
||||
|
||||
.rollout-table {
|
||||
.v-table-cell-wrapper {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.v-grid-cell.centeralign {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.v-grid-cell {
|
||||
font-size: $v-font-size--small !important;
|
||||
height: 34px !important;
|
||||
}
|
||||
|
||||
.v-grid-row {
|
||||
height: 34px !important;
|
||||
}
|
||||
|
||||
.v-grid-cell.frozen {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.v-grid-cell.frozen + th {
|
||||
border-left: $v-grid-border-size solid $widget-border-color;
|
||||
}
|
||||
|
||||
.v-button-boldhide {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.groups-pie-chart {
|
||||
float: right;
|
||||
|
||||
svg {
|
||||
.pie {
|
||||
stroke: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.v-grid-cell.centeralign {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.v-grid-cell {
|
||||
font-size: $v-font-size--small !important;
|
||||
height: 34px !important;
|
||||
}
|
||||
|
||||
.v-grid-row{
|
||||
height: 34px !important;
|
||||
}
|
||||
|
||||
.v-grid-cell.frozen{
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.v-grid-cell.frozen + th{
|
||||
border-left: $v-grid-border-size solid $widget-border-color ;
|
||||
}
|
||||
|
||||
.v-button-boldhide{
|
||||
text-decoration:none;
|
||||
}
|
||||
|
||||
/*
|
||||
First child is intended for unassigned targets and should not be repeated.
|
||||
*/
|
||||
.pie:nth-child(1) { fill: #CBCBCB }
|
||||
|
||||
.pie:nth-child(16n+2) { fill: #E20015 }
|
||||
.pie:nth-child(16n+3) { fill: #B90276 }
|
||||
.pie:nth-child(16n+4) { fill: #50237F }
|
||||
.pie:nth-child(16n+5) { fill: #005691 }
|
||||
.pie:nth-child(16n+6) { fill: #008ECF }
|
||||
.pie:nth-child(16n+7) { fill: #00A8B0 }
|
||||
.pie:nth-child(16n+8) { fill: #78BE20 }
|
||||
.pie:nth-child(16n+9) { fill: #006249 }
|
||||
.pie:nth-child(16n+10) { fill: #F07F8A }
|
||||
.pie:nth-child(16n+11) { fill: #DC80BA }
|
||||
.pie:nth-child(16n+12) { fill: #A791BF }
|
||||
.pie:nth-child(16n+13) { fill: #7FAAC8 }
|
||||
.pie:nth-child(16n+14) { fill: #7FC6E7 }
|
||||
.pie:nth-child(16n+15) { fill: #7FD3D7 }
|
||||
.pie:nth-child(16n+16) { fill: #BBDE8F }
|
||||
.pie:nth-child(16n+17) { fill: #7FB0A4 }
|
||||
|
||||
.pie-info {
|
||||
pointer-events: none;
|
||||
|
||||
rect {
|
||||
fill: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
text {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
fill: $hawkbit-primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.rollout-groups {
|
||||
|
||||
.v-button {
|
||||
margin: 0 3px 3px 3px;
|
||||
}
|
||||
|
||||
.rollout-group-name {
|
||||
border-left: 5px solid #000000;
|
||||
}
|
||||
.v-gridlayout-slot:nth-child(16n+8) .rollout-group-name { border-left-color: #E20015 }
|
||||
.v-gridlayout-slot:nth-child(16n+14) .rollout-group-name { border-left-color: #B90276 }
|
||||
.v-gridlayout-slot:nth-child(16n+20) .rollout-group-name { border-left-color: #50237F }
|
||||
.v-gridlayout-slot:nth-child(16n+26) .rollout-group-name { border-left-color: #005691 }
|
||||
.v-gridlayout-slot:nth-child(16n+32) .rollout-group-name { border-left-color: #008ECF }
|
||||
.v-gridlayout-slot:nth-child(16n+38) .rollout-group-name { border-left-color: #00A8B0 }
|
||||
.v-gridlayout-slot:nth-child(16n+44) .rollout-group-name { border-left-color: #78BE20 }
|
||||
.v-gridlayout-slot:nth-child(16n+50) .rollout-group-name { border-left-color: #006249 }
|
||||
.v-gridlayout-slot:nth-child(16n+56) .rollout-group-name { border-left-color: #F07F8A }
|
||||
.v-gridlayout-slot:nth-child(16n+62) .rollout-group-name { border-left-color: #DC80BA }
|
||||
.v-gridlayout-slot:nth-child(16n+68) .rollout-group-name { border-left-color: #A791BF }
|
||||
.v-gridlayout-slot:nth-child(16n+74) .rollout-group-name { border-left-color: #7FAAC8 }
|
||||
.v-gridlayout-slot:nth-child(16n+80) .rollout-group-name { border-left-color: #7FC6E7 }
|
||||
.v-gridlayout-slot:nth-child(16n+86) .rollout-group-name { border-left-color: #7FD3D7 }
|
||||
.v-gridlayout-slot:nth-child(16n+92) .rollout-group-name { border-left-color: #BBDE8F }
|
||||
.v-gridlayout-slot:nth-child(16n+98) .rollout-group-name { border-left-color: #7FB0A4 }
|
||||
}
|
||||
|
||||
.rollout-target-count-title {
|
||||
}
|
||||
|
||||
.rollout-target-count-loading {
|
||||
color: #ed473b;
|
||||
}
|
||||
|
||||
.rollout-group-unassigned {
|
||||
border-left: 5px solid #CBCBCB;
|
||||
padding-left: 5px;
|
||||
margin-bottom: 2px;
|
||||
font-size: $v-font-size * .7;
|
||||
}
|
||||
|
||||
.groups-legend {
|
||||
.v-slot .rollout-group-count {
|
||||
border-left: 5px solid transparent;
|
||||
padding-left: 5px;
|
||||
font-size: $v-font-size * .7;
|
||||
}
|
||||
|
||||
.v-slot:nth-child(16n+1) .rollout-group-count { border-left-color: #E20015 }
|
||||
.v-slot:nth-child(16n+2) .rollout-group-count { border-left-color: #B90276 }
|
||||
.v-slot:nth-child(16n+3) .rollout-group-count { border-left-color: #50237F }
|
||||
.v-slot:nth-child(16n+4) .rollout-group-count { border-left-color: #005691 }
|
||||
.v-slot:nth-child(16n+5) .rollout-group-count { border-left-color: #008ECF }
|
||||
.v-slot:nth-child(16n+6) .rollout-group-count { border-left-color: #00A8B0 }
|
||||
.v-slot:nth-child(16n+7) .rollout-group-count { border-left-color: #78BE20 }
|
||||
.v-slot:nth-child(16n+8) .rollout-group-count { border-left-color: #006249 }
|
||||
.v-slot:nth-child(16n+9) .rollout-group-count { border-left-color: #F07F8A }
|
||||
.v-slot:nth-child(16n+10) .rollout-group-count { border-left-color: #DC80BA }
|
||||
.v-slot:nth-child(16n+11) .rollout-group-count { border-left-color: #A791BF }
|
||||
.v-slot:nth-child(16n+12) .rollout-group-count { border-left-color: #7FAAC8 }
|
||||
.v-slot:nth-child(16n+13) .rollout-group-count { border-left-color: #7FC6E7 }
|
||||
.v-slot:nth-child(16n+14) .rollout-group-count { border-left-color: #7FD3D7 }
|
||||
.v-slot:nth-child(16n+15) .rollout-group-count { border-left-color: #BBDE8F }
|
||||
.v-slot:nth-child(16n+16) .rollout-group-count { border-left-color: #7FB0A4 }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ header.action=Actions
|
||||
header.action.run=Run
|
||||
header.action.pause=Pause
|
||||
header.action.update=Edit
|
||||
header.action.copy=Copy
|
||||
header.status=Status
|
||||
|
||||
# event container
|
||||
@@ -514,12 +515,17 @@ header.rolloutgroup.target.message = Messages
|
||||
rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1}
|
||||
|
||||
|
||||
prompt.groups = Groups
|
||||
prompt.number.of.groups = Number of groups
|
||||
prompt.tigger.threshold = Trigger threshold
|
||||
prompt.error.threshold = Error threshold
|
||||
prompt.distribution.set = Distribution Set
|
||||
button.rollout.groups.def.button = Define groups
|
||||
button.rollout.add.group = Add Group
|
||||
caption.configure.rollout = Configure Rollout
|
||||
caption.configure.rollout.groups = Configure Deployment Groups
|
||||
caption.update.rollout = Update Rollout
|
||||
caption.create.rollout = Create new Rollout
|
||||
prompt.target.filter = Custom Target Filter
|
||||
message.rollout.nonzero.group.number = Number of groups must be greater than zero
|
||||
message.rollout.max.group.number = Number of groups must not be greater than 500
|
||||
@@ -536,6 +542,26 @@ label.target.per.group = Targets per group :
|
||||
message.dist.already.assigned = Distribution {0} is already assigned to target
|
||||
message.error.creating.rollout = Server error. Error creating Rollout. Please contact the administrator
|
||||
message.error.starting.rollout = Server error. Error starting Rollout. Please contact the administrator
|
||||
caption.rollout.group.definition.desc = Define which groups the Rollout should have.
|
||||
header.target.percentage = Target percentage
|
||||
textfield.target.percentage = Target percentage
|
||||
textfield.rollout.group.default.name = Group {0}
|
||||
message.rollout.group.name.invalid = Enter a group name with a length between 1 and 64
|
||||
caption.rollout.tabs.simple = Number of Groups
|
||||
caption.rollout.tabs.advanced = Advanced Group definition
|
||||
caption.rollout.generate.groups = Generate the groups automatically with the specified thresholds.
|
||||
caption.rollout.action.type = Action type
|
||||
message.rollout.remaining.targets.error = Not all targets are addressed
|
||||
textfield.rollout.copied.name = Copy of {0}
|
||||
label.rollout.targets.in.group = {0} in {1}
|
||||
caption.rollout.start.type = Start type
|
||||
caption.rollout.start.manual = Manual
|
||||
caption.rollout.start.manual.desc = The user starts the rollout manually.
|
||||
caption.rollout.start.auto = Auto
|
||||
caption.rollout.start.auto.desc = The rollout is started as soon as it is created.
|
||||
caption.rollout.start.scheduled = Scheduled
|
||||
caption.rollout.start.scheduled.desc = The rollout starts as soon as it is ready and the set time has passed.
|
||||
label.rollout.calculating = Calculating groups ...
|
||||
#rollout - end
|
||||
|
||||
#Menu
|
||||
|
||||
11
pom.xml
11
pom.xml
@@ -539,6 +539,17 @@
|
||||
<artifactId>jlorem</artifactId>
|
||||
<version>${jlorem.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.gwtd3</groupId>
|
||||
<artifactId>gwt-d3-api</artifactId>
|
||||
<version>1.2.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.gwt</groupId>
|
||||
<artifactId>gwt-user</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!-- Spring -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
|
||||
Reference in New Issue
Block a user