Feature Approval Workflow for rollouts (#678)
* implement feature approval workflow Signed-off-by: Ioannis Spyropoulos <ioannis.spyropoulos@bosch-si.com> Signed-off-by: Michael Müller <Michael.Mueller17@bosch-si.com> * add documentation for REST endpoints Signed-off-by: Ioannis Spyropoulos <ioannis.spyropoulos@bosch-si.com> Signed-off-by: Michael Müller <Michael.Mueller17@bosch-si.com> * fix broken documentation test Signed-off-by: Ioannis Spyropoulos <ioannis.spyropoulos@bosch-si.com> * fix broken documentation test II Signed-off-by: Ioannis Spyropoulos <ioannis.spyropoulos@bosch-si.com>
This commit is contained in:
committed by
Dominic Schabel
parent
8a14c931c8
commit
cef7c2bbf2
@@ -24,6 +24,11 @@ The following capabilities are currently supported by the _Rollout Management_:
|
||||
- Selection of targets as input for the rollout based on _target filter_ functionality.
|
||||
- Selection of a _DistributionSet_.
|
||||
- Auto-splitting of the input target list into a defined number deployment groups.
|
||||
- Approval workflow
|
||||
- Has to be enabled explicitly in configuration.
|
||||
- Enables a workflow that requires a user with adequate permissions to review any new or updated rollout before it
|
||||
can be started.
|
||||
- Allows integration with 3rd party workflow engines.
|
||||
|
||||
- Cascading start of the deployment groups based on installation status of the previous group.
|
||||
- Emergency shutdown of the rollout in case a group exceeds the defined error threshold.
|
||||
|
||||
@@ -340,6 +340,54 @@ public interface RolloutManagement {
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_HANDLE)
|
||||
void resumeRollout(long rolloutId);
|
||||
|
||||
/**
|
||||
* Approves or denies a created rollout being in state
|
||||
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved, it
|
||||
* switches state to {@link RolloutStatus#READY}, otherwise it switches to state
|
||||
* {@link RolloutStatus#APPROVAL_DENIED}
|
||||
*
|
||||
* @param rolloutId
|
||||
* the rollout to be approved or denied.
|
||||
* @param decision
|
||||
* decision whether a rollout is approved or denied.
|
||||
*
|
||||
* @return approved or denied rollout
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if rollout with given ID does not exist
|
||||
* @throws RolloutIllegalStateException
|
||||
* if given rollout is not in
|
||||
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. Only rollouts
|
||||
* waiting for approval can be acted upon.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_APPROVE)
|
||||
Rollout approveOrDeny(long rolloutId, Rollout.ApprovalDecision decision);
|
||||
|
||||
/**
|
||||
* Approves or denies a created rollout being in state
|
||||
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. If the rollout is approved, it
|
||||
* switches state to {@link RolloutStatus#READY}, otherwise it switches to state
|
||||
* {@link RolloutStatus#APPROVAL_DENIED}
|
||||
*
|
||||
* @param rolloutId
|
||||
* the rollout to be approved or denied.
|
||||
* @param decision
|
||||
* decision whether a rollout is approved or denied.
|
||||
* @param remark
|
||||
* user remark on approve / deny decision
|
||||
*
|
||||
* @return approved or denied rollout
|
||||
*
|
||||
* @throws EntityNotFoundException
|
||||
* if rollout with given ID does not exist
|
||||
* @throws RolloutIllegalStateException
|
||||
* if given rollout is not in
|
||||
* {@link RolloutStatus#WAITING_FOR_APPROVAL}. Only rollouts
|
||||
* waiting for approveOrDeny can be acted upon.
|
||||
*/
|
||||
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_APPROVE)
|
||||
Rollout approveOrDeny(long rolloutId, Rollout.ApprovalDecision decision, String remark);
|
||||
|
||||
/**
|
||||
* Starts a rollout which has been created. The rollout must be in
|
||||
* {@link RolloutStatus#READY} state. The Rollout will be set into the
|
||||
|
||||
@@ -25,6 +25,17 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus.Status;
|
||||
*/
|
||||
public interface Rollout extends NamedEntity {
|
||||
|
||||
/**
|
||||
* Maximum length of author name.
|
||||
*/
|
||||
int APPROVED_BY_MAX_SIZE = 40;
|
||||
|
||||
|
||||
/**
|
||||
* Maximum length on comment regarding approval decision.
|
||||
*/
|
||||
int APPROVAL_REMARK_MAX_SIZE = 255;
|
||||
|
||||
/**
|
||||
* @return <code>true</code> if the rollout is deleted and only kept for
|
||||
* history purposes.
|
||||
@@ -81,6 +92,16 @@ public interface Rollout extends NamedEntity {
|
||||
*/
|
||||
TotalTargetCountStatus getTotalTargetCountStatus();
|
||||
|
||||
/**
|
||||
* @return user that approved or denied the {@link Rollout}.
|
||||
*/
|
||||
String getApprovalDecidedBy();
|
||||
|
||||
/**
|
||||
* @return additional note on approval/denial decision.
|
||||
*/
|
||||
String getApprovalRemark();
|
||||
|
||||
/**
|
||||
*
|
||||
* State machine for rollout.
|
||||
@@ -93,6 +114,16 @@ public interface Rollout extends NamedEntity {
|
||||
*/
|
||||
CREATING,
|
||||
|
||||
/**
|
||||
* Rollout needs to be approved.
|
||||
*/
|
||||
WAITING_FOR_APPROVAL,
|
||||
|
||||
/**
|
||||
* Rollout approval is denied. Can not be started.
|
||||
*/
|
||||
APPROVAL_DENIED,
|
||||
|
||||
/**
|
||||
* Rollout is ready to start.
|
||||
*/
|
||||
@@ -154,4 +185,18 @@ public interface Rollout extends NamedEntity {
|
||||
ERROR_STARTING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum that holds all possible approval workflow decisions.
|
||||
*/
|
||||
enum ApprovalDecision {
|
||||
/**
|
||||
* Representing an granted approval for a rollout.
|
||||
*/
|
||||
APPROVED,
|
||||
/**
|
||||
* Representing a rejected rollout.
|
||||
*/
|
||||
DENIED
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -114,6 +114,11 @@ public class TenantConfigurationProperties {
|
||||
*/
|
||||
public static final String ANONYMOUS_DOWNLOAD_MODE_ENABLED = "anonymous.download.enabled";
|
||||
|
||||
/**
|
||||
* Represents setting if approval for a rollout is needed.
|
||||
*/
|
||||
public static final String ROLLOUT_APPROVAL_ENABLED = "rollout.approval.enabled";
|
||||
|
||||
/**
|
||||
* Repository on autoclose mode instead of canceling in case of new DS
|
||||
* assignment over active actions.
|
||||
|
||||
@@ -59,12 +59,14 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
|
||||
|
||||
protected final LockRegistry lockRegistry;
|
||||
|
||||
protected final RolloutApprovalStrategy rolloutApprovalStrategy;
|
||||
|
||||
protected AbstractRolloutManagement(final TargetManagement targetManagement,
|
||||
final DeploymentManagement deploymentManagement, final RolloutGroupManagement rolloutGroupManagement,
|
||||
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
|
||||
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final PlatformTransactionManager txManager, final TenantAware tenantAware,
|
||||
final LockRegistry lockRegistry) {
|
||||
final LockRegistry lockRegistry, final RolloutApprovalStrategy rolloutApprovalStrategy) {
|
||||
this.targetManagement = targetManagement;
|
||||
this.deploymentManagement = deploymentManagement;
|
||||
this.rolloutGroupManagement = rolloutGroupManagement;
|
||||
@@ -75,6 +77,7 @@ public abstract class AbstractRolloutManagement implements RolloutManagement {
|
||||
this.txManager = txManager;
|
||||
this.tenantAware = tenantAware;
|
||||
this.lockRegistry = lockRegistry;
|
||||
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
|
||||
}
|
||||
|
||||
protected Long runInNewTransaction(final String transactionName, final TransactionCallback<Long> action) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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;
|
||||
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
|
||||
/**
|
||||
* This interface provides methods to enable plugging in of different strategies
|
||||
* to handle rollout approval.
|
||||
*/
|
||||
public interface RolloutApprovalStrategy {
|
||||
|
||||
/**
|
||||
* This method handles whether a rollout needs approval. Various factors may be
|
||||
* important according to the implementation, e.g. user roles of the rollout
|
||||
* creator, state of the system, ....
|
||||
*
|
||||
* @param rollout
|
||||
* rollout to decide for whether approval is needed.
|
||||
* @return true if the rollout according to this strategy needs approval.
|
||||
*/
|
||||
boolean isApprovalNeeded(Rollout rollout);
|
||||
|
||||
/**
|
||||
* Depending on the implementation, creation of a approval task,
|
||||
* notification,... inside or outside of hawkbit may be necessary.
|
||||
* Implementations may also decide to provide an empty implementation for this
|
||||
* method.
|
||||
*
|
||||
* @param rollout
|
||||
* rollout to create approval task for.
|
||||
*/
|
||||
void onApprovalRequired(Rollout rollout);
|
||||
|
||||
/**
|
||||
* Returns the user that made a decision to approve or deny the given rollout.
|
||||
* Depending on the implementation this may be different to the current user eg.
|
||||
* when the decision is made in an external system.
|
||||
*
|
||||
* @param rollout
|
||||
* target rollout
|
||||
* @return identifier of the user that decided on approval
|
||||
*/
|
||||
String getApprovalUser(Rollout rollout);
|
||||
}
|
||||
@@ -71,4 +71,10 @@ hawkbit.server.tenant.configuration.anonymous-download-enabled.keyName=anonymous
|
||||
hawkbit.server.tenant.configuration.anonymous-download-enabled.defaultValue=${hawkbit.server.download.anonymous.enabled}
|
||||
hawkbit.server.tenant.configuration.anonymous-download-enabled.dataType=java.lang.Boolean
|
||||
hawkbit.server.tenant.configuration.anonymous-download-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
|
||||
|
||||
hawkbit.server.tenant.configuration.rollout-approval-enabled.keyName=rollout.approval.enabled
|
||||
hawkbit.server.tenant.configuration.rollout-approval-enabled.defaultValue=false
|
||||
hawkbit.server.tenant.configuration.rollout-approval-enabled.dataType=java.lang.Boolean
|
||||
hawkbit.server.tenant.configuration.rollout-approval-enabled.validator=org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationBooleanValidator
|
||||
|
||||
# Default tenant configuration - END
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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.jpa;
|
||||
|
||||
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||
import org.eclipse.hawkbit.im.authentication.UserPrincipal;
|
||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.security.SystemSecurityContext;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link RolloutApprovalStrategy}. Decides whether
|
||||
* approval is needed based on configuration of the tenant as well as the roles
|
||||
* of the user who created the Rollout. Provides a no-operation implementation
|
||||
* of {@link RolloutApprovalStrategy#onApprovalRequired(Rollout)}.
|
||||
*/
|
||||
public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
private final TenantConfigurationManagement tenantConfigurationManagement;
|
||||
|
||||
private final SystemSecurityContext systemSecurityContext;
|
||||
|
||||
DefaultRolloutApprovalStrategy(UserDetailsService userDetailsService,
|
||||
TenantConfigurationManagement tenantConfigurationManagement,
|
||||
SystemSecurityContext systemSecurityContext) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.tenantConfigurationManagement = tenantConfigurationManagement;
|
||||
this.systemSecurityContext = systemSecurityContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true, if rollout approval is enabled and rollout creator doesn't have
|
||||
* approval role.
|
||||
*/
|
||||
@Override
|
||||
public boolean isApprovalNeeded(final Rollout rollout) {
|
||||
final UserDetails userDetails = this.getActor(rollout);
|
||||
final boolean approvalEnabled = this.tenantConfigurationManagement
|
||||
.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue();
|
||||
return approvalEnabled && userDetails.getAuthorities().stream()
|
||||
.noneMatch(authority -> SpPermission.APPROVE_ROLLOUT.equals(authority.getAuthority()));
|
||||
}
|
||||
|
||||
|
||||
private UserDetails getActor(Rollout rollout) {
|
||||
final String actor = rollout.getLastModifiedBy() != null ? rollout.getLastModifiedBy() : rollout.getCreatedBy();
|
||||
return systemSecurityContext.runAsSystem(() -> {
|
||||
UserPrincipal userPrincipal = (UserPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
||||
if(userPrincipal.getUsername().equals(actor)) {
|
||||
return userPrincipal;
|
||||
} else {
|
||||
return this.userDetailsService.loadUserByUsername(actor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/***
|
||||
* Per default do nothing.
|
||||
*
|
||||
* @param rollout
|
||||
* rollout to create approval task for.
|
||||
*/
|
||||
@Override
|
||||
public void onApprovalRequired(Rollout rollout) {
|
||||
// do nothing per default, can be extended by further implementations.
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApprovalUser(Rollout rollout) {
|
||||
return SecurityContextHolder.getContext().getAuthentication().getName();
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import org.eclipse.hawkbit.repository.AbstractRolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.DistributionSetManagement;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.RolloutFields;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutHelper;
|
||||
@@ -157,9 +158,9 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
|
||||
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
|
||||
final Database database) {
|
||||
final Database database, final RolloutApprovalStrategy rolloutApprovalStrategy) {
|
||||
super(targetManagement, deploymentManagement, rolloutGroupManagement, distributionSetManagement, context,
|
||||
eventPublisher, virtualPropertyReplacer, txManager, tenantAware, lockRegistry);
|
||||
eventPublisher, virtualPropertyReplacer, txManager, tenantAware, lockRegistry, rolloutApprovalStrategy);
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
@@ -357,8 +358,14 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
// When all groups are ready the rollout status can be changed to be
|
||||
// ready, too.
|
||||
if (readyGroups == rolloutGroups.size()) {
|
||||
LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
|
||||
if (!rolloutApprovalStrategy.isApprovalNeeded(rollout)) {
|
||||
rollout.setStatus(RolloutStatus.READY);
|
||||
LOGGER.debug("rollout {} creation done. Switch to READY.", rollout.getId());
|
||||
} else {
|
||||
LOGGER.debug("rollout {} creation done. Switch to WAITING_FOR_APPROVAL.", rollout.getId());
|
||||
rollout.setStatus(RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
rolloutApprovalStrategy.onApprovalRequired(rollout);
|
||||
}
|
||||
rollout.setLastCheck(0);
|
||||
rollout.setTotalTargets(totalTargets);
|
||||
rolloutRepository.save(rollout);
|
||||
@@ -451,6 +458,39 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
groups.stream().map(RolloutGroupCreate::build).collect(Collectors.toList()), baseFilter, totalTargets));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision) {
|
||||
return this.approveOrDeny(rolloutId, decision, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public Rollout approveOrDeny(final long rolloutId, final Rollout.ApprovalDecision decision, final String remark) {
|
||||
LOGGER.debug("approveOrDeny rollout called for rollout {} with decision {}", rolloutId, decision);
|
||||
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(rolloutId);
|
||||
RolloutHelper.verifyRolloutInStatus(rollout, RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
switch (decision) {
|
||||
case APPROVED:
|
||||
rollout.setStatus(RolloutStatus.READY);
|
||||
break;
|
||||
case DENIED:
|
||||
rollout.setStatus(RolloutStatus.APPROVAL_DENIED);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown approval decision: " + decision);
|
||||
}
|
||||
rollout.setApprovalDecidedBy(rolloutApprovalStrategy.getApprovalUser(rollout));
|
||||
if (remark != null) {
|
||||
rollout.setApprovalRemark(remark);
|
||||
}
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Retryable(include = {
|
||||
@@ -964,7 +1004,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
final JpaRollout rollout = getRolloutAndThrowExceptionIfNotFound(update.getId());
|
||||
|
||||
checkIfDeleted(update.getId(), rollout.getStatus());
|
||||
|
||||
update.getName().ifPresent(rollout::setName);
|
||||
update.getDescription().ifPresent(rollout::setDescription);
|
||||
update.getActionType().ifPresent(rollout::setActionType);
|
||||
@@ -976,6 +1015,11 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
|
||||
|
||||
rollout.setDistributionSet(set);
|
||||
});
|
||||
if (rolloutApprovalStrategy.isApprovalNeeded(rollout)) {
|
||||
rollout.setStatus(RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
rollout.setApprovalDecidedBy(null);
|
||||
rollout.setApprovalRemark(null);
|
||||
}
|
||||
|
||||
return rolloutRepository.save(rollout);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.eclipse.hawkbit.repository.PropertiesQuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryDefaultConfiguration;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutManagement;
|
||||
import org.eclipse.hawkbit.repository.RolloutStatusCache;
|
||||
@@ -106,6 +107,7 @@ import org.springframework.orm.jpa.vendor.EclipseLinkJpaDialect;
|
||||
import org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter;
|
||||
import org.springframework.retry.annotation.EnableRetry;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
import org.springframework.transaction.jta.JtaTransactionManager;
|
||||
@@ -559,10 +561,25 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
|
||||
final DistributionSetManagement distributionSetManagement, final ApplicationContext context,
|
||||
final ApplicationEventPublisher eventPublisher, final VirtualPropertyReplacer virtualPropertyReplacer,
|
||||
final PlatformTransactionManager txManager, final TenantAware tenantAware, final LockRegistry lockRegistry,
|
||||
final JpaProperties properties) {
|
||||
final JpaProperties properties, final RolloutApprovalStrategy rolloutApprovalStrategy) {
|
||||
return new JpaRolloutManagement(targetManagement, deploymentManagement, rolloutGroupManagement,
|
||||
distributionSetManagement, context, eventPublisher, virtualPropertyReplacer, txManager, tenantAware,
|
||||
lockRegistry, properties.getDatabase());
|
||||
lockRegistry, properties.getDatabase(), rolloutApprovalStrategy);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* {@link DefaultRolloutApprovalStrategy} bean.
|
||||
*
|
||||
* @return a new {@link RolloutApprovalStrategy}
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
RolloutApprovalStrategy rolloutApprovalStrategy(final UserDetailsService userDetailsService,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement,
|
||||
final SystemSecurityContext systemSecurityContext) {
|
||||
return new DefaultRolloutApprovalStrategy(userDetailsService, tenantConfigurationManagement,
|
||||
systemSecurityContext);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -88,7 +88,9 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
||||
@ConversionValue(objectValue = "ERROR_CREATING", dataValue = "7"),
|
||||
@ConversionValue(objectValue = "ERROR_STARTING", dataValue = "8"),
|
||||
@ConversionValue(objectValue = "DELETING", dataValue = "9"),
|
||||
@ConversionValue(objectValue = "DELETED", dataValue = "10") })
|
||||
@ConversionValue(objectValue = "DELETED", dataValue = "10"),
|
||||
@ConversionValue(objectValue = "WAITING_FOR_APPROVAL", dataValue = "11"),
|
||||
@ConversionValue(objectValue = "APPROVAL_DENIED", dataValue = "12")})
|
||||
@Convert("rolloutstatus")
|
||||
@NotNull
|
||||
private RolloutStatus status = RolloutStatus.CREATING;
|
||||
@@ -120,6 +122,14 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
||||
@Column(name = "start_at")
|
||||
private Long startAt;
|
||||
|
||||
@Column(name = "approval_decided_by")
|
||||
@Size(min = 1, max = Rollout.APPROVED_BY_MAX_SIZE)
|
||||
private String approvalDecidedBy;
|
||||
|
||||
@Column(name = "approval_remark")
|
||||
@Size(max = Rollout.APPROVAL_REMARK_MAX_SIZE)
|
||||
private String approvalRemark;
|
||||
|
||||
@Transient
|
||||
private transient TotalTargetCountStatus totalTargetCountStatus;
|
||||
|
||||
@@ -271,4 +281,22 @@ public class JpaRollout extends AbstractJpaNamedEntity implements Rollout, Event
|
||||
this.deleted = deleted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApprovalDecidedBy() {
|
||||
return approvalDecidedBy;
|
||||
}
|
||||
|
||||
public void setApprovalDecidedBy(final String approvalDecidedBy) {
|
||||
this.approvalDecidedBy = approvalDecidedBy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApprovalRemark() {
|
||||
return approvalRemark;
|
||||
}
|
||||
|
||||
public void setApprovalRemark(final String approvalRemark) {
|
||||
this.approvalRemark = approvalRemark;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sp_rollout ADD column approval_decided_by varchar(40);
|
||||
ALTER TABLE sp_rollout ADD column approval_remark varchar(255);
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sp_rollout ADD column approval_decided_by varchar(40);
|
||||
ALTER TABLE sp_rollout ADD column approval_remark varchar(255);
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sp_rollout ADD column approval_decided_by varchar(40);
|
||||
ALTER TABLE sp_rollout ADD column approval_remark varchar(255);
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sp_rollout ADD column approval_decided_by varchar(40);
|
||||
ALTER TABLE sp_rollout ADD column approval_remark varchar(255);
|
||||
@@ -27,6 +27,7 @@ import org.eclipse.hawkbit.repository.model.Target;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTag;
|
||||
import org.eclipse.hawkbit.repository.model.TargetTagAssignmentResult;
|
||||
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
||||
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
@@ -95,6 +96,9 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
|
||||
@Autowired
|
||||
protected TenantConfigurationProperties tenantConfigurationProperties;
|
||||
|
||||
@Autowired
|
||||
protected RolloutTestApprovalStrategy approvalStrategy;
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
protected List<Action> findActionsByRolloutAndStatus(final Rollout rollout, final Action.Status actionStatus) {
|
||||
return Lists.newArrayList(actionRepository.findByRolloutIdAndStatus(PAGE, rollout.getId(), actionStatus));
|
||||
|
||||
@@ -68,12 +68,17 @@ import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
import ru.yandex.qatools.allure.annotations.Description;
|
||||
import ru.yandex.qatools.allure.annotations.Features;
|
||||
import ru.yandex.qatools.allure.annotations.Step;
|
||||
@@ -87,6 +92,12 @@ import ru.yandex.qatools.allure.annotations.Title;
|
||||
@Stories("Rollout Management")
|
||||
public class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
@Before
|
||||
@After
|
||||
public void reset() {
|
||||
this.approvalStrategy.setApprovalNeeded(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Verifies that a running action with distribution-set (A) is not canceled by a rollout which tries to also assign a distribution-set (A)")
|
||||
public void rolloutShouldNotCancelRunningActionWithTheSameDistributionSet() {
|
||||
@@ -1539,6 +1550,59 @@ public class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Creating a rollout with approval role or approval engine disabled results in the rollout being in " +
|
||||
"READY state.")
|
||||
public void createdRolloutWithApprovalRoleOrApprovalDisabledTransitionsToReadyState() {
|
||||
approvalStrategy.setApprovalNeeded(false);
|
||||
final String successCondition = "50";
|
||||
final String errorCondition = "80";
|
||||
final Rollout rollout = createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10,
|
||||
5, successCondition, errorCondition);
|
||||
assertThat(rollout.getStatus()).isEqualTo(Rollout.RolloutStatus.READY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Creating a rollout without approver role and approval enabled leads to transition to " +
|
||||
"WAITING_FOR_APPROVAL state.")
|
||||
public void createdRolloutWithoutApprovalRoleTransitionsToWaitingForApprovalState() {
|
||||
approvalStrategy.setApprovalNeeded(true);
|
||||
final String successCondition = "50";
|
||||
final String errorCondition = "80";
|
||||
final Rollout rollout = createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10,
|
||||
5, successCondition, errorCondition);
|
||||
assertThat(rollout.getStatus()).isEqualTo(Rollout.RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
@Description("Approving a rollout leads to transition to READY state.")
|
||||
public void approvedRolloutTransitionsToReadyState() {
|
||||
approvalStrategy.setApprovalNeeded(true);
|
||||
final String successCondition = "50";
|
||||
final String errorCondition = "80";
|
||||
final Rollout rollout = createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10,
|
||||
5, successCondition, errorCondition);
|
||||
assertThat(rollout.getStatus()).isEqualTo(Rollout.RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
rolloutManagement.approveOrDeny(rollout.getId(), Rollout.ApprovalDecision.APPROVED);
|
||||
final Rollout resultingRollout = rolloutRepository.findOne(rollout.getId());
|
||||
assertThat(resultingRollout.getStatus()).isEqualTo(Rollout.RolloutStatus.READY);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Denying approval for a rollout leads to transition to APPROVAL_DENIED state.")
|
||||
public void deniedRolloutTransitionsToApprovalDeniedState() {
|
||||
approvalStrategy.setApprovalNeeded(true);
|
||||
final String successCondition = "50";
|
||||
final String errorCondition = "80";
|
||||
final Rollout rollout = createSimpleTestRolloutWithTargetsAndDistributionSet(10, 10,
|
||||
5, successCondition, errorCondition);
|
||||
assertThat(rollout.getStatus()).isEqualTo(Rollout.RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
rolloutManagement.approveOrDeny(rollout.getId(), Rollout.ApprovalDecision.DENIED);
|
||||
final Rollout resultingRollout = rolloutRepository.findOne(rollout.getId());
|
||||
assertThat(resultingRollout.getStatus()).isEqualTo(RolloutStatus.APPROVAL_DENIED);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ExpectEvents({ @Expect(type = RolloutDeletedEvent.class, count = 1),
|
||||
@Expect(type = DistributionSetCreatedEvent.class, count = 1),
|
||||
|
||||
@@ -23,11 +23,13 @@ import org.eclipse.hawkbit.cache.DefaultDownloadIdCache;
|
||||
import org.eclipse.hawkbit.cache.DownloadIdCache;
|
||||
import org.eclipse.hawkbit.cache.TenantAwareCacheManager;
|
||||
import org.eclipse.hawkbit.event.BusProtoStuffMessageConverter;
|
||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.RolloutStatusCache;
|
||||
import org.eclipse.hawkbit.repository.event.ApplicationEventFilter;
|
||||
import org.eclipse.hawkbit.repository.model.helper.EventPublisherHolder;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
||||
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.test.util.TestdataFactory;
|
||||
import org.eclipse.hawkbit.security.DdiSecurityProperties;
|
||||
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
|
||||
@@ -128,7 +130,6 @@ public class TestConfiguration implements AsyncConfigurer {
|
||||
TenantAwareCacheManager cacheManager() {
|
||||
return new TenantAwareCacheManager(new GuavaCacheManager(), tenantAware());
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean for the download id cache.
|
||||
*
|
||||
@@ -214,6 +215,11 @@ public class TestConfiguration implements AsyncConfigurer {
|
||||
return serviceMatcher;
|
||||
}
|
||||
|
||||
@Bean
|
||||
RolloutApprovalStrategy rolloutApprovalStrategy() {
|
||||
return new RolloutTestApprovalStrategy();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return the protostuff io message converter
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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.test.util;
|
||||
|
||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
|
||||
/**
|
||||
* Provides an approval strategy that can be manipulated by setting the {link
|
||||
* {@link #approvalNeeded}} flag used for testing.
|
||||
*/
|
||||
public class RolloutTestApprovalStrategy implements RolloutApprovalStrategy {
|
||||
|
||||
private boolean approvalNeeded = false;
|
||||
|
||||
@Override
|
||||
public boolean isApprovalNeeded(Rollout rollout) {
|
||||
return approvalNeeded;
|
||||
}
|
||||
|
||||
public void setApprovalNeeded(boolean approvalNeeded) {
|
||||
this.approvalNeeded = approvalNeeded;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApprovalRequired(Rollout rollout) {
|
||||
// do nothing, as no action is needed when testing
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getApprovalUser(Rollout rollout) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -81,6 +81,36 @@ public interface MgmtRolloutRestApi {
|
||||
MediaType.APPLICATION_JSON_VALUE })
|
||||
ResponseEntity<MgmtRolloutResponseBody> create(MgmtRolloutRestRequestBody rolloutRequestBody);
|
||||
|
||||
/**
|
||||
* Handles the request for approving a rollout.
|
||||
*
|
||||
* @param rolloutId
|
||||
* the ID of the rollout to be approved.
|
||||
* @param remark
|
||||
* an optional remark on the approval decision
|
||||
* @return OK response (200) if rollout is approved now. In case of any
|
||||
* exception the corresponding errors occur.
|
||||
*/
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/approve", produces = { MediaTypes.HAL_JSON_VALUE,
|
||||
MediaType.APPLICATION_JSON_VALUE })
|
||||
ResponseEntity<Void> approve(@PathVariable("rolloutId") Long rolloutId,
|
||||
@RequestParam(value = "remark", required = false) String remark);
|
||||
|
||||
/**
|
||||
* Handles the request for denying the approval of a rollout.
|
||||
*
|
||||
* @param rolloutId
|
||||
* the ID of the rollout to be denied.
|
||||
* @param remark
|
||||
* an optional remark on the denial decision
|
||||
* @return OK response (200) if rollout is denied now. In case of any
|
||||
* exception the corresponding errors occur.
|
||||
*/
|
||||
@RequestMapping(method = RequestMethod.POST, value = "/{rolloutId}/deny", produces = { MediaTypes.HAL_JSON_VALUE,
|
||||
MediaType.APPLICATION_JSON_VALUE })
|
||||
ResponseEntity<Void> deny(@PathVariable("rolloutId") Long rolloutId,
|
||||
@RequestParam(value = "remark", required = false) String remark);
|
||||
|
||||
/**
|
||||
* Handles the POST request for starting a rollout.
|
||||
*
|
||||
|
||||
@@ -88,6 +88,8 @@ final class MgmtRolloutMapper {
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).start(rollout.getId())).withRel("start"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).pause(rollout.getId())).withRel("pause"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).resume(rollout.getId())).withRel("resume"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).approve(rollout.getId(), null)).withRel("approve"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).deny(rollout.getId(), null)).withRel("deny"));
|
||||
body.add(linkTo(methodOn(MgmtRolloutRestApi.class).getRolloutGroups(rollout.getId(),
|
||||
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_OFFSET_VALUE,
|
||||
MgmtRestConstants.REQUEST_PARAMETER_PAGING_DEFAULT_LIMIT_VALUE, null, null)).withRel("groups"));
|
||||
|
||||
@@ -129,6 +129,18 @@ public class MgmtRolloutResource implements MgmtRolloutRestApi {
|
||||
return ResponseEntity.status(HttpStatus.CREATED).body(MgmtRolloutMapper.toResponseRollout(rollout, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> approve(@PathVariable("rolloutId") final Long rolloutId, final String remark) {
|
||||
rolloutManagement.approveOrDeny(rolloutId, Rollout.ApprovalDecision.APPROVED, remark);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> deny(@PathVariable("rolloutId") final Long rolloutId, final String remark) {
|
||||
rolloutManagement.approveOrDeny(rolloutId, Rollout.ApprovalDecision.DENIED, remark);
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResponseEntity<Void> start(@PathVariable("rolloutId") final Long rolloutId) {
|
||||
this.rolloutManagement.start(rolloutId);
|
||||
|
||||
@@ -179,6 +179,81 @@ include::../errors/415.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== POST /rest/v1/rollouts/{rolloutId}/approve
|
||||
|
||||
=== Implementation Notes
|
||||
Handles the POST request of approving a created rollout within SP.
|
||||
Only possible if approval workflow is enabled in system configuration and rollout is in state WAITING_FOR_APPROVAL.
|
||||
Required Permission: APPROVE_ROLLOUT
|
||||
|
||||
=== Approve Rollout
|
||||
|
||||
==== CURL
|
||||
|
||||
include::{snippets}/rollouts/approve-rollout/curl-request.adoc[]
|
||||
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/rollouts/approve-rollout/http-request.adoc[]
|
||||
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
==== Response example
|
||||
|
||||
include::{snippets}/rollouts/approve-rollout/http-response.adoc[]
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== POST /rest/v1/rollouts/{rolloutId}/deny
|
||||
|
||||
=== Implementation Notes
|
||||
Handles the POST request of denying a created rollout within SP.
|
||||
Only possible if approval workflow is enabled in system configuration and rollout is in state WAITING_FOR_APPROVAL.
|
||||
Required Permission: APPROVE_ROLLOUT
|
||||
|
||||
=== Deny Rollout
|
||||
|
||||
|
||||
==== CURL
|
||||
|
||||
include::{snippets}/rollouts/deny-rollout/curl-request.adoc[]
|
||||
|
||||
|
||||
==== Request URL
|
||||
|
||||
include::{snippets}/rollouts/deny-rollout/http-request.adoc[]
|
||||
|
||||
|
||||
=== Response (Status 200)
|
||||
|
||||
==== Response example
|
||||
|
||||
include::{snippets}/rollouts/deny-rollout/http-response.adoc[]
|
||||
|
||||
=== Error responses
|
||||
|
||||
|===
|
||||
| HTTP Status Code | Reason | Response Model
|
||||
include::../errors/400.adoc[]
|
||||
include::../errors/401.adoc[]
|
||||
include::../errors/403.adoc[]
|
||||
include::../errors/405.adoc[]
|
||||
include::../errors/406.adoc[]
|
||||
include::../errors/429.adoc[]
|
||||
|===
|
||||
|
||||
== POST /rest/v1/rollouts/{rolloutId}/start
|
||||
|
||||
=== Implementation Notes
|
||||
|
||||
@@ -103,6 +103,8 @@ public final class MgmtApiModelProperties {
|
||||
public static final String ROLLOUT_LINKS_START_ASYNC = "Link to start the rollout in async mode";
|
||||
public static final String ROLLOUT_LINKS_PAUSE = "Link to pause a running rollout";
|
||||
public static final String ROLLOUT_LINKS_RESUME = "Link to resume a paused rollout";
|
||||
public static final String ROLLOUT_LINKS_APPROVE = "Link to approve a rollout";
|
||||
public static final String ROLLOUT_LINKS_DENY = "Link to deny a rollout";
|
||||
public static final String ROLLOUT_LINKS_GROUPS = "Link to retrieve the groups a rollout";
|
||||
public static final String ROLLOUT_START_ASYNC = "Start the rollout asynchronous";
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ 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.test.util.RolloutTestApprovalStrategy;
|
||||
import org.eclipse.hawkbit.rest.documentation.AbstractApiRestDocumentation;
|
||||
import org.eclipse.hawkbit.rest.documentation.ApiModelPropertiesGeneric;
|
||||
import org.eclipse.hawkbit.rest.documentation.DocumenationResponseFieldsSnippet;
|
||||
@@ -41,6 +42,7 @@ import org.eclipse.hawkbit.rest.util.JsonBuilder;
|
||||
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
@@ -62,12 +64,16 @@ import ru.yandex.qatools.allure.annotations.Stories;
|
||||
@Stories("Rollout Resource")
|
||||
public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentation {
|
||||
|
||||
@Autowired
|
||||
private RolloutTestApprovalStrategy approvalStrategy;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.resourceName = "rollouts";
|
||||
super.setUp();
|
||||
arrayPrefix = "content[].";
|
||||
approvalStrategy.setApprovalNeeded(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -127,7 +133,6 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
allFieldDescriptor.add(
|
||||
fieldWithPath(arrayPrefix + "totalTargets").description(MgmtApiModelProperties.ROLLOUT_TOTAL_TARGETS));
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.self").ignored());
|
||||
|
||||
if (withDetails) {
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "totalTargetsPerStatus")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_TOTAL_TARGETS_PER_STATUS));
|
||||
@@ -139,6 +144,10 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
.description(MgmtApiModelProperties.ROLLOUT_LINKS_RESUME));
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.groups")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_LINKS_GROUPS));
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.approve")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_LINKS_APPROVE));
|
||||
allFieldDescriptor.add(fieldWithPath(arrayPrefix + "_links.deny")
|
||||
.description(MgmtApiModelProperties.ROLLOUT_LINKS_DENY));
|
||||
}
|
||||
|
||||
return new DocumenationResponseFieldsSnippet(allFieldDescriptor);
|
||||
@@ -386,6 +395,29 @@ public class RolloutResourceDocumentationTest extends AbstractApiRestDocumentati
|
||||
pathParameters(parameterWithName("rolloutId").description(ApiModelPropertiesGeneric.ITEM_ID))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the POST request of approving a rollout. Required Permission: "
|
||||
+ SpPermission.APPROVE_ROLLOUT)
|
||||
public void approveRollout() throws Exception {
|
||||
approvalStrategy.setApprovalNeeded(true);
|
||||
final Rollout rollout = createRolloutEntity();
|
||||
mockMvc.perform(post(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}/approve", rollout.getId())
|
||||
.accept(MediaTypes.HAL_JSON_VALUE)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("rolloutId").description(ApiModelPropertiesGeneric.ITEM_ID))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the POST request of denying a rollout. Required Permission: " + SpPermission.APPROVE_ROLLOUT)
|
||||
public void denyRollout() throws Exception {
|
||||
approvalStrategy.setApprovalNeeded(true);
|
||||
final Rollout rollout = createRolloutEntity();
|
||||
mockMvc.perform(post(MgmtRestConstants.ROLLOUT_V1_REQUEST_MAPPING + "/{rolloutId}/deny", rollout.getId())
|
||||
.accept(MediaTypes.HAL_JSON_VALUE)).andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
|
||||
.andDo(this.document.document(
|
||||
pathParameters(parameterWithName("rolloutId").description(ApiModelPropertiesGeneric.ITEM_ID))));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Description("Handles the GET request of retrieving the deploy groups of a rollout. Required Permission: "
|
||||
+ SpPermission.READ_ROLLOUT)
|
||||
|
||||
@@ -80,6 +80,8 @@ public class TenantResourceDocumentationTest extends AbstractApiRestDocumentatio
|
||||
"if the anonymous download mode is enabled.");
|
||||
CONFIG_ITEM_DESCRIPTIONS.put(TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED,
|
||||
"if autoclose running actions with new Distribution Set assignment is enabled.");
|
||||
CONFIG_ITEM_DESCRIPTIONS.put(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED,
|
||||
"if approval mode for Rollout Management is enabled.");
|
||||
}
|
||||
|
||||
@Autowired
|
||||
|
||||
@@ -42,6 +42,7 @@ spring.http.multipart.max-request-size=-1
|
||||
# UI help links
|
||||
hawkbit.server.ui.links.documentation.root=https://www.eclipse.org/hawkbit/documentation/overview/introduction.html
|
||||
hawkbit.server.ui.links.documentation.security=https://www.eclipse.org/hawkbit/documentation/security/security.html
|
||||
hawkbit.server.ui.links.documentation.rollout=https://www.eclipse.org/hawkbit/documentation/architecture/rollout-management.html
|
||||
|
||||
hawkbit.server.ui.links.documentation.deployment-view=https://www.eclipse.org/hawkbit/documentation/interfaces/management-ui.html
|
||||
hawkbit.server.ui.links.documentation.distribution-view=https://www.eclipse.org/hawkbit/documentation/interfaces/management-ui.html
|
||||
|
||||
@@ -145,6 +145,11 @@ public final class SpPermission {
|
||||
*/
|
||||
public static final String HANDLE_ROLLOUT = "HANDLE_ROLLOUT";
|
||||
|
||||
/**
|
||||
* Permission to approve or deny a rollout prior to starting.
|
||||
*/
|
||||
public static final String APPROVE_ROLLOUT = "APPROVE_ROLLOUT";
|
||||
|
||||
private SpPermission() {
|
||||
// Constants only
|
||||
}
|
||||
@@ -389,6 +394,14 @@ public final class SpPermission {
|
||||
public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_HANDLE = HAS_AUTH_PREFIX + HANDLE_ROLLOUT
|
||||
+ HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE;
|
||||
|
||||
/**
|
||||
* Spring security eval hasAuthority expression to check if spring
|
||||
* context contains {@link SpPermission#APPROVE_ROLLOUT} or
|
||||
* {@link #IS_SYSTEM_CODE}.
|
||||
*/
|
||||
public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_APPROVE = HAS_AUTH_PREFIX + APPROVE_ROLLOUT
|
||||
+ HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE;
|
||||
|
||||
/**
|
||||
* Spring security eval hasAuthority expression to check if spring
|
||||
* context contains {@link SpPermission#UPDATE_ROLLOUT} or
|
||||
|
||||
@@ -31,7 +31,7 @@ public final class PermissionTest {
|
||||
@Test
|
||||
@Description("Verify the get permission function")
|
||||
public void testGetPermissions() {
|
||||
final int allPermission = 17;
|
||||
final int allPermission = 18;
|
||||
final Collection<String> allAuthorities = SpPermission.getAllAuthorities();
|
||||
final List<GrantedAuthority> allAuthoritiesList = PermissionUtils.createAllAuthorityList();
|
||||
assertThat(allAuthorities).hasSize(allPermission);
|
||||
|
||||
@@ -151,4 +151,12 @@ public class SpPermissionChecker implements Serializable {
|
||||
public boolean hasRolloutTargetsReadPermission() {
|
||||
return hasTargetReadPermission() && permissionService.hasPermission(SpPermission.READ_ROLLOUT);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return <code>true</code> if rollout can be approved by the user.
|
||||
*/
|
||||
public boolean hasRolloutApprovalPermission() {
|
||||
return hasRolloutReadPermission() && permissionService.hasPermission(SpPermission.APPROVE_ROLLOUT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,6 +137,11 @@ public class UiProperties implements Serializable {
|
||||
*/
|
||||
private String security = "";
|
||||
|
||||
/**
|
||||
* Link to rollout related documentation.
|
||||
*/
|
||||
private String rollout = "";
|
||||
|
||||
/**
|
||||
* Link to target filter view.
|
||||
*/
|
||||
@@ -167,6 +172,10 @@ public class UiProperties implements Serializable {
|
||||
return security;
|
||||
}
|
||||
|
||||
public String getRollout() {
|
||||
return rollout;
|
||||
}
|
||||
|
||||
public String getSystemConfigurationView() {
|
||||
return systemConfigurationView;
|
||||
}
|
||||
@@ -203,6 +212,10 @@ public class UiProperties implements Serializable {
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public void setRollout(final String rollout) {
|
||||
this.rollout = rollout;
|
||||
}
|
||||
|
||||
public void setSystemConfigurationView(final String systemConfigurationView) {
|
||||
this.systemConfigurationView = systemConfigurationView;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ 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.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.ui.AbstractHawkbitUI;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
@@ -73,12 +74,13 @@ public class RolloutView extends VerticalLayout implements View {
|
||||
final RolloutGroupManagement rolloutGroupManagement, final TargetManagement targetManagement,
|
||||
final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory,
|
||||
final VaadinMessageSource i18n, final TargetFilterQueryManagement targetFilterQueryManagement,
|
||||
final QuotaManagement quotaManagement) {
|
||||
final QuotaManagement quotaManagement,
|
||||
final TenantConfigurationManagement tenantConfigManagement) {
|
||||
this.permChecker = permissionChecker;
|
||||
this.rolloutManagement = rolloutManagement;
|
||||
this.rolloutListView = new RolloutListView(permissionChecker, rolloutUIState, eventBus, rolloutManagement,
|
||||
targetManagement, uiNotification, uiProperties, entityFactory, i18n, targetFilterQueryManagement,
|
||||
rolloutGroupManagement, quotaManagement);
|
||||
rolloutGroupManagement, quotaManagement, tenantConfigManagement);
|
||||
this.rolloutGroupsListView = new RolloutGroupsListView(i18n, eventBus, rolloutGroupManagement, rolloutUIState,
|
||||
permissionChecker);
|
||||
this.rolloutGroupTargetsListView = new RolloutGroupTargetsListView(eventBus, i18n, rolloutUIState);
|
||||
|
||||
@@ -78,9 +78,11 @@ import com.vaadin.data.util.converter.StringToIntegerConverter;
|
||||
import com.vaadin.data.validator.IntegerRangeValidator;
|
||||
import com.vaadin.data.validator.LongRangeValidator;
|
||||
import com.vaadin.data.validator.NullValidator;
|
||||
import com.vaadin.server.FontAwesome;
|
||||
import com.vaadin.shared.ui.label.ContentMode;
|
||||
import com.vaadin.ui.ComboBox;
|
||||
import com.vaadin.ui.GridLayout;
|
||||
import com.vaadin.ui.HorizontalLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.OptionGroup;
|
||||
import com.vaadin.ui.TabSheet;
|
||||
@@ -106,6 +108,10 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
private static final String MESSAGE_ENTER_NUMBER = "message.enter.number";
|
||||
|
||||
private static final String APPROVAL_BUTTON_LABEL = "button.approve";
|
||||
|
||||
private static final String DENY_BUTTON_LABEL = "button.deny";
|
||||
|
||||
private final ActionTypeOptionGroupLayout actionTypeOptionGroupLayout;
|
||||
|
||||
private final AutoStartOptionGroupLayout autoStartOptionGroupLayout;
|
||||
@@ -150,6 +156,10 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
private OptionGroup errorThresholdOptionGroup;
|
||||
|
||||
private Label approvalLabel;
|
||||
|
||||
private HorizontalLayout approvalButtonsLayout;
|
||||
|
||||
private CommonDialogWindow window;
|
||||
|
||||
private boolean editRolloutEnabled;
|
||||
@@ -166,6 +176,10 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
private GroupsLegendLayout groupsLegendLayout;
|
||||
|
||||
private OptionGroup approveButtonsGroup;
|
||||
|
||||
private TextField approvalRemarkField;
|
||||
|
||||
private final transient RolloutGroupConditions defaultRolloutGroupConditions;
|
||||
|
||||
private final NullValidator nullValidator = new NullValidator(null, false);
|
||||
@@ -213,6 +227,11 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
if (editRolloutEnabled) {
|
||||
editRollout();
|
||||
if (rollout.getStatus().equals(Rollout.RolloutStatus.WAITING_FOR_APPROVAL)) {
|
||||
rolloutManagement.approveOrDeny(rollout.getId(),
|
||||
(Rollout.ApprovalDecision) approveButtonsGroup.getValue(), approvalRemarkField.getValue());
|
||||
eventBus.publish(this, RolloutEvent.UPDATE_ROLLOUT);
|
||||
}
|
||||
return;
|
||||
}
|
||||
createRollout();
|
||||
@@ -413,6 +432,12 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
rollout = null;
|
||||
groupsDefinitionTabs.setVisible(true);
|
||||
groupsDefinitionTabs.setSelectedTab(0);
|
||||
|
||||
approvalLabel.setVisible(false);
|
||||
approvalButtonsLayout.setVisible(false);
|
||||
approveButtonsGroup.clear();
|
||||
approvalRemarkField.clear();
|
||||
approveButtonsGroup.removeAllValidators();
|
||||
}
|
||||
|
||||
private void addGroupsDefinitionTabs() {
|
||||
@@ -443,7 +468,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
setSpacing(true);
|
||||
setSizeUndefined();
|
||||
setRows(7);
|
||||
setRows(8);
|
||||
setColumns(4);
|
||||
setStyleName("marginTop");
|
||||
setColumnExpandRatio(3, 1);
|
||||
@@ -477,6 +502,9 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
addComponent(groupsDefinitionTabs, 0, 6, 3, 6);
|
||||
|
||||
addComponent(approvalLabel, 0, 7);
|
||||
addComponent(approvalButtonsLayout, 1, 7, 3, 7);
|
||||
|
||||
rolloutName.focus();
|
||||
}
|
||||
|
||||
@@ -541,6 +569,8 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
|
||||
groupsLegendLayout = new GroupsLegendLayout(i18n);
|
||||
|
||||
approvalLabel = getLabel("label.approval.decision");
|
||||
approvalButtonsLayout = createApprovalLayout();
|
||||
}
|
||||
|
||||
private void displayValidationStatus(final DefineGroupsLayout.ValidationStatus status) {
|
||||
@@ -610,6 +640,29 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
return layout;
|
||||
}
|
||||
|
||||
private HorizontalLayout createApprovalLayout() {
|
||||
approveButtonsGroup = new OptionGroup();
|
||||
approveButtonsGroup.setId(UIComponentIdProvider.ROLLOUT_APPROVAL_OPTIONGROUP_ID);
|
||||
approveButtonsGroup.addStyleName(ValoTheme.OPTIONGROUP_SMALL);
|
||||
approveButtonsGroup.addStyleName(ValoTheme.OPTIONGROUP_HORIZONTAL);
|
||||
approveButtonsGroup.addStyleName("custom-option-group");
|
||||
approveButtonsGroup.addItems(Rollout.ApprovalDecision.APPROVED, Rollout.ApprovalDecision.DENIED);
|
||||
|
||||
approveButtonsGroup.setItemCaption(Rollout.ApprovalDecision.APPROVED, i18n.getMessage(APPROVAL_BUTTON_LABEL));
|
||||
approveButtonsGroup.setItemIcon(Rollout.ApprovalDecision.APPROVED, FontAwesome.CHECK);
|
||||
approveButtonsGroup.setItemCaption(Rollout.ApprovalDecision.DENIED, i18n.getMessage(DENY_BUTTON_LABEL));
|
||||
approveButtonsGroup.setItemIcon(Rollout.ApprovalDecision.DENIED, FontAwesome.TIMES);
|
||||
|
||||
approvalRemarkField = createTextField("label.approval.remark",
|
||||
UIComponentIdProvider.ROLLOUT_APPROVAL_REMARK_FIELD_ID, Rollout.APPROVAL_REMARK_MAX_SIZE);
|
||||
approvalRemarkField.setWidth(100.0F, Unit.PERCENTAGE);
|
||||
|
||||
HorizontalLayout layout = new HorizontalLayout(approveButtonsGroup, approvalRemarkField);
|
||||
layout.setWidth(100.0F, Unit.PERCENTAGE);
|
||||
layout.setExpandRatio(approvalRemarkField, 1.0F);
|
||||
return layout;
|
||||
}
|
||||
|
||||
private static Label createCountLabel() {
|
||||
final Label groupSize = new LabelBuilder().visible(false).name("").buildLabel();
|
||||
groupSize.addStyleName(ValoTheme.LABEL_TINY + " " + "rollout-target-count-message");
|
||||
@@ -966,6 +1019,13 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
|
||||
if (rollout.getStatus() != Rollout.RolloutStatus.READY) {
|
||||
disableRequiredFieldsOnEdit();
|
||||
}
|
||||
|
||||
if (rollout.getStatus() == Rollout.RolloutStatus.WAITING_FOR_APPROVAL) {
|
||||
approvalButtonsLayout.setVisible(true);
|
||||
approveButtonsGroup.addValidator(nullValidator);
|
||||
approvalLabel.setVisible(true);
|
||||
}
|
||||
|
||||
rolloutName.setValue(rollout.getName());
|
||||
groupsDefinitionTabs.setVisible(false);
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@ public class ProxyRollout {
|
||||
private long forcedTime;
|
||||
private RolloutStatus status;
|
||||
private TotalTargetCountStatus totalTargetCountStatus;
|
||||
private String approvalDecidedBy;
|
||||
private String approvalRemark;
|
||||
|
||||
public RolloutRendererData getRolloutRendererData() {
|
||||
return rolloutRendererData;
|
||||
@@ -217,4 +219,20 @@ public class ProxyRollout {
|
||||
public void setTotalTargetCountStatus(final TotalTargetCountStatus totalTargetCountStatus) {
|
||||
this.totalTargetCountStatus = totalTargetCountStatus;
|
||||
}
|
||||
|
||||
public String getApprovalDecidedBy() {
|
||||
return approvalDecidedBy;
|
||||
}
|
||||
|
||||
public void setApprovalDecidedBy(final String approvalDecidedBy) {
|
||||
this.approvalDecidedBy = approvalDecidedBy;
|
||||
}
|
||||
|
||||
public String getApprovalRemark() {
|
||||
return approvalRemark;
|
||||
}
|
||||
|
||||
public void setApprovalRemark(final String approvalRemark) {
|
||||
this.approvalRemark = approvalRemark;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,6 +124,8 @@ public class RolloutBeanQuery extends AbstractBeanQuery<ProxyRollout> {
|
||||
final TotalTargetCountStatus totalTargetCountActionStatus = rollout.getTotalTargetCountStatus();
|
||||
proxyRollout.setTotalTargetCountStatus(totalTargetCountActionStatus);
|
||||
proxyRollout.setTotalTargetsCount(String.valueOf(rollout.getTotalTargets()));
|
||||
proxyRollout.setApprovalDecidedBy(rollout.getApprovalDecidedBy());
|
||||
proxyRollout.setApprovalRemark(rollout.getApprovalRemark());
|
||||
return proxyRollout;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.ui.rollout.rollout;
|
||||
|
||||
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.*;
|
||||
import static org.eclipse.hawkbit.ui.rollout.DistributionBarHelper.getTooltip;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -24,6 +26,7 @@ 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.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
|
||||
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
|
||||
@@ -77,6 +80,7 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
|
||||
private static final String ROLLOUT_RENDERER_DATA = "rolloutRendererData";
|
||||
|
||||
private static final String VIRT_PROP_APPROVE = "approve";
|
||||
private static final String VIRT_PROP_RUN = "run";
|
||||
private static final String VIRT_PROP_PAUSE = "pause";
|
||||
private static final String VIRT_PROP_UPDATE = "update";
|
||||
@@ -87,6 +91,8 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
|
||||
private final transient RolloutGroupManagement rolloutGroupManagement;
|
||||
|
||||
private final transient TenantConfigurationManagement tenantConfigManagement;
|
||||
|
||||
private final AddUpdateRolloutWindowLayout addUpdateRolloutWindow;
|
||||
|
||||
private final UINotification uiNotification;
|
||||
@@ -95,7 +101,8 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
|
||||
private static final List<RolloutStatus> DELETE_COPY_BUTTON_ENABLED = Arrays.asList(RolloutStatus.CREATING,
|
||||
RolloutStatus.ERROR_CREATING, RolloutStatus.ERROR_STARTING, RolloutStatus.PAUSED, RolloutStatus.READY,
|
||||
RolloutStatus.RUNNING, RolloutStatus.STARTING, RolloutStatus.STOPPED, RolloutStatus.FINISHED);
|
||||
RolloutStatus.RUNNING, RolloutStatus.STARTING, RolloutStatus.STOPPED, RolloutStatus.FINISHED,
|
||||
RolloutStatus.WAITING_FOR_APPROVAL, RolloutStatus.APPROVAL_DENIED);
|
||||
|
||||
private static final List<RolloutStatus> UPDATE_BUTTON_ENABLED = Arrays.asList(RolloutStatus.CREATING,
|
||||
RolloutStatus.ERROR_CREATING, RolloutStatus.ERROR_STARTING, RolloutStatus.PAUSED, RolloutStatus.READY,
|
||||
@@ -106,11 +113,14 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
private static final List<RolloutStatus> RUN_BUTTON_ENABLED = Arrays.asList(RolloutStatus.READY,
|
||||
RolloutStatus.PAUSED);
|
||||
|
||||
private static final List<RolloutStatus> APPROVE_BUTTON_ENABLED = Collections.singletonList(RolloutStatus.WAITING_FOR_APPROVAL);
|
||||
|
||||
private static final Map<RolloutStatus, StatusFontIcon> statusIconMap = new EnumMap<>(RolloutStatus.class);
|
||||
|
||||
private static final List<Object> HIDDEN_COLUMNS = Arrays.asList(SPUILabelDefinitions.VAR_CREATED_DATE,
|
||||
SPUILabelDefinitions.VAR_CREATED_USER, SPUILabelDefinitions.VAR_MODIFIED_DATE,
|
||||
SPUILabelDefinitions.VAR_MODIFIED_BY, SPUILabelDefinitions.VAR_DESC);
|
||||
SPUILabelDefinitions.VAR_MODIFIED_BY, SPUILabelDefinitions.VAR_APPROVAL_DECIDED_BY,
|
||||
SPUILabelDefinitions.VAR_APPROVAL_REMARK, SPUILabelDefinitions.VAR_DESC);
|
||||
|
||||
static {
|
||||
statusIconMap.put(RolloutStatus.FINISHED,
|
||||
@@ -118,6 +128,10 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
statusIconMap.put(RolloutStatus.PAUSED,
|
||||
new StatusFontIcon(FontAwesome.PAUSE, SPUIStyleDefinitions.STATUS_ICON_BLUE));
|
||||
statusIconMap.put(RolloutStatus.RUNNING, new StatusFontIcon(null, SPUIStyleDefinitions.STATUS_SPINNER_YELLOW));
|
||||
statusIconMap.put(RolloutStatus.WAITING_FOR_APPROVAL,
|
||||
new StatusFontIcon(FontAwesome.HOURGLASS_HALF, SPUIStyleDefinitions.STATUS_ICON_ORANGE));
|
||||
statusIconMap.put(RolloutStatus.APPROVAL_DENIED,
|
||||
new StatusFontIcon(FontAwesome.TIMES_CIRCLE, SPUIStyleDefinitions.STATUS_ICON_RED));
|
||||
statusIconMap.put(RolloutStatus.READY,
|
||||
new StatusFontIcon(FontAwesome.DOT_CIRCLE_O, SPUIStyleDefinitions.STATUS_ICON_LIGHT_BLUE));
|
||||
statusIconMap.put(RolloutStatus.STOPPED,
|
||||
@@ -134,12 +148,15 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
RolloutListGrid(final VaadinMessageSource 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 TargetManagement targetManagement, final EntityFactory entityFactory,
|
||||
final UiProperties uiProperties,
|
||||
final TargetFilterQueryManagement targetFilterQueryManagement,
|
||||
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement) {
|
||||
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement,
|
||||
final TenantConfigurationManagement tenantConfigManagement) {
|
||||
super(i18n, eventBus, permissionChecker);
|
||||
this.rolloutManagement = rolloutManagement;
|
||||
this.rolloutGroupManagement = rolloutGroupManagement;
|
||||
this.tenantConfigManagement = tenantConfigManagement;
|
||||
this.addUpdateRolloutWindow = new AddUpdateRolloutWindowLayout(rolloutManagement, targetManagement,
|
||||
uiNotification, uiProperties, entityFactory, i18n, eventBus, targetFilterQueryManagement,
|
||||
rolloutGroupManagement, quotaManagement);
|
||||
@@ -250,7 +267,10 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
false);
|
||||
rolloutGridContainer.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_DATE, String.class, null, false,
|
||||
false);
|
||||
|
||||
rolloutGridContainer.addContainerProperty(SPUILabelDefinitions.VAR_APPROVAL_DECIDED_BY, String.class, null, false,
|
||||
false);
|
||||
rolloutGridContainer.addContainerProperty(SPUILabelDefinitions.VAR_APPROVAL_REMARK, String.class, null, false,
|
||||
false);
|
||||
rolloutGridContainer.addContainerProperty(SPUILabelDefinitions.VAR_MODIFIED_DATE, String.class, null, false,
|
||||
false);
|
||||
rolloutGridContainer.addContainerProperty(SPUILabelDefinitions.VAR_CREATED_USER, String.class, null, false,
|
||||
@@ -289,6 +309,9 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
getColumn(VIRT_PROP_RUN).setMinimumWidth(25);
|
||||
getColumn(VIRT_PROP_RUN).setMaximumWidth(25);
|
||||
|
||||
getColumn(VIRT_PROP_APPROVE).setMinimumWidth(25);
|
||||
getColumn(VIRT_PROP_APPROVE).setMaximumWidth(25);
|
||||
|
||||
getColumn(VIRT_PROP_PAUSE).setMinimumWidth(25);
|
||||
getColumn(VIRT_PROP_PAUSE).setMaximumWidth(25);
|
||||
|
||||
@@ -315,12 +338,15 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
getColumn(SPUILabelDefinitions.VAR_CREATED_USER).setHeaderCaption(i18n.getMessage("header.createdBy"));
|
||||
getColumn(SPUILabelDefinitions.VAR_MODIFIED_DATE).setHeaderCaption(i18n.getMessage("header.modifiedDate"));
|
||||
getColumn(SPUILabelDefinitions.VAR_MODIFIED_BY).setHeaderCaption(i18n.getMessage("header.modifiedBy"));
|
||||
getColumn(SPUILabelDefinitions.VAR_APPROVAL_REMARK).setHeaderCaption(i18n.getMessage("header.approvalRemark"));
|
||||
getColumn(SPUILabelDefinitions.VAR_APPROVAL_DECIDED_BY).setHeaderCaption(i18n.getMessage("header.approvalDecidedBy"));
|
||||
getColumn(SPUILabelDefinitions.VAR_DESC).setHeaderCaption(i18n.getMessage("header.description"));
|
||||
getColumn(SPUILabelDefinitions.VAR_TOTAL_TARGETS_COUNT_STATUS)
|
||||
.setHeaderCaption(i18n.getMessage("header.detail.status"));
|
||||
getColumn(SPUILabelDefinitions.VAR_STATUS).setHeaderCaption(i18n.getMessage("header.status"));
|
||||
|
||||
getColumn(VIRT_PROP_RUN).setHeaderCaption(i18n.getMessage("header.action.run"));
|
||||
getColumn(VIRT_PROP_APPROVE).setHeaderCaption(i18n.getMessage("header.action.approve"));
|
||||
getColumn(VIRT_PROP_PAUSE).setHeaderCaption(i18n.getMessage("header.action.pause"));
|
||||
getColumn(VIRT_PROP_UPDATE).setHeaderCaption(i18n.getMessage("header.action.update"));
|
||||
getColumn(VIRT_PROP_COPY).setHeaderCaption(i18n.getMessage("header.action.copy"));
|
||||
@@ -331,7 +357,7 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
|
||||
private HeaderCell joinColumns() {
|
||||
|
||||
return getDefaultHeaderRow().join(VIRT_PROP_RUN, VIRT_PROP_PAUSE, VIRT_PROP_UPDATE, VIRT_PROP_COPY,
|
||||
return getDefaultHeaderRow().join(VIRT_PROP_RUN, VIRT_PROP_APPROVE, VIRT_PROP_PAUSE, VIRT_PROP_UPDATE, VIRT_PROP_COPY,
|
||||
VIRT_PROP_DELETE);
|
||||
}
|
||||
|
||||
@@ -346,10 +372,11 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
final List<String> columnsToShowInOrder = Arrays.asList(ROLLOUT_RENDERER_DATA,
|
||||
SPUILabelDefinitions.VAR_DIST_NAME_VERSION, SPUILabelDefinitions.VAR_STATUS,
|
||||
SPUILabelDefinitions.VAR_TOTAL_TARGETS_COUNT_STATUS, SPUILabelDefinitions.VAR_NUMBER_OF_GROUPS,
|
||||
SPUILabelDefinitions.VAR_TOTAL_TARGETS, VIRT_PROP_RUN, VIRT_PROP_PAUSE, VIRT_PROP_UPDATE,
|
||||
SPUILabelDefinitions.VAR_TOTAL_TARGETS, VIRT_PROP_APPROVE, VIRT_PROP_RUN, VIRT_PROP_PAUSE, VIRT_PROP_UPDATE,
|
||||
VIRT_PROP_COPY, VIRT_PROP_DELETE, SPUILabelDefinitions.VAR_CREATED_DATE,
|
||||
SPUILabelDefinitions.VAR_CREATED_USER, SPUILabelDefinitions.VAR_MODIFIED_DATE,
|
||||
SPUILabelDefinitions.VAR_MODIFIED_BY, SPUILabelDefinitions.VAR_DESC);
|
||||
SPUILabelDefinitions.VAR_MODIFIED_BY, SPUILabelDefinitions.VAR_APPROVAL_DECIDED_BY,
|
||||
SPUILabelDefinitions.VAR_APPROVAL_REMARK, SPUILabelDefinitions.VAR_DESC);
|
||||
|
||||
setColumns(columnsToShowInOrder.toArray());
|
||||
}
|
||||
@@ -361,6 +388,7 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
}
|
||||
|
||||
getColumn(VIRT_PROP_RUN).setHidable(false);
|
||||
getColumn(VIRT_PROP_APPROVE).setHidable(false);
|
||||
getColumn(VIRT_PROP_PAUSE).setHidable(false);
|
||||
getColumn(VIRT_PROP_DELETE).setHidable(false);
|
||||
getColumn(VIRT_PROP_UPDATE).setHidable(false);
|
||||
@@ -388,6 +416,9 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
getColumn(VIRT_PROP_RUN).setRenderer(
|
||||
new GridButtonRenderer(clickEvent -> startOrResumeRollout((Long) clickEvent.getItemId())),
|
||||
new RolloutGridButtonConverter(this::createRunButtonMetadata));
|
||||
getColumn(VIRT_PROP_APPROVE).setRenderer(
|
||||
new GridButtonRenderer(clickEvent -> approveRollout((Long) clickEvent.getItemId())),
|
||||
new RolloutGridButtonConverter(this::createApprovalButtonMetadata));
|
||||
getColumn(VIRT_PROP_PAUSE).setRenderer(
|
||||
new GridButtonRenderer(clickEvent -> pauseRollout((Long) clickEvent.getItemId())),
|
||||
new RolloutGridButtonConverter(this::createPauseButtonMetadata));
|
||||
@@ -440,6 +471,7 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
final GeneratedPropertyContainer decoratedContainer = getDecoratedContainer();
|
||||
|
||||
decoratedContainer.addGeneratedProperty(VIRT_PROP_RUN, new GenericPropertyValueGenerator());
|
||||
decoratedContainer.addGeneratedProperty(VIRT_PROP_APPROVE, new GenericPropertyValueGenerator());
|
||||
decoratedContainer.addGeneratedProperty(VIRT_PROP_PAUSE, new GenericPropertyValueGenerator());
|
||||
decoratedContainer.addGeneratedProperty(VIRT_PROP_UPDATE, new GenericPropertyValueGenerator());
|
||||
decoratedContainer.addGeneratedProperty(VIRT_PROP_COPY, new GenericPropertyValueGenerator());
|
||||
@@ -520,6 +552,13 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
}
|
||||
}
|
||||
|
||||
private void approveRollout(final Long rolloutId) {
|
||||
final CommonDialogWindow addTargetWindow = addUpdateRolloutWindow.getWindow(rolloutId, false);
|
||||
addTargetWindow.setCaption(i18n.getMessage("caption.approve.rollout"));
|
||||
UI.getCurrent().addWindow(addTargetWindow);
|
||||
addTargetWindow.setVisible(Boolean.TRUE);
|
||||
}
|
||||
|
||||
private void updateRollout(final Long rolloutId) {
|
||||
final CommonDialogWindow addTargetWindow = addUpdateRolloutWindow.getWindow(rolloutId, false);
|
||||
addTargetWindow.setCaption(i18n.getMessage("caption.update.rollout"));
|
||||
@@ -578,7 +617,7 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
String description = null;
|
||||
|
||||
if (SPUILabelDefinitions.VAR_STATUS.equals(cell.getPropertyId())) {
|
||||
description = cell.getProperty().getValue().toString().toLowerCase();
|
||||
description = cell.getProperty().getValue().toString().toLowerCase().replace("_", " ");
|
||||
} else if (SPUILabelDefinitions.ACTION.equals(cell.getPropertyId())) {
|
||||
description = SPUILabelDefinitions.ACTION.toLowerCase();
|
||||
} else if (ROLLOUT_RENDERER_DATA.equals(cell.getPropertyId())) {
|
||||
@@ -595,6 +634,12 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
return !expectedRolloutStatus.contains(currentRolloutStatus);
|
||||
}
|
||||
|
||||
private StatusFontIcon createApprovalButtonMetadata(final RolloutStatus rolloutStatus) {
|
||||
final boolean isDisabled = hasToBeDisabled(rolloutStatus, APPROVE_BUTTON_ENABLED);
|
||||
return new StatusFontIcon(FontAwesome.GAVEL, null, i18n.getMessage("tooltip.rollout.approve"),
|
||||
UIComponentIdProvider.ROLLOUT_APPROVAL_BUTTON_ID, isDisabled);
|
||||
}
|
||||
|
||||
private StatusFontIcon createRunButtonMetadata(final RolloutStatus rolloutStatus) {
|
||||
final boolean isDisabled = hasToBeDisabled(rolloutStatus, RUN_BUTTON_ENABLED);
|
||||
return new StatusFontIcon(FontAwesome.PLAY, null, i18n.getMessage("tooltip.rollout.run"),
|
||||
@@ -744,6 +789,11 @@ public class RolloutListGrid extends AbstractGrid<LazyQueryContainer> {
|
||||
if (!permissionChecker.hasRolloutCreatePermission()) {
|
||||
modifiableColumnsList.remove(VIRT_PROP_COPY);
|
||||
}
|
||||
if (!permissionChecker.hasRolloutApprovalPermission() ||
|
||||
!tenantConfigManagement.getConfigurationValue(
|
||||
TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue()) {
|
||||
modifiableColumnsList.remove(VIRT_PROP_APPROVE);
|
||||
}
|
||||
if (!permissionChecker.hasRolloutDeletePermission()) {
|
||||
modifiableColumnsList.remove(VIRT_PROP_DELETE);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ 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.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.ui.SpPermissionChecker;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
import org.eclipse.hawkbit.ui.common.grid.AbstractGrid;
|
||||
@@ -38,6 +39,7 @@ public class RolloutListView extends AbstractGridComponentLayout {
|
||||
private final transient EntityFactory entityFactory;
|
||||
private final transient TargetFilterQueryManagement targetFilterQueryManagement;
|
||||
private final transient QuotaManagement quotaManagement;
|
||||
private final transient TenantConfigurationManagement tenantConfigManagement;
|
||||
|
||||
private final SpPermissionChecker permissionChecker;
|
||||
private final RolloutUIState rolloutUIState;
|
||||
@@ -47,9 +49,11 @@ public class RolloutListView extends AbstractGridComponentLayout {
|
||||
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 VaadinMessageSource i18n,
|
||||
final UiProperties uiProperties, final EntityFactory entityFactory,
|
||||
final VaadinMessageSource i18n,
|
||||
final TargetFilterQueryManagement targetFilterQueryManagement,
|
||||
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement) {
|
||||
final RolloutGroupManagement rolloutGroupManagement, final QuotaManagement quotaManagement,
|
||||
final TenantConfigurationManagement tenantConfigManagement) {
|
||||
super(i18n, eventBus);
|
||||
this.permissionChecker = permissionChecker;
|
||||
this.rolloutUIState = rolloutUIState;
|
||||
@@ -61,6 +65,7 @@ public class RolloutListView extends AbstractGridComponentLayout {
|
||||
this.uiProperties = uiProperties;
|
||||
this.entityFactory = entityFactory;
|
||||
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
||||
this.tenantConfigManagement = tenantConfigManagement;
|
||||
|
||||
init();
|
||||
}
|
||||
@@ -76,7 +81,7 @@ public class RolloutListView extends AbstractGridComponentLayout {
|
||||
public AbstractGrid<LazyQueryContainer> createGrid() {
|
||||
return new RolloutListGrid(i18n, eventBus, rolloutManagement, uiNotification, rolloutUIState, permissionChecker,
|
||||
targetManagement, entityFactory, uiProperties, targetFilterQueryManagement, rolloutGroupManagement,
|
||||
quotaManagement);
|
||||
quotaManagement, tenantConfigManagement);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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.tenantconfiguration;
|
||||
|
||||
import com.vaadin.data.Property;
|
||||
import com.vaadin.ui.CheckBox;
|
||||
import com.vaadin.ui.GridLayout;
|
||||
import com.vaadin.ui.Label;
|
||||
import com.vaadin.ui.Link;
|
||||
import com.vaadin.ui.Panel;
|
||||
import com.vaadin.ui.VerticalLayout;
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.ui.UiProperties;
|
||||
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
|
||||
import org.eclipse.hawkbit.ui.tenantconfiguration.rollout.ApprovalConfigurationItem;
|
||||
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
|
||||
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
||||
|
||||
/**
|
||||
* Provides configuration of the RolloutManagement including enabling/disabling
|
||||
* of the approval workflow.
|
||||
*/
|
||||
public class RolloutConfigurationView extends BaseConfigurationView
|
||||
implements Property.ValueChangeListener, ConfigurationItem.ConfigurationItemChangeListener {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final ApprovalConfigurationItem approvalConfigurationItem;
|
||||
private final VaadinMessageSource i18n;
|
||||
private final UiProperties uiProperties;
|
||||
private CheckBox approvalCheckbox;
|
||||
|
||||
RolloutConfigurationView(final VaadinMessageSource i18n,
|
||||
final TenantConfigurationManagement tenantConfigurationManagement, final UiProperties uiProperties) {
|
||||
this.i18n = i18n;
|
||||
this.uiProperties = uiProperties;
|
||||
this.approvalConfigurationItem = new ApprovalConfigurationItem(tenantConfigurationManagement, i18n);
|
||||
this.init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
|
||||
final Panel rootPanel = new Panel();
|
||||
rootPanel.setSizeFull();
|
||||
|
||||
rootPanel.addStyleName("config-panel");
|
||||
|
||||
final VerticalLayout vLayout = new VerticalLayout();
|
||||
vLayout.setMargin(true);
|
||||
vLayout.setSizeFull();
|
||||
|
||||
final Label headerDisSetType = new Label(i18n.getMessage("configuration.rollout.title"));
|
||||
headerDisSetType.addStyleName("config-panel-header");
|
||||
vLayout.addComponent(headerDisSetType);
|
||||
|
||||
final Link linkToApprovalHelp = SPUIComponentProvider
|
||||
.getHelpLink(uiProperties.getLinks().getDocumentation().getRollout());
|
||||
vLayout.addComponent(linkToApprovalHelp);
|
||||
|
||||
final GridLayout gridLayout = new GridLayout(2, 1);
|
||||
gridLayout.setSpacing(true);
|
||||
gridLayout.setImmediate(true);
|
||||
gridLayout.setColumnExpandRatio(1, 1.0F);
|
||||
|
||||
approvalCheckbox = SPUIComponentProvider.getCheckBox("", "", null, false, "");
|
||||
approvalCheckbox.setId(UIComponentIdProvider.ROLLOUT_APPROVAL_ENABLED_CHECKBOX);
|
||||
approvalCheckbox.setValue(approvalConfigurationItem.isConfigEnabled());
|
||||
approvalCheckbox.addValueChangeListener(this);
|
||||
approvalConfigurationItem.addChangeListener(this);
|
||||
gridLayout.addComponent(approvalCheckbox, 0, 0);
|
||||
gridLayout.addComponent(approvalConfigurationItem, 1, 0);
|
||||
|
||||
vLayout.addComponent(gridLayout);
|
||||
rootPanel.setContent(vLayout);
|
||||
setCompositionRoot(rootPanel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
this.approvalConfigurationItem.save();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo() {
|
||||
this.approvalConfigurationItem.undo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void valueChange(Property.ValueChangeEvent event) {
|
||||
if (approvalCheckbox.equals(event.getProperty())) {
|
||||
if (approvalCheckbox.getValue()) {
|
||||
approvalConfigurationItem.configEnable();
|
||||
} else {
|
||||
approvalConfigurationItem.configDisable();
|
||||
}
|
||||
notifyConfigurationChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configurationHasChanged() {
|
||||
notifyConfigurationChanged();
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,8 @@ public class TenantConfigurationDashboardView extends CustomComponent implements
|
||||
|
||||
private final PollingConfigurationView pollingConfigurationView;
|
||||
|
||||
private final RolloutConfigurationView rolloutConfigurationView;
|
||||
|
||||
private final VaadinMessageSource i18n;
|
||||
|
||||
private final UiProperties uiProperties;
|
||||
@@ -90,6 +92,7 @@ public class TenantConfigurationDashboardView extends CustomComponent implements
|
||||
this.pollingConfigurationView = new PollingConfigurationView(i18n, controllerPollProperties,
|
||||
tenantConfigurationManagement);
|
||||
this.repositoryConfigurationView = new RepositoryConfigurationView(i18n, tenantConfigurationManagement);
|
||||
this.rolloutConfigurationView = new RolloutConfigurationView(i18n, tenantConfigurationManagement, uiProperties);
|
||||
|
||||
this.i18n = i18n;
|
||||
this.uiProperties = uiProperties;
|
||||
@@ -105,7 +108,7 @@ public class TenantConfigurationDashboardView extends CustomComponent implements
|
||||
configurationViews.add(defaultDistributionSetTypeLayout);
|
||||
}
|
||||
configurationViews.add(repositoryConfigurationView);
|
||||
|
||||
configurationViews.add(rolloutConfigurationView);
|
||||
configurationViews.add(authenticationConfigurationView);
|
||||
configurationViews.add(pollingConfigurationView);
|
||||
if (customConfigurationViews != null) {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2018 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.tenantconfiguration.rollout;
|
||||
|
||||
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
|
||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
|
||||
import org.eclipse.hawkbit.ui.tenantconfiguration.generic.AbstractBooleanTenantConfigurationItem;
|
||||
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
|
||||
|
||||
/**
|
||||
* This class represents the UI item for the target security token section in
|
||||
* the authentication configuration view.
|
||||
*/
|
||||
public class ApprovalConfigurationItem extends AbstractBooleanTenantConfigurationItem {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private boolean configurationEnabled;
|
||||
private boolean configurationEnabledChange;
|
||||
|
||||
/**
|
||||
* Constructor for tenant specific approval mode setting.
|
||||
*
|
||||
* @param tenantConfigurationManagement used to enable/disable the approval mode per tenant
|
||||
* @param i18n used to translate labels
|
||||
*/
|
||||
public ApprovalConfigurationItem(final TenantConfigurationManagement tenantConfigurationManagement,
|
||||
final VaadinMessageSource i18n) {
|
||||
super(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, tenantConfigurationManagement, i18n);
|
||||
|
||||
super.init("configuration.rollout.approval.label");
|
||||
configurationEnabled = isConfigEnabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configEnable() {
|
||||
if (!configurationEnabled) {
|
||||
configurationEnabledChange = true;
|
||||
}
|
||||
configurationEnabled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configDisable() {
|
||||
if (configurationEnabled) {
|
||||
configurationEnabledChange = true;
|
||||
}
|
||||
configurationEnabled = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
if (!configurationEnabledChange) {
|
||||
return;
|
||||
}
|
||||
getTenantConfigurationManagement().addOrUpdateConfiguration(getConfigurationKey(), configurationEnabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void undo() {
|
||||
configurationEnabledChange = false;
|
||||
configurationEnabled = getTenantConfigurationManagement()
|
||||
.getConfigurationValue(getConfigurationKey(), Boolean.class).getValue();
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,16 @@ public final class SPUILabelDefinitions {
|
||||
* Last modified date.
|
||||
*/
|
||||
public static final String VAR_MODIFIED_DATE = "modifiedDate";
|
||||
|
||||
/**
|
||||
* Approve/Deny remark.
|
||||
*/
|
||||
public static final String VAR_APPROVAL_REMARK = "approvalRemark";
|
||||
/**
|
||||
* Approval decider.
|
||||
*/
|
||||
public static final String VAR_APPROVAL_DECIDED_BY = "approvalDecidedBy";
|
||||
|
||||
/**
|
||||
* Poll Status.
|
||||
*/
|
||||
|
||||
@@ -866,6 +866,16 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
public static final String ROLLOUT_RUN_BUTTON_ID = ROLLOUT_ACTION_ID + ".6";
|
||||
|
||||
/**
|
||||
* Rollout approval button id.
|
||||
*/
|
||||
public static final String ROLLOUT_APPROVAL_BUTTON_ID = ROLLOUT_ACTION_ID + ".11";
|
||||
|
||||
/**
|
||||
* Rollout approve/deny option button group id.
|
||||
*/
|
||||
public static final String ROLLOUT_APPROVAL_OPTIONGROUP_ID = ROLLOUT_ACTION_ID + ".12";
|
||||
|
||||
/**
|
||||
* Rollout pause button id.
|
||||
*/
|
||||
@@ -1082,6 +1092,17 @@ public final class UIComponentIdProvider {
|
||||
*/
|
||||
public static final String REPOSITORY_ACTIONS_AUTOCLOSE_CHECKBOX = "repositoryactionsautoclosecheckbox";
|
||||
|
||||
/**
|
||||
* Configuration checkbox for
|
||||
* {@link TenantConfigurationKey#ROLLOUT_APPROVAL_ENABLED}
|
||||
*/
|
||||
public static final String ROLLOUT_APPROVAL_ENABLED_CHECKBOX = "rollout.approve.enabled.checkbox";
|
||||
|
||||
/**
|
||||
* Id of the rollout approval remark field
|
||||
*/
|
||||
public static final String ROLLOUT_APPROVAL_REMARK_FIELD_ID = "rollout.approve.remark";
|
||||
|
||||
/**
|
||||
* /* Private Constructor.
|
||||
*/
|
||||
|
||||
@@ -38,6 +38,9 @@ button.cancel = Cancel
|
||||
button.upload.file = Upload File
|
||||
button.no.auto.assignment = none
|
||||
button.auto.assignment.desc = Select auto assign distribution set
|
||||
button.approve = Approve
|
||||
button.deny = Deny
|
||||
button.apply = Apply
|
||||
bulk.targets.upload = Please upload csv file.
|
||||
bulkupload.ds.name = DS Name
|
||||
button.discard=Discard
|
||||
@@ -72,6 +75,7 @@ header.swmodules=SwModules
|
||||
header.migrations.step=IsRequiredMigrationStep
|
||||
header.action=Actions
|
||||
header.action.run=Run
|
||||
header.action.approve=Approve
|
||||
header.action.pause=Pause
|
||||
header.action.update=Edit
|
||||
header.action.copy=Copy
|
||||
@@ -219,6 +223,8 @@ label.unsupported.browser.ie=Sorry! current browser is not supported. Please use
|
||||
label.auto.assign.description=When an auto assign distribution set is selected, it will be automatically assigned to all targets that match the target filter.
|
||||
label.auto.assign.enable=Enable auto assignment
|
||||
label.scheduled=Scheduled
|
||||
label.approval.decision = Approval decision
|
||||
label.approval.remark = Remark (optional)
|
||||
|
||||
# Checkbox label prefix with - checkbox
|
||||
checkbox.dist.migration.required = Required Migration Step :
|
||||
@@ -254,6 +260,7 @@ tooltip.metadata.icon = Manage Metadata
|
||||
tooltip.next.maintenancewindow = next on {0}
|
||||
#rollout action
|
||||
tooltip.rollout.run = Run
|
||||
tooltip.rollout.approve = Approve
|
||||
tooltip.rollout.pause = Pause
|
||||
tooltip.rollout.update = Update..
|
||||
tooltip.rollout.copy = Copy..
|
||||
@@ -483,6 +490,8 @@ configuration.polling.title=Polling Configuration
|
||||
configuration.polling.time=Polling Time
|
||||
configuration.polling.overduetime=Polling Overdue Time
|
||||
configuration.polling.custom.value=use a custom value
|
||||
configuration.rollout.title=Rollout Configuration
|
||||
configuration.rollout.approval.label=Approve rollout before it can be started
|
||||
|
||||
#Calendar
|
||||
calendar.year=year
|
||||
@@ -518,6 +527,8 @@ metadata.targetvisible = Visible for targets
|
||||
header.auto.assignment.ds = Auto assignment
|
||||
header.target.filter.name = Target filter name
|
||||
header.target.filter.query = Target filter query
|
||||
header.approvalDecidedBy = Decided By
|
||||
header.approvalRemark = Approval Remark
|
||||
|
||||
distribution.details.header = Distribution set
|
||||
target.details.header = Target
|
||||
@@ -556,6 +567,7 @@ caption.configure.rollout = Configure Rollout
|
||||
caption.configure.rollout.groups = Configure Deployment Groups
|
||||
caption.update.rollout = Update Rollout
|
||||
caption.create.rollout = Create new Rollout
|
||||
caption.approve.rollout = Approve 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
|
||||
|
||||
Reference in New Issue
Block a user