diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/AbstractFileTransferHandler.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/AbstractFileTransferHandler.java index b38afd387..f1116696b 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/AbstractFileTransferHandler.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/artifacts/upload/AbstractFileTransferHandler.java @@ -36,7 +36,6 @@ import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload; import org.eclipse.hawkbit.ui.common.event.EntityModifiedEventPayload.EntityModifiedEventType; import org.eclipse.hawkbit.ui.common.event.EventTopics; import org.eclipse.hawkbit.ui.utils.SpringContextHolder; -import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,8 +68,6 @@ public abstract class AbstractFileTransferHandler implements Serializable { private final VaadinMessageSource i18n; - protected final UINotification uiNotification; - private final transient Lock uploadLock; protected static final RegexCharacterCollection ILLEGAL_FILENAME_CHARACTERS = new RegexCharacterCollection( @@ -82,7 +79,6 @@ public abstract class AbstractFileTransferHandler implements Serializable { this.i18n = i18n; this.eventBus = SpringContextHolder.getInstance().getBean(EventBus.UIEventBus.class); this.artifactUploadState = SpringContextHolder.getInstance().getBean(ArtifactUploadState.class); - this.uiNotification = SpringContextHolder.getInstance().getBean(UINotification.class); this.uploadLock = uploadLock; } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/layout/AbstractFooterSupport.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/layout/AbstractFooterSupport.java index b4aa82fd8..937dfcc8f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/layout/AbstractFooterSupport.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/common/layout/AbstractFooterSupport.java @@ -8,15 +8,64 @@ */ package org.eclipse.hawkbit.ui.common.layout; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; +import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UIMessageIdProvider; +import org.eclipse.hawkbit.ui.utils.UINotification; +import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + import com.vaadin.server.Sizeable.Unit; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.Label; import com.vaadin.ui.Layout; +import com.vaadin.ui.UI; /** * If footer support is enabled, the footer is placed below the component */ public abstract class AbstractFooterSupport { + private static final Logger LOG = LoggerFactory.getLogger(AbstractFooterSupport.class); + + protected final VaadinMessageSource i18n; + private final UINotification notification; + + protected final Label countLabel; + + private final ExecutorService countExecutor; + private Future currentCountCalculation; + private Future currentCountDetailsCalculation; + + protected AbstractFooterSupport(final VaadinMessageSource i18n, final UINotification notification) { + this.i18n = i18n; + this.notification = notification; + + this.countLabel = new Label(); + this.countExecutor = Executors.newSingleThreadExecutor(); + + init(); + } + + /** + * Init footer message label, can be overriden to adapt label styling. + * + */ + protected void init() { + countLabel.setId(UIComponentIdProvider.COUNT_LABEL); + countLabel.addStyleName(SPUIStyleDefinitions.SP_LABEL_MESSAGE_STYLE); + + countLabel.addDetachListener(e -> { + abortCurrentCountCalculation(); + abortCurrentDetailsCountCalculation(); + }); + } /** * Creates a sub-layout for the footer. @@ -29,15 +78,75 @@ public abstract class AbstractFooterSupport { footerLayout.setSpacing(false); footerLayout.setWidth(100, Unit.PERCENTAGE); - footerLayout.addComponent(getFooterMessageLabel()); + footerLayout.addComponent(countLabel); return footerLayout; } /** - * Get the footer message label. + * Calculates count asynchronously and updated the count label. * - * @return footer message + * @param countValueUpdater + * callback to update count value + * @param countUiUpdater + * callback to update count label in UI */ - protected abstract Label getFooterMessageLabel(); + protected void updateCountAsynchronously(final Runnable countValueUpdater, final Runnable countUiUpdater) { + abortCurrentCountCalculation(); + countLabel.setCaption(i18n.getMessage("label.calculating")); + + currentCountCalculation = submitAsynchronousCountUpdate(countValueUpdater, countUiUpdater); + } + + private Future submitAsynchronousCountUpdate(final Runnable countValueUpdater, final Runnable countUiUpdater) { + final UI ui = UI.getCurrent(); + final SecurityContext securityContext = SecurityContextHolder.getContext(); + + return countExecutor.submit(() -> { + try { + LOG.trace("Started calculating count asynchronously"); + SecurityContextHolder.setContext(securityContext); + countValueUpdater.run(); + + LOG.trace("Finished calculating count asynchronously, updating UI"); + ui.access(countUiUpdater); + } catch (final Exception ex) { + LOG.error("Error occurred during asynchronous count calculation", ex); + ui.access(() -> notification + .displayValidationError(i18n.getMessage(UIMessageIdProvider.MESSAGE_ERROR_COUNT_FAILED))); + } + }); + } + + /** + * Calculates count details asynchronously and updated the details count + * label. + * + * @param countDetailsValueUpdater + * callback to update details count value + * @param countDetailsUiUpdater + * callback to update details count label in UI + */ + protected void updateCountDetailsAsynchronously(final Runnable countDetailsValueUpdater, + final Runnable countDetailsUiUpdater) { + abortCurrentDetailsCountCalculation(); + countLabel.setValue(i18n.getMessage("label.calculating")); + + currentCountDetailsCalculation = submitAsynchronousCountUpdate(countDetailsValueUpdater, countDetailsUiUpdater); + } + + private void abortCurrentCountCalculation() { + if (currentCountCalculation != null && !currentCountCalculation.isCancelled()) { + currentCountCalculation.cancel(true); + currentCountCalculation = null; + } + } + + private void abortCurrentDetailsCountCalculation() { + if (currentCountDetailsCalculation != null && !currentCountDetailsCalculation.isCancelled()) { + currentCountDetailsCalculation.cancel(true); + currentCountDetailsCalculation = null; + } + } + } \ No newline at end of file diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterCountMessageLabel.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterCountMessageLabel.java index 437122560..e5ddd10b7 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterCountMessageLabel.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterCountMessageLabel.java @@ -8,57 +8,54 @@ */ package org.eclipse.hawkbit.ui.filtermanagement; +import java.util.function.IntSupplier; + import org.eclipse.hawkbit.ui.common.layout.AbstractFooterSupport; -import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; -import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; -import com.vaadin.ui.Label; - /** - * Count message label which display current filter details and details on - * pinning. + * Count message label which display current total filtered targets count. */ public class TargetFilterCountMessageLabel extends AbstractFooterSupport { - private final VaadinMessageSource i18n; - - private final Label targetCountLabel; + private int totalFilteredTargetsCount; /** * Constructor for TargetFilterCountMessageLabel * * @param i18n - * VaadinMessageSource + * VaadinMessageSource */ - public TargetFilterCountMessageLabel(final VaadinMessageSource i18n) { - this.i18n = i18n; - this.targetCountLabel = new Label(); - init(); - } - - private void init() { - targetCountLabel.setId(UIComponentIdProvider.COUNT_LABEL); - targetCountLabel.addStyleName(SPUIStyleDefinitions.SP_LABEL_MESSAGE_STYLE); - - updateTotalFilteredTargetsCount(0); + public TargetFilterCountMessageLabel(final VaadinMessageSource i18n, final UINotification notification) { + super(i18n, notification); } @Override - protected Label getFooterMessageLabel() { - return targetCountLabel; + protected void init() { + super.init(); + + totalFilteredTargetsCount = 0; + updateTotalFilteredTargetsCountLabel(); } /** - * Update the total count of target filtered + * Update the total count of filtered targets asynchronously. + * + * @param fetchTotalFilteredTargetsCount + * total filtered targets count provider * - * @param count - * Total target filtered count */ - public void updateTotalFilteredTargetsCount(final long count) { + public void updateTotalFilteredTargetsCount(final IntSupplier fetchTotalFilteredTargetsCount) { + updateCountAsynchronously(() -> totalFilteredTargetsCount = fetchTotalFilteredTargetsCount.getAsInt(), + this::updateTotalFilteredTargetsCountLabel); + } + + private void updateTotalFilteredTargetsCountLabel() { final StringBuilder targetMessage = new StringBuilder(i18n.getMessage("label.target.filtered.total")); targetMessage.append(": "); - targetMessage.append(count); - targetCountLabel.setCaption(targetMessage.toString()); + targetMessage.append(totalFilteredTargetsCount); + + countLabel.setCaption(targetMessage.toString()); } } diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterDetailsLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterDetailsLayout.java index 7f6555d58..5c820a56a 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterDetailsLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/filtermanagement/TargetFilterDetailsLayout.java @@ -62,7 +62,8 @@ public class TargetFilterDetailsLayout extends AbstractGridComponentLayout { uiProperties, rsqlValidationOracle, uiState); this.targetFilterTargetGrid = new TargetFilterTargetGrid(uiDependencies, targetFilterStateDataSupplier, uiState); - this.targetFilterCountMessageLabel = new TargetFilterCountMessageLabel(uiDependencies.getI18n()); + this.targetFilterCountMessageLabel = new TargetFilterCountMessageLabel(uiDependencies.getI18n(), + uiDependencies.getUiNotification()); initGridDataUpdatedListener(); @@ -81,7 +82,7 @@ public class TargetFilterDetailsLayout extends AbstractGridComponentLayout { private void initGridDataUpdatedListener() { targetFilterTargetGrid.addDataChangedListener(event -> targetFilterCountMessageLabel - .updateTotalFilteredTargetsCount(targetFilterTargetGrid.getDataSize())); + .updateTotalFilteredTargetsCount(targetFilterTargetGrid::getDataSize)); } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetCountMessageLabel.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetCountMessageLabel.java index d8bc33586..f812d571d 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetCountMessageLabel.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetCountMessageLabel.java @@ -18,82 +18,75 @@ import org.eclipse.hawkbit.ui.common.grid.support.FilterSupport; import org.eclipse.hawkbit.ui.common.layout.AbstractFooterSupport; import org.eclipse.hawkbit.ui.common.layout.CountAwareComponent; import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil; -import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions; -import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider; +import org.eclipse.hawkbit.ui.utils.UINotification; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.vaadin.data.provider.Query; import com.vaadin.shared.ui.ContentMode; -import com.vaadin.ui.Label; /** * Count message label which display current filter details and details on * pinning. */ public class TargetCountMessageLabel extends AbstractFooterSupport implements CountAwareComponent { - private final VaadinMessageSource i18n; private final TargetManagement targetManagement; private final FilterSupport gridFilterSupport; - private final Label targetCountLabel; - private int totalCount; private int filteredCount; + private long targetsWithAssignedDsCount; + private long targetsWithInstalledDsCount; + /** * Constructor * * @param i18n * I18N */ - public TargetCountMessageLabel(final VaadinMessageSource i18n, final TargetManagement targetManagement, + public TargetCountMessageLabel(final VaadinMessageSource i18n, final UINotification notification, + final TargetManagement targetManagement, final FilterSupport gridFilterSupport) { - this.i18n = i18n; + super(i18n, notification); + this.targetManagement = targetManagement; this.gridFilterSupport = gridFilterSupport; - this.targetCountLabel = new Label(); - - init(); } - private void init() { - targetCountLabel.setId(UIComponentIdProvider.COUNT_LABEL); - targetCountLabel.addStyleName(SPUIStyleDefinitions.SP_LABEL_MESSAGE_STYLE); - targetCountLabel.setContentMode(ContentMode.HTML); - targetCountLabel.setIcon(null); - targetCountLabel.setDescription(null); - } + @Override + protected void init() { + super.init(); - public void updateTotalCount() { - totalCount = fetchTotalCount(); - updateCountLabel(); - } - - private int fetchTotalCount() { - return gridFilterSupport.getOriginalDataProvider().size(new Query<>()); - } - - public void updateFilteredCount() { - if (gridFilterSupport.getFilter().isAnyFilterSelected()) { - filteredCount = fetchFilteredCount(); - } - - updateCountLabel(); - } - - private int fetchFilteredCount() { - return gridFilterSupport.getFilterDataProvider().size(new Query<>()); + countLabel.setContentMode(ContentMode.HTML); + countLabel.setIcon(null); + countLabel.setDescription(null); } public void updateTotalAndFilteredCount() { - totalCount = fetchTotalCount(); - if (gridFilterSupport.getFilter().isAnyFilterSelected()) { - filteredCount = fetchFilteredCount(); - } + updateCountAsynchronously(this::fetchTotalAndFilteredCount, this::updateCountLabel); + } - updateCountLabel(); + private void fetchTotalAndFilteredCount() { + fetchTotalCount(); + fetchFilteredCount(); + } + + private void fetchTotalCount() { + totalCount = gridFilterSupport.getOriginalDataProvider().size(new Query<>()); + } + + private void fetchFilteredCount() { + if (gridFilterSupport.getFilter().isAnyFilterSelected()) { + filteredCount = gridFilterSupport.getFilterDataProvider().size(new Query<>()); + } else { + filteredCount = 0; + } + } + + public void updateFilteredCount() { + updateCountAsynchronously(this::fetchFilteredCount, this::updateCountLabel); } private void updateCountLabel() { @@ -104,7 +97,7 @@ public class TargetCountMessageLabel extends AbstractFooterSupport implements Co appendFilteredTargetsMessage(countMessageBuilder, targetFilterParams); } - targetCountLabel.setCaption(countMessageBuilder.toString()); + countLabel.setCaption(countMessageBuilder.toString()); } private StringBuilder getTotalTargetsMessage() { @@ -134,7 +127,8 @@ public class TargetCountMessageLabel extends AbstractFooterSupport implements Co appendSearchMsg(filterMessageBuilder, targetFilterParams.getSearchText()); appendDsMsg(filterMessageBuilder, targetFilterParams.getDistributionId()); appendCustomFilterQueryMsg(filterMessageBuilder, targetFilterParams.getTargetFilterQueryId()); - appendTargetTypeFilterMsg(filterMessageBuilder, targetFilterParams.isNoTargetTypeClicked(), targetFilterParams.getTargetTypeId()); + appendTargetTypeFilterMsg(filterMessageBuilder, targetFilterParams.isNoTargetTypeClicked(), + targetFilterParams.getTargetTypeId()); String filterMessage = filterMessageBuilder.toString().trim(); if (filterMessage.endsWith(",")) { @@ -187,7 +181,8 @@ public class TargetCountMessageLabel extends AbstractFooterSupport implements Co } } - private void appendTargetTypeFilterMsg(final StringBuilder filterMessageBuilder, boolean noTargetTypeClicked, final Long targetTypeId) { + private void appendTargetTypeFilterMsg(final StringBuilder filterMessageBuilder, final boolean noTargetTypeClicked, + final Long targetTypeId) { if (targetTypeId != null || noTargetTypeClicked) { appendFilterMsg(filterMessageBuilder, i18n.getMessage("label.filter.target.type")); } @@ -200,27 +195,28 @@ public class TargetCountMessageLabel extends AbstractFooterSupport implements Co public void updatePinningDetails() { final Long pinnedDsId = gridFilterSupport.getFilter().getPinnedDistId(); if (pinnedDsId == null) { - targetCountLabel.setValue(""); + countLabel.setValue(""); return; } - final Long targetsWithAssigedDsCount = targetManagement.countByAssignedDistributionSet(pinnedDsId); - final Long targetsWithInstalledDsCount = targetManagement.countByInstalledDistributionSet(pinnedDsId); + updateCountDetailsAsynchronously(() -> fetchPinningCounts(pinnedDsId), this::updatePinningCountLabel); + } + private void fetchPinningCounts(final Long pinnedDsId) { + targetsWithAssignedDsCount = targetManagement.countByAssignedDistributionSet(pinnedDsId); + targetsWithInstalledDsCount = targetManagement.countByInstalledDistributionSet(pinnedDsId); + } + + private void updatePinningCountLabel() { final StringBuilder message = new StringBuilder(i18n.getMessage("label.target.count")); message.append(" : "); message.append(""); - message.append(i18n.getMessage("label.assigned.count", targetsWithAssigedDsCount)); + message.append(i18n.getMessage("label.assigned.count", targetsWithAssignedDsCount)); message.append(", "); message.append(i18n.getMessage("label.installed.count", targetsWithInstalledDsCount)); message.append(""); - targetCountLabel.setValue(message.toString()); - } - - @Override - protected Label getFooterMessageLabel() { - return targetCountLabel; + countLabel.setValue(message.toString()); } @Override diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java index 42ef3d1b2..9df67cdd5 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/management/targettable/TargetGridLayout.java @@ -141,8 +141,8 @@ public class TargetGridLayout extends AbstractGridComponentLayout { this.targetDetails = new TargetDetails(uiDependencies, targetTagManagement, targetManagement, deploymentManagement, targetMetaDataWindowBuilder); - this.countMessageLabel = new TargetCountMessageLabel(uiDependencies.getI18n(), targetManagement, - targetGrid.getFilterSupport()); + this.countMessageLabel = new TargetCountMessageLabel(uiDependencies.getI18n(), + uiDependencies.getUiNotification(), targetManagement, targetGrid.getFilterSupport()); final EventLayoutViewAware layoutViewAware = new EventLayoutViewAware(EventLayout.TARGET_LIST, EventView.DEPLOYMENT); diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetGridLayout.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetGridLayout.java index bb712acf4..e41d63300 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetGridLayout.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/rollout/rolloutgrouptargets/RolloutGroupTargetGridLayout.java @@ -54,7 +54,8 @@ public class RolloutGroupTargetGridLayout extends AbstractGridComponentLayout { this.rolloutGroupTargetsListHeader = new RolloutGroupTargetGridHeader(uiDependencies, rolloutManagementUIState); this.rolloutGroupTargetsListGrid = new RolloutGroupTargetGrid(uiDependencies, rolloutGroupManagement, rolloutManagementUIState); - this.rolloutGroupTargetCountMessageLabel = new TargetFilterCountMessageLabel(uiDependencies.getI18n()); + this.rolloutGroupTargetCountMessageLabel = new TargetFilterCountMessageLabel(uiDependencies.getI18n(), + uiDependencies.getUiNotification()); initGridDataUpdatedListener(); @@ -73,7 +74,7 @@ public class RolloutGroupTargetGridLayout extends AbstractGridComponentLayout { private void initGridDataUpdatedListener() { rolloutGroupTargetsListGrid.addDataChangedListener(event -> rolloutGroupTargetCountMessageLabel - .updateTotalFilteredTargetsCount(rolloutGroupTargetsListGrid.getDataSize())); + .updateTotalFilteredTargetsCount(rolloutGroupTargetsListGrid::getDataSize)); } private List> getMasterEntityAwareComponents() { diff --git a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java index a130a6d08..041fe199f 100644 --- a/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java +++ b/hawkbit-ui/src/main/java/org/eclipse/hawkbit/ui/utils/UIMessageIdProvider.java @@ -175,6 +175,8 @@ public final class UIMessageIdProvider { public static final String MESSAGE_ERROR_DECRYPTION_FAILED = "message.decryption.failed"; + public static final String MESSAGE_ERROR_COUNT_FAILED = "message.count.failed"; + public static final String CRON_VALIDATION_ERROR = "message.maintenancewindow.schedule.validation.error"; public static final String TOOLTIP_OVERDUE = "tooltip.overdue"; diff --git a/hawkbit-ui/src/main/resources/messages.properties b/hawkbit-ui/src/main/resources/messages.properties index 1e7f8cfcb..0a39b1f6d 100644 --- a/hawkbit-ui/src/main/resources/messages.properties +++ b/hawkbit-ui/src/main/resources/messages.properties @@ -269,6 +269,7 @@ label.invalidate.ds.cancelation.type = Type of cancelation: label.cancel.action.none = None label.cancel.action.force = Forced label.cancel.action.soft = Soft +label.calculating = Calculating... # TextFields prefix with - textfield textfield.name = Name @@ -573,6 +574,7 @@ message.encryption.unsupported = Artifact encryption not supported message.encryption.secrets.failed = Artifact encryption secrets generation failed message.encryption.failed = Artifact encryption failed message.decryption.failed = Artifact decryption failed +message.count.failed = There was an error during count calculation, please contact administrator artifact.upload.popup.caption = Upload status artifact.upload.status.caption = Status