Adapt UI for target type compatibility check (#1189)

* Added compatibility calls needed for UI

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* Adapted UI for target type compatibility checks

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* improved exception handling for incompatibility check

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* added & fixed unit tests

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fixed merged conflicts

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fixed target type incompatibly specification

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* changed UI behaviour to close assignment popup in case of IncompatibleTargetTypeException

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* added unit test to validate incompatibly specification fix

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fixed review findings

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fixed review findings

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* fix potential null pointer

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* Fixed rolloutcopy by adding dsTypeId to ProxyDistributionSetInfo

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>

* suppressed warning

Signed-off-by: Robert Sing <robert.sing@bosch-si.com>
This commit is contained in:
Robert Sing
2021-10-22 16:23:25 +02:00
committed by GitHub
parent f94b4430e0
commit dea6fa3ce6
28 changed files with 473 additions and 347 deletions

View File

@@ -20,6 +20,7 @@ import org.eclipse.hawkbit.ui.common.data.suppliers.TargetManagementStateDataSup
import org.eclipse.hawkbit.ui.error.HawkbitUIErrorHandler;
import org.eclipse.hawkbit.ui.error.extractors.ConstraintViolationErrorExtractor;
import org.eclipse.hawkbit.ui.error.extractors.EntityNotFoundErrorExtractor;
import org.eclipse.hawkbit.ui.error.extractors.IncompatibleTargetTypeErrorExtractor;
import org.eclipse.hawkbit.ui.error.extractors.InsufficientPermissionErrorExtractor;
import org.eclipse.hawkbit.ui.error.extractors.InvalidDistributionSetErrorExtractor;
import org.eclipse.hawkbit.ui.error.extractors.UiErrorDetailsExtractor;
@@ -144,9 +145,21 @@ public class MgmtUiConfiguration {
return new EntityNotFoundErrorExtractor(i18n);
}
/**
* UI incompatible Target Type error details extractor bean.
*
* @param i18n
* VaadinMessageSource
* @return UI IncompatibleTargetType Error details extractor
*/
@Bean
UiErrorDetailsExtractor incompatibleTargetTypeErrorExtractor(final VaadinMessageSource i18n) {
return new IncompatibleTargetTypeErrorExtractor(i18n);
}
/**
* UI Insufficient Permission Error details extractor bean.
*
*
* @param i18n
* VaadinMessageSource
* @return UI InsufficientPermission Error details extractor

View File

@@ -26,7 +26,8 @@ public class RolloutToProxyRolloutMapper extends AbstractNamedEntityToProxyNamed
mapNamedEntityAttributes(rollout, proxyRollout);
final DistributionSet ds = rollout.getDistributionSet();
proxyRollout.setDsInfo(new ProxyDistributionSetInfo(ds.getId(), ds.getName(), ds.getVersion(), ds.isValid()));
proxyRollout.setDsInfo(new ProxyDistributionSetInfo(ds.getId(), ds.getName(), ds.getVersion(),
ds.getType().getId(), ds.isValid()));
proxyRollout
.setNumberOfGroups(rollout.getRolloutGroupsCreated() > 0 ? rollout.getRolloutGroupsCreated() : null);
proxyRollout.setForcedTime(rollout.getForcedTime() > 0 ? rollout.getForcedTime() : null);

View File

@@ -38,7 +38,8 @@ public class TargetFilterQueryToProxyTargetFilterMapper
if (distributionSet != null) {
proxyTargetFilter.setAutoAssignmentEnabled(true);
proxyTargetFilter.setDistributionSetInfo(new ProxyDistributionSetInfo(distributionSet.getId(),
distributionSet.getName(), distributionSet.getVersion(), distributionSet.isValid()));
distributionSet.getName(), distributionSet.getVersion(), distributionSet.getType().getId(),
distributionSet.isValid()));
proxyTargetFilter.setAutoAssignActionType(targetFilterQuery.getAutoAssignActionType());
}

View File

@@ -87,7 +87,6 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa
}
/**
* Flag that indicates if the distribution set is valid.
*
* @return <code>true</code> if the distribution set is valid, otherwise
@@ -163,6 +162,7 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa
ds.setId(dsInfo.getId());
ds.setName(dsInfo.getName());
ds.setVersion(dsInfo.getVersion());
ds.setTypeInfo(new ProxyTypeInfo(dsInfo.getDsTypeId(), null));
ds.setNameVersion(dsInfo.getNameVersion());
ds.setIsValid(dsInfo.isValid());
@@ -170,11 +170,12 @@ public class ProxyDistributionSet extends ProxyNamedEntity implements VersionAwa
}
/**
* Gets the Id, Name and version of distribution set
* Gets the Id, name, version, dsTypeId and invalidation state of distribution
* set
*
* @return proxy of Id, Name and version
* @return proxy of Id, name, version, dsTypeId and invalidation state
*/
public ProxyDistributionSetInfo getInfo() {
return new ProxyDistributionSetInfo(getId(), getName(), getVersion(), getIsValid());
return new ProxyDistributionSetInfo(getId(), getName(), getVersion(), getTypeInfo().getId(), getIsValid());
}
}

View File

@@ -21,6 +21,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity {
private String name;
private String version;
private String nameVersion;
private Long dsTypeId;
private boolean isValid;
/**
@@ -39,12 +40,18 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity {
* distribution set name
* @param version
* distribution set version
* @param dsTypeId
* ID of the assigned dsType
* @param isValid
* invalidation state
*/
public ProxyDistributionSetInfo(final Long id, final String name, final String version, final boolean isValid) {
public ProxyDistributionSetInfo(final Long id, final String name, final String version, final Long dsTypeId,
final boolean isValid) {
super(id);
this.name = name;
this.version = version;
this.dsTypeId = dsTypeId;
this.isValid = isValid;
this.nameVersion = HawkbitCommonUtil.getFormattedNameVersion(name, version);
}
@@ -81,13 +88,23 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity {
this.isValid = isValid;
}
public Long getDsTypeId() {
return dsTypeId;
}
public void setDsTypeId(final Long dsTypeId) {
this.dsTypeId = dsTypeId;
}
@Override
public int hashCode() {
// nameVersion is ignored because it is a composition of name and
// version
return Objects.hash(getId(), getName(), getVersion(), isValid());
return Objects.hash(getId(), getName(), getVersion(), getDsTypeId(), isValid());
}
// equals method requires all of the used conditions
@SuppressWarnings("squid:S1067")
@Override
public boolean equals(final Object obj) {
if (obj == null) {
@@ -102,6 +119,7 @@ public class ProxyDistributionSetInfo extends ProxyIdentifiableEntity {
// version
return Objects.equals(this.getId(), other.getId()) && Objects.equals(this.getName(), other.getName())
&& Objects.equals(this.getVersion(), other.getVersion())
&& Objects.equals(this.getDsTypeId(), other.getDsTypeId())
&& Objects.equals(this.isValid(), other.isValid());
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2021 Bosch.IO 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.error.extractors;
import java.util.Optional;
import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException;
import org.eclipse.hawkbit.ui.error.UiErrorDetails;
import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
/**
* UI error details extractor for {@link IncompatibleTargetTypeException}.
*/
public class IncompatibleTargetTypeErrorExtractor extends AbstractSingleUiErrorDetailsExtractor {
private final VaadinMessageSource i18n;
/**
* Constructor for IncompatibleTargetTypeErrorExtractor.
*
* @param i18n
* Message source used for localization
*/
public IncompatibleTargetTypeErrorExtractor(final VaadinMessageSource i18n) {
this.i18n = i18n;
}
@Override
protected Optional<UiErrorDetails> findDetails(final Throwable error) {
return findExceptionOf(error, IncompatibleTargetTypeException.class)
.map(ex -> UiErrorDetails.create(i18n.getMessage("caption.error"),
i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_TARGET_TYPE_INCOMPATIBLE,
ex.getTargetTypeNames(), ex.getDistributionSetTypeNames())));
}
}

View File

@@ -64,8 +64,8 @@ public class DeploymentAssignmentWindowController {
* @param deploymentManagement
* DeploymentManagement
*/
public DeploymentAssignmentWindowController(final CommonUiDependencies uiDependencies, final UiProperties uiProperties,
final DeploymentManagement deploymentManagement) {
public DeploymentAssignmentWindowController(final CommonUiDependencies uiDependencies,
final UiProperties uiProperties, final DeploymentManagement deploymentManagement) {
this.i18n = uiDependencies.getI18n();
this.eventBus = uiDependencies.getEventBus();
this.notification = uiDependencies.getUiNotification();

View File

@@ -56,6 +56,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
private final GridLayout layout;
private String targetFilter;
private Long dsTypeId;
private final List<AdvancedGroupRow> groupRows;
private int lastGroupIndex;
@@ -237,7 +238,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
private void validateTargetsPerGroup() {
resetErrors();
if (StringUtils.isEmpty(targetFilter)) {
if (StringUtils.isEmpty(targetFilter) || dsTypeId == null) {
return;
}
@@ -245,7 +246,7 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
final List<RolloutGroupCreate> groupsCreate = getRolloutGroupsCreateFromDefinitions(
getAdvancedRolloutGroupDefinitions());
final ListenableFuture<RolloutGroupsValidation> validateTargetsInGroups = rolloutManagement
.validateTargetsInGroups(groupsCreate, targetFilter, System.currentTimeMillis());
.validateTargetsInGroups(groupsCreate, targetFilter, System.currentTimeMillis(), dsTypeId);
final UI ui = UI.getCurrent();
validateTargetsInGroups.addCallback(validation -> ui.access(() -> updateGroupsByValidation(validation)),
@@ -273,10 +274,9 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
}
/**
* YOU SHOULD NOT CALL THIS METHOD MANUALLY. It's only for the callback.
* Only 1 runningValidation should be executed. If this runningValidation is
* done, then this method is called. Maybe then a new runningValidation is
* executed.
* YOU SHOULD NOT CALL THIS METHOD MANUALLY. It's only for the callback. Only 1
* runningValidation should be executed. If this runningValidation is done, then
* this method is called. Maybe then a new runningValidation is executed.
*
*/
private void updateGroupsByValidation(final RolloutGroupsValidation validation) {
@@ -338,7 +338,30 @@ public class AdvancedGroupsLayout extends ValidatableLayout {
*/
public void setTargetFilter(final String targetFilter) {
this.targetFilter = targetFilter;
updateValidation();
}
/**
*
* @param dsTypeId
* ID of the Distribution set type which is required for the
* compatibility check
*/
public void setDsTypeId(final Long dsTypeId) {
this.dsTypeId = dsTypeId;
updateValidation();
}
/**
* @param targetFilter
* the target filter which is required for verification
* @param dsTypeId
* ID of the Distribution set type which is required for the
* compatibility check
*/
public void setTargetFilterAndDsType(final String targetFilter, final Long dsTypeId) {
this.targetFilter = targetFilter;
this.dsTypeId = dsTypeId;
updateValidation();
}

View File

@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
import com.vaadin.data.Binder;
import com.vaadin.data.Binder.Binding;
import com.vaadin.data.HasValue;
import com.vaadin.data.ValidationException;
import com.vaadin.data.Validator;
import com.vaadin.data.validator.LongRangeValidator;
@@ -72,6 +73,7 @@ public class RolloutFormLayout extends ValidatableLayout {
private Long totalTargets;
private Consumer<String> filterQueryChangedListener;
private Consumer<Long> distSetChangedListener;
/**
* Constructor for RolloutFormLayout
@@ -190,11 +192,8 @@ public class RolloutFormLayout extends ValidatableLayout {
}
private void addValueChangeListeners() {
targetFilterQueryCombo.getComponent().addValueChangeListener(event -> {
if (filterQueryChangedListener != null) {
filterQueryChangedListener.accept(event.getValue() != null ? event.getValue().getQuery() : null);
}
});
targetFilterQueryCombo.getComponent().addValueChangeListener(filterQueryChangedListener());
dsCombo.addValueChangeListener(distSetChangedListener());
actionTypeLayout.getComponent().getActionTypeOptionGroup().addValueChangeListener(
event -> actionTypeLayout.setRequired(event.getValue() == ActionType.TIMEFORCED));
@@ -202,6 +201,26 @@ public class RolloutFormLayout extends ValidatableLayout {
event -> autoStartOptionGroupLayout.setRequired(event.getValue() == AutoStartOption.SCHEDULED));
}
private HasValue.ValueChangeListener<ProxyTargetFilterQuery> filterQueryChangedListener() {
return event -> {
if (filterQueryChangedListener != null) {
filterQueryChangedListener.accept(event.getValue() != null ? event.getValue().getQuery() : null);
}
};
}
private HasValue.ValueChangeListener<ProxyDistributionSet> distSetChangedListener() {
return event -> {
if (distSetChangedListener != null) {
if (event.getValue() != null && event.getValue().getTypeInfo() != null) {
distSetChangedListener.accept(event.getValue().getTypeInfo().getId());
} else {
distSetChangedListener.accept(null);
}
}
};
}
/**
* Add rollout form to add layout
*
@@ -285,6 +304,16 @@ public class RolloutFormLayout extends ValidatableLayout {
this.filterQueryChangedListener = filterQueryChangedListener;
}
/**
* Sets the changed listener for distribution set
*
* @param distSetChangedListener
* Changed listener
*/
public void setDistSetChangedListener(final Consumer<Long> distSetChangedListener) {
this.distSetChangedListener = distSetChangedListener;
}
/**
* Sets the count of total targets
*

View File

@@ -61,7 +61,7 @@ public class VisualGroupDefinitionLayout {
if (groupDefinitionMode == GroupDefinitionMode.SIMPLE) {
updateBySimpleGroupsDefinition();
} else {
} else if (groupDefinitionMode == GroupDefinitionMode.ADVANCED) {
updateByAdvancedGroupsDefinition();
}
}

View File

@@ -42,6 +42,7 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
private final VisualGroupDefinitionLayout visualGroupDefinitionLayout;
private String filterQuery;
private Long dsTypeId;
private Long totalTargets;
private int noOfGroups;
private List<ProxyAdvancedRolloutGroup> advancedRolloutGroupDefinitions;
@@ -82,24 +83,33 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
}
private void addValueChangeListeners() {
rolloutFormLayout.setFilterQueryChangedListener(this::onTargetFilterQueryChange);
rolloutFormLayout.setFilterQueryChangedListener(this::onFilterQueryChange);
rolloutFormLayout.setDistSetChangedListener(this::onDistSetTypeChange);
groupsDefinitionTabs.addSelectedTabChangeListener(event -> onGroupDefinitionTabChanged());
simpleGroupsLayout.setNoOfGroupsChangedListener(this::onNoOfSimpleGroupsChanged);
advancedGroupsLayout.setAdvancedGroupDefinitionsChangedListener(this::onAdvancedGroupsChanged);
}
private void onTargetFilterQueryChange(final String filterQuery) {
private void onFilterQueryChange(final String filterQuery) {
this.filterQuery = filterQuery;
totalTargets = !StringUtils.isEmpty(filterQuery) ? targetManagement.countByRsql(filterQuery) : null;
updateTotalTargetsAwareComponents();
updateTotalTargets();
if (isAdvancedGroupsTabSelected()) {
advancedGroupsLayout.setTargetFilter(filterQuery);
}
}
private void updateTotalTargetsAwareComponents() {
private void onDistSetTypeChange(final Long dsTypeId) {
this.dsTypeId = dsTypeId;
updateTotalTargets();
if (isAdvancedGroupsTabSelected()) {
advancedGroupsLayout.setDsTypeId(dsTypeId);
}
}
private void updateTotalTargets() {
this.totalTargets = getTotalTargets(filterQuery, dsTypeId);
rolloutFormLayout.setTotalTargets(totalTargets);
visualGroupDefinitionLayout.setTotalTargets(totalTargets);
if (isSimpleGroupsTabSelected()) {
@@ -107,6 +117,16 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
}
}
private Long getTotalTargets(final String filterQuery, final Long distSetTypeId) {
if (StringUtils.isEmpty(filterQuery)) {
return null;
}
if (distSetTypeId == null) {
return targetManagement.countByRsql(filterQuery);
}
return targetManagement.countByRsqlAndCompatible(filterQuery, distSetTypeId);
}
private boolean isSimpleGroupsTabSelected() {
return groupsDefinitionTabs.getSelectedTab().equals(simpleGroupsLayout.getLayout());
}
@@ -128,7 +148,7 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
if (isAdvancedGroupsTabSelected()) {
adaptAdvancedGroupsValidation();
advancedGroupsLayout.setTargetFilter(filterQuery);
advancedGroupsLayout.setTargetFilterAndDsType(filterQuery, dsTypeId);
visualGroupDefinitionLayout.setGroupDefinitionMode(GroupDefinitionMode.ADVANCED);
visualGroupDefinitionLayout.setAdvancedRolloutGroupDefinitions(advancedRolloutGroupDefinitions);
@@ -209,7 +229,6 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
proxyEntity.setAdvancedRolloutGroupDefinitions(advancedGroupsLayout.getAdvancedRolloutGroupDefinitions());
proxyEntity.setGroupDefinitionMode(GroupDefinitionMode.ADVANCED);
}
return proxyEntity;
}
}

View File

@@ -163,6 +163,8 @@ public final class UIMessageIdProvider {
public static final String MESSAGE_ERROR_PERMISSION_INSUFFICIENT = "message.permission.insufficient";
public static final String MESSAGE_ERROR_TARGET_TYPE_INCOMPATIBLE = "message.target.type.incompatible";
public static final String CRON_VALIDATION_ERROR = "message.maintenancewindow.schedule.validation.error";
public static final String TOOLTIP_OVERDUE = "tooltip.overdue";

View File

@@ -538,6 +538,7 @@ message.invalidate.distributionset.affected.entities.intro.plural = When you con
message.invalidate.distributionset.affected.entities.actions = {0} action(s) will be canceled
message.invalidate.distributionset.affected.entities.rollouts = {0} rollout(s) will be stopped
message.invalidate.distributionset.affected.entities.autoassignments = {0} auto assignment(s) will be stopped
message.target.type.incompatible = Target(s) of type(s) {0} not compatible with distribution set type(s) {1}
# action info
action.target.table.selectall = Select all (Ctrl+A)