Create Rollout UI with Groups definition (#369)

* Rollout creation UI with groups definition

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Improved code quality

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Added a legend to the Rollouts Creation UI's PieChart

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Updated color palette for pie chart. Using RolloutGroupCreate for validation. Displaying pie chart for group editing.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Optimized ComboBoxBuilder

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Removed not necessary RPC interfaces.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Fix Rollout verification count query. Improved UI style. Added dependency management for d3.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Auto start and scheduled start of Rollouts

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Optimized RSQL Utility to avoid multiple joins on the same table.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Fixed RSQL Utility to avoid multiple joins on the same table to only work with OR nodes. Optimized validation queries for rollout group creation.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Asynchronous handling of groups validation in the create rollouts UI.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Added loading indicator to create rollout UI.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Avoid parallel validation for a user session.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Fix issues after merge of master into branch.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Improve code quality

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Refreshing the total target count after validation on the rollouts creation UI.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>

* Remove unused RolloutUpdate method. Optimization of the RolloutHelper.
Comments for RolloutGroupsValidation and RSQLUtility with explanation.

Signed-off-by: Dominik Herbst <dominik.herbst@bosch-si.com>
This commit is contained in:
Dominik Herbst
2017-01-26 09:37:20 +01:00
committed by Kai Zimmermann
parent 430bf632cf
commit 833b0795e3
45 changed files with 2864 additions and 369 deletions

View File

@@ -33,6 +33,7 @@
<inherits name="org.eclipse.hawkbit.ui.customrenderers.CustomRendererWidgetSet" />
<inherits name="org.eclipse.hawkbit.ui.filtermanagement.TextFieldSuggestionBox" />
<inherits name="org.eclipse.hawkbit.ui.rollout.groupschart.GroupsPieChart" />
<inherits name="org.eclipse.hawkbit.ui.dd.CustomAcceptCriteria" />

View File

@@ -247,6 +247,13 @@ public class CommonDialogWindow extends Window {
saveButton.setEnabled(isSaveButtonEnabledAfterValueChange(null, null));
}
/**
* Clears the original values in case no value changed check is wished
*/
public final void clearOriginalValues() {
orginalValues.clear();
}
protected void addCloseListenerForSaveButton() {
saveButton.addClickListener(closeClickListener);
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.common.builder;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
import com.vaadin.data.Property;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.themes.ValoTheme;
/**
* Builds ComboBox Elements with a commonly used properties.
*/
public class ComboBoxBuilder {
private Property.ValueChangeListener valueChangeListener;
private String id;
private String prompt;
public ComboBoxBuilder setValueChangeListener(final Property.ValueChangeListener valueChangeListener) {
this.valueChangeListener = valueChangeListener;
return this;
}
public ComboBoxBuilder setId(final String id) {
this.id = id;
return this;
}
public ComboBoxBuilder setPrompt(String prompt) {
this.prompt = prompt;
return this;
}
/**
* @return a new ComboBox
*/
public ComboBox buildCombBox() {
final ComboBox targetFilter = SPUIComponentProvider.getComboBox(null, "", null, ValoTheme.COMBOBOX_SMALL, false,
"", prompt);
targetFilter.setImmediate(true);
targetFilter.setPageLength(7);
targetFilter.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME);
targetFilter.setSizeUndefined();
if (id != null) {
targetFilter.setId(id);
}
if (valueChangeListener != null) {
targetFilter.addValueChangeListener(valueChangeListener);
}
return targetFilter;
}
}

View File

@@ -14,6 +14,7 @@ import javax.annotation.PreDestroy;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RolloutGroupManagement;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.ui.HawkbitUI;
import org.eclipse.hawkbit.ui.SpPermissionChecker;
@@ -65,10 +66,10 @@ public class RolloutView extends VerticalLayout implements View {
final UIEventBus eventBus, final RolloutManagement rolloutManagement,
final RolloutGroupManagement rolloutGroupManagement, final TargetManagement targetManagement,
final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory,
final I18N i18n) {
final I18N i18n, final TargetFilterQueryManagement targetFilterQueryManagement) {
this.permChecker = permissionChecker;
this.rolloutListView = new RolloutListView(permissionChecker, rolloutUIState, eventBus, rolloutManagement,
targetManagement, uiNotification, uiProperties, entityFactory, i18n);
targetManagement, uiNotification, uiProperties, entityFactory, i18n, targetFilterQueryManagement);
this.rolloutGroupsListView = new RolloutGroupsListView(i18n, eventBus, rolloutGroupManagement, rolloutUIState,
permissionChecker);
this.rolloutGroupTargetsListView = new RolloutGroupTargetsListView(eventBus, i18n, rolloutUIState);

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2015 Bosch Software Innovations GmbH and others.
All rights reserved. This program and the accompanying materials
are made available under the terms of the Eclipse Public License v1.0
which accompanies this distribution, and is available at
http://www.eclipse.org/legal/epl-v10.html
-->
<module>
<inherits name="com.vaadin.DefaultWidgetSet"/>
<inherits name="org.eclipse.hawkbit.ui.AppWidgetSet"/>
<inherits name="org.vaadin.peter.contextmenu.ContextmenuWidgetset"/>
<inherits name="org.vaadin.tokenfield.TokenfieldWidgetset"/>
<inherits name="org.eclipse.hawkbit.ui.customrenderers.CustomRendererWidgetSet"/>
<inherits name="org.vaadin.hene.flexibleoptiongroup.widgetset.FlexibleOptionGroupWidgetset"/>
<inherits name="org.vaadin.alump.distributionbar.gwt.DistributionBarWidgetset"/>
<inherits name="com.github.gwtd3.D3"/>
</module>

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.rollout.groupschart;
import org.eclipse.hawkbit.ui.rollout.groupschart.client.GroupsPieChartState;
import com.vaadin.ui.AbstractComponent;
import java.util.List;
/**
* Draws a pie charts for the provided groups.
*/
public class GroupsPieChart extends AbstractComponent {
private static final long serialVersionUID = 1311542227339430098L;
/**
* Updates the state of the chart
*
* @param groupTargetCounts
* list of target counts
* @param totalTargetsCount
* total count of targets that are represented by the pie
*/
public void setChartState(final List<Long> groupTargetCounts, final Long totalTargetsCount) {
getState().setGroupTargetCounts(groupTargetCounts);
getState().setTotalTargetCount(totalTargetsCount);
markAsDirty();
}
@Override
protected GroupsPieChartState getState() {
return (GroupsPieChartState) super.getState();
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.rollout.groupschart.client;
import com.vaadin.client.communication.StateChangeEvent;
import org.eclipse.hawkbit.ui.rollout.groupschart.GroupsPieChart;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ui.AbstractComponentConnector;
import com.vaadin.shared.ui.Connect;
/**
* Connector between client side GroupsPieChartWidget and service side.
*/
@Connect(GroupsPieChart.class)
public class GroupsPieChartConnector extends AbstractComponentConnector {
private static final long serialVersionUID = -2907528194018611155L;
@Override
protected Widget createWidget() {
return GWT.create(GroupsPieChartWidget.class);
}
@Override
public GroupsPieChartWidget getWidget() {
return (GroupsPieChartWidget) super.getWidget();
}
@Override
public GroupsPieChartState getState() {
return (GroupsPieChartState) super.getState();
}
@Override
public void onStateChanged(StateChangeEvent stateChangeEvent) {
super.onStateChanged(stateChangeEvent);
getWidget().update(getState().getGroupTargetCounts(), getState().getTotalTargetCount());
}
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.rollout.groupschart.client;
import com.vaadin.shared.AbstractComponentState;
import java.util.List;
/**
* State to transfer for the groups pie chart between server and client.
*/
public class GroupsPieChartState extends AbstractComponentState {
private static final long serialVersionUID = 7344220498082627571L;
private transient List<Long> groupTargetCounts;
private Long totalTargetCount;
public List<Long> getGroupTargetCounts() {
return groupTargetCounts;
}
public void setGroupTargetCounts(List<Long> groupTargetCounts) {
this.groupTargetCounts = groupTargetCounts;
}
public Long getTotalTargetCount() {
return totalTargetCount;
}
public void setTotalTargetCount(Long totalTargetCount) {
this.totalTargetCount = totalTargetCount;
}
}

View File

@@ -0,0 +1,201 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.rollout.groupschart.client;
import com.github.gwtd3.api.D3;
import com.github.gwtd3.api.arrays.Array;
import com.github.gwtd3.api.core.Selection;
import com.github.gwtd3.api.core.UpdateSelection;
import com.github.gwtd3.api.core.Value;
import com.github.gwtd3.api.functions.DatumFunction;
import com.github.gwtd3.api.svg.Arc;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import java.util.List;
/**
* Draws a pie chart using D3. The slices are based on the list of Longs and on
* the total target count. The total target count represents 100% of the pie. If
* the sum in the list of Longs is less than the total target count a slice for
* unassigned targets will be displayed.
*
*/
@SuppressWarnings("squid:TrailingCommentCheck")
public class GroupsPieChartWidget extends DockLayoutPanel {
private static final String ATTR_VISIBILITY = "visibility";
private static final String ATTR_TRANSFORM = "transform";
private List<Long> groupTargetCounts;
private Long totalTargetCount;
private long unassignedTargets;
private Selection svg;
private Selection pieGroup;
private Selection infoText;
private Arc arc;
/**
* Initializes the pie chart
*/
public GroupsPieChartWidget() {
super(Style.Unit.PX);
init();
}
private void init() {
initChart();
draw();
}
/**
* Updates the pie chart with new data
*
* @param groupTargetCounts
* list of target counts
* @param totalTargetCount
* total count of targets that are represented by the pie
*/
public void update(final List<Long> groupTargetCounts, final Long totalTargetCount) {
this.groupTargetCounts = groupTargetCounts;
this.totalTargetCount = totalTargetCount;
if (groupTargetCounts != null) {
long sum = 0;
for (Long targetCount : groupTargetCounts) {
sum += targetCount;
}
unassignedTargets = totalTargetCount - sum;
}
draw();
}
private static PieArc getPie(Long count, Long total, double startAngle) {
final Double percentage = count.doubleValue() / total.doubleValue();
return new PieArc(startAngle, startAngle + percentage * 2 * Math.PI);
}
private void draw() {
if (svg == null || groupTargetCounts == null || totalTargetCount == null) {
return;
}
final Array<Arc> dataArray = Array.create();
PieArc pie = getPie(unassignedTargets, totalTargetCount, 0);
dataArray.push(pie.getArc());
double lastAngle = pie.getEndAngle();
for (int i = 0; i < groupTargetCounts.size(); i++) {
final PieArc arcEntry = getPie(groupTargetCounts.get(i), totalTargetCount, lastAngle);
dataArray.push(arcEntry.getArc());
lastAngle = arcEntry.getEndAngle();
}
UpdateSelection pies = pieGroup.selectAll(".pie").data(dataArray);
pies.enter().append("path").classed("pie", true).on(BrowserEvents.MOUSEOVER, new DatumFunction<Void>() {
@Override
public Void apply(Element context, Value d, int index) {
Array<Double> point = arc.centroid(d.as(Arc.class), index);
double x = point.getNumber(0);
double y = point.getNumber(1);
if (index == 0) {
updateHoverText("Unassigned: " + unassignedTargets, x, y);
} else {
updateHoverText(index + ": " + groupTargetCounts.get(index - 1), x, y);
}
return null;
}
}).on(BrowserEvents.MOUSEOUT, new DatumFunction<Void>() {
@Override
public Void apply(Element context, Value d, int index) {
infoText.attr(ATTR_VISIBILITY, "hidden");
return null;
}
});
pies.exit().remove();
pies.attr("d", arc);
}
private void updateHoverText(final String displayText, final double x, final double y) {
final Selection text = infoText.select("text");
final Selection background = infoText.select("rect");
text.html(displayText);
final double textWidth = getTextWidth(text.node());
final double textHeight = getTextHeight(text.node());
background.attr("width", textWidth * 1.1);
background.attr("height", textHeight);
moveSelection(background, -textWidth * 1.1 / 2.0, -textHeight*0.8);
moveSelection(infoText, x, y);
infoText.attr(ATTR_VISIBILITY, "visible");
}
private static void moveSelection(Selection sel, double x, double y) {
sel.attr(ATTR_TRANSFORM, "translate(" + x + ", " + y + ")");
}
private static final native double getTextWidth(Element e)/*-{
return e.getBBox().width;
}-*/;
private static final native double getTextHeight(Element e)/*-{
return e.getBBox().height;
}-*/;
private void initChart() {
arc = D3.svg().arc().innerRadius(0).outerRadius(90);
int height = 200;
int width = 260;
svg = D3.select(this).append("svg").attr("width", width).attr("height", height).append("g");
moveSelection(svg, (float) width / 2, (float) height / 2);
pieGroup = svg.append("g");
infoText = svg.append("g").attr(ATTR_VISIBILITY, "hidden").classed("pie-info", true);
infoText.append("rect");
infoText.append("text").attr("text-anchor", "middle");
}
private static class PieArc {
private double startAngle;
private double endAngle;
public PieArc(double startAngle, double endAngle) {
this.startAngle = startAngle;
this.endAngle = endAngle;
}
public double getEndAngle() {
return endAngle;
}
public Arc getArc() {
return Arc.constantArc().startAngle(startAngle).endAngle(endAngle);
}
}
}

View File

@@ -9,13 +9,20 @@
package org.eclipse.hawkbit.ui.rollout.rollout;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.builder.RolloutCreate;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.builder.RolloutUpdate;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.RepositoryModelConstants;
import org.eclipse.hawkbit.repository.model.Rollout;
@@ -26,19 +33,22 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessActi
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditions;
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.ui.UiProperties;
import org.eclipse.hawkbit.ui.common.CommonDialogWindow;
import org.eclipse.hawkbit.ui.common.CommonDialogWindow.SaveDialogCloseListener;
import org.eclipse.hawkbit.ui.common.DistributionSetIdName;
import org.eclipse.hawkbit.ui.common.builder.ComboBoxBuilder;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.common.builder.TextAreaBuilder;
import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
import org.eclipse.hawkbit.ui.common.builder.WindowBuilder;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.filtermanagement.TargetFilterBeanQuery;
import org.eclipse.hawkbit.ui.management.footer.ActionTypeOptionGroupLayout;
import org.eclipse.hawkbit.ui.management.footer.ActionTypeOptionGroupLayout.ActionTypeOption;
import org.eclipse.hawkbit.ui.rollout.event.RolloutEvent;
import org.eclipse.hawkbit.ui.rollout.groupschart.GroupsPieChart;
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
import org.eclipse.hawkbit.ui.utils.I18N;
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
@@ -47,6 +57,8 @@ import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
import org.eclipse.hawkbit.ui.utils.SPUIStyleDefinitions;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import org.eclipse.hawkbit.ui.utils.UINotification;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory;
import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer;
import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition;
@@ -66,6 +78,7 @@ import com.vaadin.ui.ComboBox;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.OptionGroup;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;
import com.vaadin.ui.themes.ValoTheme;
@@ -83,16 +96,22 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private final ActionTypeOptionGroupLayout actionTypeOptionGroupLayout;
private final AutoStartOptionGroupLayout autoStartOptionGroupLayout;
private final transient RolloutManagement rolloutManagement;
private final transient TargetManagement targetManagement;
private final transient TargetFilterQueryManagement targetFilterQueryManagement;
private final UINotification uiNotification;
private final UiProperties uiProperties;
private final transient UiProperties uiProperties;
private final transient EntityFactory entityFactory;
private final DefineGroupsLayout defineGroupsLayout;
private final I18N i18n;
private final transient EventBus.UIEventBus eventBus;
@@ -117,22 +136,29 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private CommonDialogWindow window;
private Boolean editRolloutEnabled;
private boolean editRolloutEnabled;
private Rollout rolloutForEdit;
private Rollout rollout;
private Long totalTargetsCount;
private Label totalTargetsLabel;
private TextArea targetFilterQuery;
private TabSheet groupsDefinitionTabs;
private GroupsPieChart groupsPieChart;
private GroupsLegendLayout groupsLegendLayout;
private final transient RolloutGroupConditions defaultRolloutGroupConditions;
private final NullValidator nullValidator = new NullValidator(null, false);
AddUpdateRolloutWindowLayout(final RolloutManagement rolloutManagement, final TargetManagement targetManagement,
final UINotification uiNotification, final UiProperties uiProperties, final EntityFactory entityFactory,
final I18N i18n, final UIEventBus eventBus) {
final I18N i18n, final UIEventBus eventBus, final TargetFilterQueryManagement targetFilterQueryManagement) {
this.actionTypeOptionGroupLayout = new ActionTypeOptionGroupLayout(i18n);
this.autoStartOptionGroupLayout = new AutoStartOptionGroupLayout(i18n);
this.rolloutManagement = rolloutManagement;
this.targetManagement = targetManagement;
this.uiNotification = uiNotification;
@@ -140,10 +166,20 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
this.entityFactory = entityFactory;
this.i18n = i18n;
this.eventBus = eventBus;
this.targetFilterQueryManagement = targetFilterQueryManagement;
this.defineGroupsLayout = new DefineGroupsLayout(i18n, entityFactory, rolloutManagement,
targetFilterQueryManagement);
defaultRolloutGroupConditions = new RolloutGroupConditionBuilder().withDefaults().build();
setSizeUndefined();
createRequiredComponents();
buildLayout();
defineGroupsLayout.setValidationListener(this::displayValidationStatus);
defineGroupsLayout.setDefaultErrorThreshold(defaultRolloutGroupConditions.getErrorConditionExp());
defineGroupsLayout.setDefaultTriggerThreshold(defaultRolloutGroupConditions.getSuccessConditionExp());
}
/**
@@ -173,27 +209,39 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
*
* @param rolloutId
* the rollout id
* @param copy
* whether the rollout should be copied
* @return the window
*/
public CommonDialogWindow getWindow(final Long rolloutId) {
window = getWindow();
populateData(rolloutId);
public CommonDialogWindow getWindow(final Long rolloutId, final boolean copy) {
resetComponents();
window = createWindow();
populateData(rolloutId, copy);
return window;
}
public CommonDialogWindow getWindow() {
resetComponents();
private CommonDialogWindow createWindow() {
return new WindowBuilder(SPUIDefinitions.CREATE_UPDATE_WINDOW).caption(i18n.get("caption.configure.rollout"))
.content(this).layout(this).i18n(i18n)
.helpLink(uiProperties.getLinks().getDocumentation().getRolloutView())
.saveDialogCloseListener(new SaveOnDialogCloseListener()).buildCommonDialogWindow();
}
public CommonDialogWindow getWindow() {
resetComponents();
window = createWindow();
window.updateAllComponents(noOfGroups);
window.updateAllComponents(triggerThreshold);
window.updateAllComponents(errorThreshold);
return window;
}
/**
* Reset the field values.
*/
public void resetComponents() {
editRolloutEnabled = Boolean.FALSE;
defineGroupsLayout.resetComponents();
editRolloutEnabled = false;
rolloutName.clear();
targetFilterQuery.clear();
resetFields();
@@ -201,22 +249,26 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
populateDistributionSet();
populateTargetFilterQuery();
setDefaultSaveStartGroupOption();
totalTargetsLabel.setVisible(false);
groupsLegendLayout.reset();
groupSizeLabel.setVisible(false);
noOfGroups.setVisible(true);
removeComponent(1, 2);
addComponent(targetFilterQueryCombo, 1, 2);
actionTypeOptionGroupLayout.selectDefaultOption();
autoStartOptionGroupLayout.selectDefaultOption();
totalTargetsCount = 0L;
rolloutForEdit = null;
rollout = null;
groupsDefinitionTabs.setVisible(true);
groupsDefinitionTabs.setSelectedTab(0);
}
private void resetFields() {
rolloutName.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
noOfGroups.clear();
noOfGroups.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
triggerThreshold.clear();
triggerThreshold.setValue(defaultRolloutGroupConditions.getSuccessConditionExp());
triggerThreshold.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
errorThreshold.clear();
errorThreshold.setValue(defaultRolloutGroupConditions.getErrorConditionExp());
errorThreshold.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
description.clear();
description.removeStyleName(SPUIStyleDefinitions.SP_TEXTFIELD_ERROR);
@@ -224,11 +276,13 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private void buildLayout() {
setSpacing(Boolean.TRUE);
setSpacing(true);
setSizeUndefined();
setRows(9);
setColumns(3);
setRows(7);
setColumns(4);
setStyleName("marginTop");
setColumnExpandRatio(3, 1);
setWidth(850, Unit.PIXELS);
addComponent(getMandatoryLabel("textfield.name"), 0, 0);
addComponent(rolloutName, 1, 0);
@@ -243,28 +297,19 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
targetFilterQueryCombo.addValidator(nullValidator);
targetFilterQuery.removeValidator(nullValidator);
addComponent(totalTargetsLabel, 2, 2);
addComponent(getLabel("textfield.description"), 0, 3);
addComponent(description, 1, 3, 1, 3);
addComponent(getMandatoryLabel("prompt.number.of.groups"), 0, 3);
addComponent(noOfGroups, 1, 3);
noOfGroups.addValidator(nullValidator);
addComponent(groupsLegendLayout, 3, 0, 3, 3);
addComponent(groupsPieChart, 2, 0, 2, 3);
addComponent(groupSizeLabel, 2, 3);
addComponent(getMandatoryLabel("caption.rollout.action.type"), 0, 4);
addComponent(actionTypeOptionGroupLayout, 1, 4, 3, 4);
addComponent(getMandatoryLabel("prompt.tigger.threshold"), 0, 4);
addComponent(triggerThreshold, 1, 4);
triggerThreshold.addValidator(nullValidator);
addComponent(getMandatoryLabel("caption.rollout.start.type"), 0, 5);
addComponent(autoStartOptionGroupLayout, 1, 5, 3, 5);
addComponent(getPercentHintLabel(), 2, 4);
addComponent(getMandatoryLabel("prompt.error.threshold"), 0, 5);
addComponent(errorThreshold, 1, 5);
errorThreshold.addValidator(nullValidator);
addComponent(errorThresholdOptionGroup, 2, 5);
addComponent(getLabel("textfield.description"), 0, 6);
addComponent(description, 1, 6, 2, 6);
addComponent(actionTypeOptionGroupLayout, 0, 7, 2, 7);
addComponent(groupsDefinitionTabs, 0, 6, 3, 6);
rolloutName.focus();
}
@@ -310,15 +355,93 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
noOfGroups = createNoOfGroupsField();
groupSizeLabel = createCountLabel();
triggerThreshold = createTriggerThreshold();
errorThreshold = createErrorThreshold();
description = createDescription();
errorThresholdOptionGroup = createErrorThresholdOptionGroup();
setDefaultSaveStartGroupOption();
actionTypeOptionGroupLayout.selectDefaultOption();
totalTargetsLabel = createCountLabel();
autoStartOptionGroupLayout.selectDefaultOption();
targetFilterQuery = createTargetFilterQuery();
actionTypeOptionGroupLayout.addStyleName(SPUIStyleDefinitions.ROLLOUT_ACTION_TYPE_LAYOUT);
autoStartOptionGroupLayout.addStyleName(SPUIStyleDefinitions.ROLLOUT_ACTION_TYPE_LAYOUT);
groupsDefinitionTabs = createGroupDefinitionTabs();
groupsPieChart = new GroupsPieChart();
groupsPieChart.setWidth(260, Unit.PIXELS);
groupsPieChart.setHeight(220, Unit.PIXELS);
groupsPieChart.setStyleName(SPUIStyleDefinitions.ROLLOUT_GROUPS_CHART);
groupsLegendLayout = new GroupsLegendLayout(i18n);
}
private void displayValidationStatus(DefineGroupsLayout.ValidationStatus status) {
if(status == DefineGroupsLayout.ValidationStatus.LOADING) {
groupsLegendLayout.displayLoading();
} else {
validateGroups();
}
}
private TabSheet createGroupDefinitionTabs() {
TabSheet tabSheet = new TabSheet();
tabSheet.setId(UIComponentIdProvider.ROLLOUT_GROUPS);
tabSheet.setWidth(850, Unit.PIXELS);
tabSheet.setHeight(300, Unit.PIXELS);
tabSheet.setStyleName(SPUIStyleDefinitions.ROLLOUT_GROUPS);
TabSheet.Tab simpleTab = tabSheet.addTab(createSimpleGroupDefinitionTab(),
i18n.get("caption.rollout.tabs.simple"));
simpleTab.setId(UIComponentIdProvider.ROLLOUT_SIMPLE_TAB);
TabSheet.Tab advancedTab = tabSheet.addTab(defineGroupsLayout, i18n.get("caption.rollout.tabs.advanced"));
advancedTab.setId(UIComponentIdProvider.ROLLOUT_ADVANCED_TAB);
tabSheet.addSelectedTabChangeListener(event -> validateGroups());
return tabSheet;
}
private static int getPositionOfSelectedTab(final TabSheet tabSheet) {
return tabSheet.getTabPosition(tabSheet.getTab(tabSheet.getSelectedTab()));
}
private boolean isNumberOfGroups() {
return getPositionOfSelectedTab(groupsDefinitionTabs) == 0;
}
private boolean isGroupsDefinition() {
return getPositionOfSelectedTab(groupsDefinitionTabs) == 1;
}
private GridLayout createSimpleGroupDefinitionTab() {
GridLayout layout = new GridLayout();
layout.setSpacing(true);
layout.setColumns(3);
layout.setRows(4);
layout.setStyleName("marginTop");
layout.addComponent(getLabel("caption.rollout.generate.groups"), 0, 0, 2, 0);
layout.addComponent(getMandatoryLabel("prompt.number.of.groups"), 0, 1);
layout.addComponent(noOfGroups, 1, 1);
noOfGroups.addValidator(nullValidator);
layout.addComponent(groupSizeLabel, 2, 1);
layout.addComponent(getMandatoryLabel("prompt.tigger.threshold"), 0, 2);
layout.addComponent(triggerThreshold, 1, 2);
triggerThreshold.addValidator(nullValidator);
layout.addComponent(getPercentHintLabel(), 2, 2);
layout.addComponent(getMandatoryLabel("prompt.error.threshold"), 0, 3);
layout.addComponent(errorThreshold, 1, 3);
errorThreshold.addValidator(nullValidator);
layout.addComponent(errorThresholdOptionGroup, 2, 3);
return layout;
}
private static Label createCountLabel() {
@@ -365,35 +488,95 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
errorThreshold.getValidators();
}
private void validateGroups() {
if(editRolloutEnabled) {
return;
}
if(isGroupsDefinition()) {
List<RolloutGroupCreate> savedRolloutGroups = defineGroupsLayout.getSavedRolloutGroups();
if(!defineGroupsLayout.isValid() || savedRolloutGroups == null || savedRolloutGroups.isEmpty()) {
noOfGroups.clear();
} else {
noOfGroups.setValue(String.valueOf(savedRolloutGroups.size()));
}
updateGroupsChart(defineGroupsLayout.getGroupsValidation());
}
if(isNumberOfGroups()) {
if(noOfGroups.isValid() && !noOfGroups.getValue().isEmpty()) {
updateGroupsChart(Integer.parseInt(noOfGroups.getValue()));
} else {
updateGroupsChart(0);
}
}
}
private void updateGroupsChart(final RolloutGroupsValidation validation) {
if(validation == null) {
groupsPieChart.setChartState(null, null);
return;
}
List<Long> targetsPerGroup = validation.getTargetsPerGroup();
if(validation.getTotalTargets() == 0L || targetsPerGroup.isEmpty()) {
groupsPieChart.setChartState(null, null);
} else {
groupsPieChart.setChartState(targetsPerGroup, validation.getTotalTargets());
}
totalTargetsCount = validation.getTotalTargets();
groupsLegendLayout.populateTotalTargets(validation.getTotalTargets());
groupsLegendLayout.populateGroupsLegendByValidation(validation, defineGroupsLayout.getSavedRolloutGroups());
}
private void updateGroupsChart(final List<RolloutGroup> savedGroups, long totalTargetsCount) {
List<Long> targetsPerGroup = savedGroups.stream().map(group -> (long) group.getTotalTargets())
.collect(Collectors.toList());
groupsPieChart.setChartState(targetsPerGroup, totalTargetsCount);
groupsLegendLayout.populateGroupsLegendByGroups(savedGroups);
}
private void updateGroupsChart(final int amountOfGroups) {
if (totalTargetsCount == null || totalTargetsCount == 0L || amountOfGroups == 0) {
groupsPieChart.setChartState(null, null);
groupsLegendLayout.populateGroupsLegendByTargetCounts(Collections.emptyList());
} else {
final List<Long> groups = new ArrayList<>(amountOfGroups);
long leftTargets = totalTargetsCount;
for (int i = 0; i < amountOfGroups; i++) {
float percentage = 1.0F / (amountOfGroups - i);
long targetsInGroup = Math.round(percentage * (double) leftTargets);
leftTargets -= targetsInGroup;
groups.add(targetsInGroup);
}
groupsPieChart.setChartState(groups, totalTargetsCount);
groupsLegendLayout.populateGroupsLegendByTargetCounts(groups);
}
}
private ComboBox createTargetFilterQueryCombo() {
final ComboBox targetFilter = SPUIComponentProvider.getComboBox(null, "", null, ValoTheme.COMBOBOX_SMALL, false,
"", i18n.get("prompt.target.filter"));
targetFilter.setImmediate(true);
targetFilter.setPageLength(7);
targetFilter.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME);
targetFilter.setId(UIComponentIdProvider.ROLLOUT_TARGET_FILTER_COMBO_ID);
targetFilter.setSizeUndefined();
targetFilter.addValueChangeListener(this::onTargetFilterChange);
return targetFilter;
return new ComboBoxBuilder().setValueChangeListener(this::onTargetFilterChange)
.setPrompt(i18n.get("prompt.target.filter")).setId(UIComponentIdProvider.ROLLOUT_TARGET_FILTER_COMBO_ID)
.buildCombBox();
}
private void onTargetFilterChange(final ValueChangeEvent event) {
final String filterQueryString = getTargetFilterQuery();
if (!Strings.isNullOrEmpty(filterQueryString)) {
totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(filterQueryString);
totalTargetsLabel.setValue(getTotalTargetMessage());
totalTargetsLabel.setVisible(true);
} else {
if (Strings.isNullOrEmpty(filterQueryString)) {
totalTargetsCount = 0L;
totalTargetsLabel.setVisible(false);
groupsLegendLayout.populateTotalTargets(null);
defineGroupsLayout.setTargetFilter(null);
} else {
totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(filterQueryString);
groupsLegendLayout.populateTotalTargets(totalTargetsCount);
defineGroupsLayout.setTargetFilter(filterQueryString);
}
onGroupNumberChange(event);
}
private String getTotalTargetMessage() {
return new StringBuilder(i18n.get("label.target.filter.count")).append(totalTargetsCount).toString();
}
private String getTargetPerGroupMessage(final String value) {
return new StringBuilder(i18n.get("label.target.per.group")).append(value).toString();
}
@@ -403,6 +586,16 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
targetFilterQueryCombo.setContainerDataSource(container);
}
private void populateTargetFilterQuery(final Rollout rollout) {
final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement
.findTargetFilterQueryByQuery(new PageRequest(0, 1), rollout.getTargetFilterQuery());
if(filterQueries.getTotalElements() > 0) {
final TargetFilterQuery filterQuery = filterQueries.getContent().get(0);
targetFilterQueryCombo.setValue(filterQuery.getName());
}
}
private static Container createTargetFilterComboContainer() {
final BeanQueryFactory<TargetFilterBeanQuery> targetFilterQF = new BeanQueryFactory<>(
TargetFilterBeanQuery.class);
@@ -412,19 +605,32 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
}
private void editRollout() {
if (rolloutForEdit == null) {
if (rollout == null) {
return;
}
final Rollout updatedRollout = rolloutManagement.updateRollout(entityFactory.rollout()
.update(rolloutForEdit.getId()).name(rolloutName.getValue()).description(description.getValue()));
uiNotification.displaySuccess(i18n.get("message.update.success", new Object[] { updatedRollout.getName() }));
final DistributionSetIdName distributionSetIdName = (DistributionSetIdName) distributionSet.getValue();
RolloutUpdate rolloutUpdate = entityFactory.rollout().update(rollout.getId()).name(rolloutName.getValue())
.description(description.getValue()).set(distributionSetIdName.getId()).actionType(getActionType())
.forcedTime(getForcedTimeStamp());
if (AutoStartOptionGroupLayout.AutoStartOption.AUTO_START.equals(getAutoStartOption())) {
rolloutUpdate.startAt(System.currentTimeMillis());
}
if (AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())) {
rolloutUpdate.startAt(getScheduledStartTime());
}
final Rollout updatedRollout = rolloutManagement.updateRollout(rolloutUpdate);
uiNotification.displaySuccess(i18n.get("message.update.success", updatedRollout.getName()));
eventBus.publish(this, RolloutEvent.UPDATE_ROLLOUT);
}
private boolean duplicateCheckForEdit() {
final String rolloutNameVal = getRolloutName();
if (!rolloutForEdit.getName().equals(rolloutNameVal)
if (!rollout.getName().equals(rolloutNameVal)
&& rolloutManagement.findRolloutByName(rolloutNameVal) != null) {
uiNotification.displayValidationError(i18n.get("message.rollout.duplicate.check", rolloutNameVal));
return false;
@@ -433,10 +639,19 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
}
private long getForcedTimeStamp() {
return (((ActionTypeOptionGroupLayout.ActionTypeOption) actionTypeOptionGroupLayout.getActionTypeOptionGroup()
.getValue()) == ActionTypeOption.AUTO_FORCED)
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
: RepositoryModelConstants.NO_FORCE_TIME;
return ActionTypeOption.AUTO_FORCED.equals(actionTypeOptionGroupLayout.getActionTypeOptionGroup().getValue())
? actionTypeOptionGroupLayout.getForcedTimeDateField().getValue().getTime()
: RepositoryModelConstants.NO_FORCE_TIME;
}
private Long getScheduledStartTime() {
return AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())
? autoStartOptionGroupLayout.getStartAtDateField().getValue().getTime() : null;
}
private AutoStartOptionGroupLayout.AutoStartOption getAutoStartOption() {
return (AutoStartOptionGroupLayout.AutoStartOption) autoStartOptionGroupLayout.getAutoStartOptionGroup()
.getValue();
}
private ActionType getActionType() {
@@ -446,26 +661,40 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private void createRollout() {
final Rollout rolloutToCreate = saveRollout();
uiNotification.displaySuccess(i18n.get("message.save.success", new Object[] { rolloutToCreate.getName() }));
uiNotification.displaySuccess(i18n.get("message.save.success", rolloutToCreate.getName()));
}
private Rollout saveRollout() {
final int amountGroup = Integer.parseInt(noOfGroups.getValue());
final int errorThresoldPercent = getErrorThresoldPercentage(amountGroup);
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder().withDefaults()
.successAction(RolloutGroupSuccessAction.NEXTGROUP, null)
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue())
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, String.valueOf(errorThresoldPercent))
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
final DistributionSetIdName distributionSetIdName = (DistributionSetIdName) distributionSet.getValue();
return rolloutManagement.createRollout(entityFactory.rollout().create().name(rolloutName.getValue())
final int amountGroup = Integer.parseInt(noOfGroups.getValue());
final int errorThresholdPercent = getErrorThresholdPercentage(amountGroup);
final RolloutGroupConditions conditions = new RolloutGroupConditionBuilder()
.successAction(RolloutGroupSuccessAction.NEXTGROUP, null)
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue())
.errorCondition(RolloutGroupErrorCondition.THRESHOLD, String.valueOf(errorThresholdPercent))
.errorAction(RolloutGroupErrorAction.PAUSE, null).build();
final RolloutCreate rolloutCreate = entityFactory.rollout().create().name(rolloutName.getValue())
.description(description.getValue()).set(distributionSetIdName.getId())
.targetFilterQuery(getTargetFilterQuery()).actionType(getActionType()).forcedTime(getForcedTimeStamp()),
amountGroup, conditions);
.targetFilterQuery(getTargetFilterQuery()).actionType(getActionType()).forcedTime(getForcedTimeStamp());
if (AutoStartOptionGroupLayout.AutoStartOption.AUTO_START.equals(getAutoStartOption())) {
rolloutCreate.startAt(System.currentTimeMillis());
}
if (AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED.equals(getAutoStartOption())) {
rolloutCreate.startAt(getScheduledStartTime());
}
if (isNumberOfGroups()) {
return rolloutManagement.createRollout(rolloutCreate, amountGroup, conditions);
} else if (isGroupsDefinition()) {
List<RolloutGroupCreate> groups = defineGroupsLayout.getSavedRolloutGroups();
return rolloutManagement.createRollout(rolloutCreate, groups, conditions);
}
throw new IllegalStateException("Either of the Tabs must be selected");
}
private String getTargetFilterQuery() {
@@ -478,7 +707,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
return null;
}
private int getErrorThresoldPercentage(final int amountGroup) {
private int getErrorThresholdPercentage(final int amountGroup) {
int errorThresoldPercent = Integer.parseInt(errorThreshold.getValue());
if (errorThresholdOptionGroup.getValue().equals(ERRORTHRESOLDOPTIONS.COUNT.getValue())) {
final int groupSize = (int) Math.ceil((double) totalTargetsCount / (double) amountGroup);
@@ -491,7 +720,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
private boolean duplicateCheck() {
if (rolloutManagement.findRolloutByName(getRolloutName()) != null) {
uiNotification.displayValidationError(
i18n.get("message.rollout.duplicate.check", new Object[] { getRolloutName() }));
i18n.get("message.rollout.duplicate.check", getRolloutName()));
return false;
}
return true;
@@ -515,6 +744,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
UIComponentIdProvider.ROLLOUT_ERROR_THRESOLD_ID);
errorField.addValidator(new ThresholdFieldValidator());
errorField.setMaxLength(7);
errorField.setValue(defaultRolloutGroupConditions.getErrorConditionExp());
return errorField;
}
@@ -522,6 +752,7 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
final TextField thresholdField = createIntegerTextField("prompt.tigger.threshold",
UIComponentIdProvider.ROLLOUT_TRIGGER_THRESOLD_ID);
thresholdField.addValidator(new ThresholdFieldValidator());
thresholdField.setValue(defaultRolloutGroupConditions.getSuccessConditionExp());
return thresholdField;
}
@@ -535,23 +766,25 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
}
private void onGroupNumberChange(final ValueChangeEvent event) {
if (event.getProperty().getValue() != null && noOfGroups.isValid()) {
if(editRolloutEnabled) {
return;
}
if (event.getProperty().getValue() != null && noOfGroups.isValid() && totalTargetsCount != null
&& isNumberOfGroups()) {
groupSizeLabel.setValue(getTargetPerGroupMessage(String.valueOf(getGroupSize())));
groupSizeLabel.setVisible(true);
updateGroupsChart(Integer.parseInt(noOfGroups.getValue()));
} else {
groupSizeLabel.setVisible(false);
if(isNumberOfGroups()) {
updateGroupsChart(0);
}
}
}
private ComboBox createDistributionSetCombo() {
final ComboBox dsSet = SPUIComponentProvider.getComboBox(null, "", null, ValoTheme.COMBOBOX_SMALL, false, "",
i18n.get("prompt.distribution.set"));
dsSet.setImmediate(true);
dsSet.setPageLength(7);
dsSet.setItemCaptionPropertyId(SPUILabelDefinitions.VAR_NAME);
dsSet.setId(UIComponentIdProvider.ROLLOUT_DS_ID);
dsSet.setSizeUndefined();
return dsSet;
return new ComboBoxBuilder().setPrompt(i18n.get("prompt.distribution.set"))
.setId(UIComponentIdProvider.ROLLOUT_DS_ID).buildCombBox();
}
private void populateDistributionSet() {
@@ -618,12 +851,12 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
}
class GroupNumberValidator implements Validator {
private static final long serialVersionUID = 9049939751976326550L;
private static final long serialVersionUID = 9043919751971326521L;
@Override
public void validate(final Object value) {
if (value != null) {
new IntegerRangeValidator(i18n.get(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 0, 500), 0, 500)
new IntegerRangeValidator(i18n.get(MESSAGE_ROLLOUT_FIELD_VALUE_RANGE, 1, 500), 1, 500)
.validate(Integer.valueOf(value.toString()));
}
}
@@ -636,31 +869,47 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
* @param rolloutId
* rollout id
*/
private void populateData(final Long rolloutId) {
private void populateData(final Long rolloutId, final boolean copy) {
if (rolloutId == null) {
return;
}
editRolloutEnabled = Boolean.TRUE;
rolloutForEdit = rolloutManagement.findRolloutById(rolloutId);
rolloutName.setValue(rolloutForEdit.getName());
description.setValue(rolloutForEdit.getDescription());
distributionSet.setValue(DistributionSetIdName.generate(rolloutForEdit.getDistributionSet()));
final List<RolloutGroup> rolloutGroups = rolloutForEdit.getRolloutGroups();
setThresholdValues(rolloutGroups);
setActionType(rolloutForEdit);
disableRequiredFieldsOnEdit();
targetFilterQuery.setValue(rolloutForEdit.getTargetFilterQuery());
removeComponent(1, 2);
targetFilterQueryCombo.removeValidator(nullValidator);
addComponent(targetFilterQuery, 1, 2);
targetFilterQuery.addValidator(nullValidator);
rollout = rolloutManagement.findRolloutById(rolloutId);
description.setValue(rollout.getDescription());
distributionSet.setValue(DistributionSetIdName.generate(rollout.getDistributionSet()));
setActionType(rollout);
setAutoStartType(rollout);
totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(rolloutForEdit.getTargetFilterQuery());
totalTargetsLabel.setValue(getTotalTargetMessage());
totalTargetsLabel.setVisible(true);
if (copy) {
rolloutName.setValue(i18n.get("textfield.rollout.copied.name", rollout.getName()));
populateTargetFilterQuery(rollout);
window.setOrginaleValues();
defineGroupsLayout.populateByRollout(rollout);
groupsDefinitionTabs.setSelectedTab(1);
window.clearOriginalValues();
} else {
editRolloutEnabled = true;
if (rollout.getStatus() != Rollout.RolloutStatus.READY) {
disableRequiredFieldsOnEdit();
}
rolloutName.setValue(rollout.getName());
groupsDefinitionTabs.setVisible(false);
targetFilterQuery.setValue(rollout.getTargetFilterQuery());
removeComponent(1, 2);
targetFilterQueryCombo.removeValidator(nullValidator);
addComponent(targetFilterQuery, 1, 2);
targetFilterQuery.addValidator(nullValidator);
window.setOrginaleValues();
updateGroupsChart(rollout.getRolloutGroups(), rollout.getTotalTargets());
}
totalTargetsCount = targetManagement.countTargetByTargetFilterQuery(rollout.getTargetFilterQuery());
groupsLegendLayout.populateTotalTargets(totalTargetsCount);
}
private void disableRequiredFieldsOnEdit() {
@@ -668,9 +917,11 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
distributionSet.setEnabled(false);
errorThreshold.setEnabled(false);
triggerThreshold.setEnabled(false);
actionTypeOptionGroupLayout.getActionTypeOptionGroup().setEnabled(false);
errorThresholdOptionGroup.setEnabled(false);
actionTypeOptionGroupLayout.getActionTypeOptionGroup().setEnabled(false);
actionTypeOptionGroupLayout.addStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT);
autoStartOptionGroupLayout.getAutoStartOptionGroup().setEnabled(false);
autoStartOptionGroupLayout.addStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT);
}
private void enableFields() {
@@ -679,6 +930,8 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
triggerThreshold.setEnabled(true);
actionTypeOptionGroupLayout.getActionTypeOptionGroup().setEnabled(true);
actionTypeOptionGroupLayout.removeStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT);
autoStartOptionGroupLayout.getAutoStartOptionGroup().setEnabled(true);
autoStartOptionGroupLayout.removeStyleName(SPUIStyleDefinitions.DISABLE_ACTION_TYPE_LAYOUT);
noOfGroups.setEnabled(true);
targetFilterQueryCombo.setEnabled(true);
errorThresholdOptionGroup.setEnabled(true);
@@ -697,39 +950,22 @@ public class AddUpdateRolloutWindowLayout extends GridLayout {
}
}
/**
* @param rolloutGroups
*/
private void setThresholdValues(final List<RolloutGroup> rolloutGroups) {
if (rolloutGroups != null && !rolloutGroups.isEmpty()) {
errorThreshold.setValue(rolloutGroups.get(0).getErrorConditionExp());
triggerThreshold.setValue(rolloutGroups.get(0).getSuccessConditionExp());
noOfGroups.setValue(String.valueOf(rolloutGroups.size()));
private void setAutoStartType(final Rollout rollout) {
if (rollout.getStartAt() == null) {
autoStartOptionGroupLayout.getAutoStartOptionGroup()
.setValue(AutoStartOptionGroupLayout.AutoStartOption.MANUAL);
} else if (rollout.getStartAt() < System.currentTimeMillis()) {
autoStartOptionGroupLayout.getAutoStartOptionGroup()
.setValue(AutoStartOptionGroupLayout.AutoStartOption.AUTO_START);
autoStartOptionGroupLayout.getStartAtDateField().setValue(new Date(rollout.getStartAt()));
} else {
errorThreshold.setValue("0");
triggerThreshold.setValue("0");
noOfGroups.setValue("0");
autoStartOptionGroupLayout.getAutoStartOptionGroup()
.setValue(AutoStartOptionGroupLayout.AutoStartOption.SCHEDULED);
autoStartOptionGroupLayout.getStartAtDateField().setValue(new Date(rollout.getStartAt()));
}
}
enum SAVESTARTOPTIONS {
SAVE("Save"), START("Start");
String value;
SAVESTARTOPTIONS(final String val) {
this.value = val;
}
/**
* @return the value
*/
public String getValue() {
return value;
}
}
enum ERRORTHRESOLDOPTIONS {
private enum ERRORTHRESOLDOPTIONS {
PERCENT("%"), COUNT("Count");
String value;

View File

@@ -0,0 +1,165 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.rollout.rollout;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.TimeZone;
import org.eclipse.hawkbit.ui.utils.I18N;
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroup;
import org.vaadin.hene.flexibleoptiongroup.FlexibleOptionGroupItemComponent;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.server.FontAwesome;
import com.vaadin.shared.ui.datefield.Resolution;
import com.vaadin.ui.DateField;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.themes.ValoTheme;
/**
* Rollout start types options layout
*/
public class AutoStartOptionGroupLayout extends HorizontalLayout {
private static final long serialVersionUID = -8460459258964093525L;
private static final String STYLE_DIST_WINDOW_AUTO_START = "dist-window-actiontype";
private final I18N i18n;
private FlexibleOptionGroup autoStartOptionGroup;
private DateField startAtDateField;
/**
* Instantiates the auto start options layout
*
* @param i18n
* the internationalization helper
*/
public AutoStartOptionGroupLayout(final I18N i18n) {
this.i18n = i18n;
createOptionGroup();
addValueChangeListener();
setStyleName("dist-window-actiontype-horz-layout");
setSizeUndefined();
}
private void addValueChangeListener() {
autoStartOptionGroup.addValueChangeListener(new ValueChangeListener() {
private static final long serialVersionUID = 1L;
@Override
public void valueChange(final ValueChangeEvent event) {
if (event.getProperty().getValue().equals(AutoStartOption.SCHEDULED)) {
startAtDateField.setEnabled(true);
startAtDateField.setRequired(true);
} else {
startAtDateField.setEnabled(false);
startAtDateField.setRequired(false);
}
}
});
}
private void createOptionGroup() {
autoStartOptionGroup = new FlexibleOptionGroup();
autoStartOptionGroup.addItem(AutoStartOption.MANUAL);
autoStartOptionGroup.addItem(AutoStartOption.AUTO_START);
autoStartOptionGroup.addItem(AutoStartOption.SCHEDULED);
selectDefaultOption();
final FlexibleOptionGroupItemComponent manualItem = autoStartOptionGroup
.getItemComponent(AutoStartOption.MANUAL);
manualItem.setStyleName(STYLE_DIST_WINDOW_AUTO_START);
// set Id for Forced radio button.
manualItem.setId(UIComponentIdProvider.ROLLOUT_START_MANUAL_ID);
addComponent(manualItem);
final Label manualLabel = new Label();
manualLabel.setStyleName("statusIconPending");
manualLabel.setIcon(FontAwesome.HAND_PAPER_O);
manualLabel.setCaption(i18n.get("caption.rollout.start.manual"));
manualLabel.setDescription(i18n.get("caption.rollout.start.manual.desc"));
manualLabel.setStyleName("padding-right-style");
addComponent(manualLabel);
final FlexibleOptionGroupItemComponent autoStartItem = autoStartOptionGroup
.getItemComponent(AutoStartOption.AUTO_START);
autoStartItem.setId(UIComponentIdProvider.ROLLOUT_START_AUTO_ID);
autoStartItem.setStyleName(STYLE_DIST_WINDOW_AUTO_START);
addComponent(autoStartItem);
final Label autoStartLabel = new Label();
autoStartLabel.setSizeFull();
autoStartLabel.setIcon(FontAwesome.PLAY);
autoStartLabel.setCaption(i18n.get("caption.rollout.start.auto"));
autoStartLabel.setDescription(i18n.get("caption.rollout.start.auto.desc"));
autoStartLabel.setStyleName("padding-right-style");
addComponent(autoStartLabel);
final FlexibleOptionGroupItemComponent scheduledItem = autoStartOptionGroup
.getItemComponent(AutoStartOption.SCHEDULED);
scheduledItem.setStyleName(STYLE_DIST_WINDOW_AUTO_START);
// setted Id for Time Forced radio button.
scheduledItem.setId(UIComponentIdProvider.ROLLOUT_START_SCHEDULED_ID);
addComponent(scheduledItem);
final Label scheduledLabel = new Label();
scheduledLabel.setStyleName("statusIconPending");
scheduledLabel.setIcon(FontAwesome.CLOCK_O);
scheduledLabel.setCaption(i18n.get("caption.rollout.start.scheduled"));
scheduledLabel.setDescription(i18n.get("caption.rollout.start.scheduled.desc"));
scheduledLabel.setStyleName(STYLE_DIST_WINDOW_AUTO_START);
addComponent(scheduledLabel);
startAtDateField = new DateField();
startAtDateField.setInvalidAllowed(false);
startAtDateField.setInvalidCommitted(false);
startAtDateField.setEnabled(false);
startAtDateField.setStyleName("dist-window-forcedtime");
final TimeZone tz = SPDateTimeUtil.getBrowserTimeZone();
startAtDateField.setValue(
Date.from(LocalDateTime.now().plusMinutes(30).atZone(SPDateTimeUtil.getTimeZoneId(tz)).toInstant()));
startAtDateField.setImmediate(true);
startAtDateField.setTimeZone(tz);
startAtDateField.setLocale(i18n.getLocale());
startAtDateField.setResolution(Resolution.MINUTE);
startAtDateField.addStyleName(ValoTheme.DATEFIELD_SMALL);
addComponent(startAtDateField);
}
/**
* To Set Default option for save.
*/
public void selectDefaultOption() {
autoStartOptionGroup.select(AutoStartOption.MANUAL);
}
/**
* Rollout start options
*/
public enum AutoStartOption {
MANUAL, AUTO_START, SCHEDULED;
}
public FlexibleOptionGroup getAutoStartOptionGroup() {
return autoStartOptionGroup;
}
public DateField getStartAtDateField() {
return startAtDateField;
}
}

View File

@@ -0,0 +1,617 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.rollout.rollout;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import com.vaadin.ui.UI;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupConditionBuilder;
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.ui.common.builder.ComboBoxBuilder;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.common.builder.TextAreaBuilder;
import org.eclipse.hawkbit.ui.common.builder.TextFieldBuilder;
import org.eclipse.hawkbit.ui.components.SPUIComponentProvider;
import org.eclipse.hawkbit.ui.decorators.SPUIButtonStyleNoBorderWithIcon;
import org.eclipse.hawkbit.ui.filtermanagement.TargetFilterBeanQuery;
import org.eclipse.hawkbit.ui.utils.HawkbitCommonUtil;
import org.eclipse.hawkbit.ui.utils.I18N;
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
import org.eclipse.hawkbit.ui.utils.SPUILabelDefinitions;
import org.eclipse.hawkbit.ui.utils.UIComponentIdProvider;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.util.concurrent.ListenableFuture;
import org.vaadin.addons.lazyquerycontainer.BeanQueryFactory;
import org.vaadin.addons.lazyquerycontainer.LazyQueryContainer;
import org.vaadin.addons.lazyquerycontainer.LazyQueryDefinition;
import com.vaadin.data.Container;
import com.vaadin.data.Item;
import com.vaadin.data.Validator;
import com.vaadin.data.util.converter.StringToFloatConverter;
import com.vaadin.data.util.converter.StringToIntegerConverter;
import com.vaadin.data.validator.FloatRangeValidator;
import com.vaadin.data.validator.IntegerRangeValidator;
import com.vaadin.data.validator.StringLengthValidator;
import com.vaadin.server.FontAwesome;
import com.vaadin.server.UserError;
import com.vaadin.ui.Button;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;
/**
* Define groups for a Rollout
*/
public class DefineGroupsLayout extends GridLayout {
private static final long serialVersionUID = 2939193468001472916L;
private I18N i18n;
private transient EntityFactory entityFactory;
private transient RolloutManagement rolloutManagement;
private transient TargetFilterQueryManagement targetFilterQueryManagement;
private String defaultTriggerThreshold;
private String defaultErrorThreshold;
private String targetFilter;
private transient List<GroupRow> groupRows;
private int groupsCount;
private transient List<RolloutGroupCreate> savedRolloutGroups;
private transient ValidationListener validationListener;
private ValidationStatus validationStatus = ValidationStatus.VALID;
private transient RolloutGroupsValidation groupsValidation;
private transient ListenableFuture<RolloutGroupsValidation> runningValidation;
private boolean validationRequested;
DefineGroupsLayout(I18N i18n, EntityFactory entityFactory, RolloutManagement rolloutManagement,
TargetFilterQueryManagement targetFilterQueryManagement) {
this.i18n = i18n;
this.entityFactory = entityFactory;
this.rolloutManagement = rolloutManagement;
this.targetFilterQueryManagement = targetFilterQueryManagement;
groupRows = new ArrayList<>(10);
setSizeUndefined();
buildLayout();
}
private void buildLayout() {
setSpacing(Boolean.TRUE);
setSizeUndefined();
setRows(3);
setColumns(6);
setStyleName("marginTop");
addComponent(getLabel("caption.rollout.group.definition.desc"), 0, 0, 5, 0);
final int headerRow = 1;
addComponent(getLabel("header.name"), 0, headerRow);
addComponent(getLabel("header.target.filter.query"), 1, headerRow);
addComponent(getLabel("header.target.percentage"), 2, headerRow);
addComponent(getLabel("header.rolloutgroup.threshold"), 3, headerRow);
addComponent(getLabel("header.rolloutgroup.threshold.error"), 4, headerRow);
addComponent(createAddButton(), 0, 2, 5, 2);
}
/**
* @param targetFilter
* the target filter which is required for verification
*/
public void setTargetFilter(final String targetFilter) {
this.targetFilter = targetFilter;
updateValidation();
}
private int addRow() {
final int insertIndex = getRows() - 1;
insertRow(insertIndex);
return insertIndex;
}
private Label getLabel(final String key) {
return new LabelBuilder().name(i18n.get(key)).buildLabel();
}
private Button createAddButton() {
Button button = SPUIComponentProvider.getButton(UIComponentIdProvider.ROLLOUT_GROUP_ADD_ID,
i18n.get("button.rollout.add.group"), "", "", true, FontAwesome.PLUS,
SPUIButtonStyleNoBorderWithIcon.class);
button.setSizeUndefined();
button.addStyleName("default-color");
button.setEnabled(true);
button.setVisible(true);
button.addClickListener(event -> addGroupRow());
return button;
}
private GroupRow addGroupRow() {
int rowIndex = addRow();
GroupRow groupRow = new GroupRow();
groupRow.addToGridRow(this, rowIndex);
groupRows.add(groupRow);
updateValidation();
return groupRow;
}
public List<RolloutGroupCreate> getSavedRolloutGroups() {
return savedRolloutGroups;
}
/**
* @return the validation instance if was already validated
*/
public RolloutGroupsValidation getGroupsValidation() {
return groupsValidation;
}
private void removeAllRows() {
for (int i = getRows() - 2; i > 1; i--) {
removeRow(i);
}
groupRows.clear();
}
public void setDefaultTriggerThreshold(final String defaultTriggerThreshold) {
this.defaultTriggerThreshold = defaultTriggerThreshold;
}
public void setDefaultErrorThreshold(final String defaultErrorThreshold) {
this.defaultErrorThreshold = defaultErrorThreshold;
}
/**
* Reset the field values.
*/
public void resetComponents() {
validationStatus = ValidationStatus.VALID;
groupsCount = 0;
removeAllRows();
addGroupRow();
}
/**
* Populate groups by rollout
*
* @param rollout
* the rollout
*/
public void populateByRollout(final Rollout rollout) {
if (rollout == null) {
return;
}
removeAllRows();
final List<RolloutGroup> groups = rollout.getRolloutGroups();
for (final RolloutGroup group : groups) {
final GroupRow groupRow = addGroupRow();
groupRow.populateByGroup(group);
}
}
/**
* @return whether the groups definition form is valid
*/
public boolean isValid() {
if (groupRows.isEmpty() || validationStatus != ValidationStatus.VALID) {
return false;
}
return groupRows.stream().allMatch(GroupRow::isValid);
}
private void updateValidation() {
validationStatus = ValidationStatus.VALID;
if (isValid()) {
setValidationStatus(ValidationStatus.LOADING);
savedRolloutGroups = getGroupsFromRows();
validateRemainingTargets();
} else {
resetRemainingTargetsError();
setValidationStatus(ValidationStatus.INVALID);
}
}
private void setValidationStatus(final ValidationStatus status) {
validationStatus = status;
if (validationListener != null) {
validationListener.validation(status);
}
}
private void resetRemainingTargetsError() {
groupRows.forEach(GroupRow::hideLastGroupError);
}
private void validateRemainingTargets() {
resetRemainingTargetsError();
if (targetFilter == null || (runningValidation != null && !runningValidation.isDone())) {
validationRequested = true;
return;
}
validationRequested = false;
final UI ui = UI.getCurrent();
runningValidation = rolloutManagement
.validateTargetsInGroups(savedRolloutGroups, targetFilter, System.currentTimeMillis());
runningValidation.addCallback(validation -> ui.access(() -> this.setGroupsValidation(validation)),
throwable -> ui.access(() -> this.setGroupsValidation(null)));
}
private void setGroupsValidation(RolloutGroupsValidation validation) {
groupsValidation = validation;
if(validationRequested) {
validateRemainingTargets();
return;
}
final GroupRow lastRow = groupRows.get(groupRows.size() - 1);
if (groupsValidation != null && groupsValidation.isValid() && validationStatus != ValidationStatus.INVALID) {
lastRow.hideLastGroupError();
setValidationStatus(ValidationStatus.VALID);
} else {
lastRow.markWithLastGroupError();
setValidationStatus(ValidationStatus.INVALID);
}
}
private List<RolloutGroupCreate> getGroupsFromRows() {
return groupRows.stream().map(GroupRow::getGroupEntity).collect(Collectors.toList());
}
public void setValidationListener(final ValidationListener validationListener) {
this.validationListener = validationListener;
}
/**
* Status of the groups validation
*/
public enum ValidationStatus {
VALID,
INVALID,
LOADING
}
/**
* Implement the interface and set the instance with setValidationListener
* to receive updates for any changes within the group rows.
*/
@FunctionalInterface
public interface ValidationListener {
/**
* Is called after user input
*
* @param isValid
* whether the input of the group rows is valid
*/
void validation(ValidationStatus isValid);
}
private class GroupRow {
private TextField groupName;
private ComboBox targetFilterQueryCombo;
private TextArea targetFilterQuery;
private TextField targetPercentage;
private TextField triggerThreshold;
private TextField errorThreshold;
private HorizontalLayout optionsLayout;
private boolean populated;
private boolean initialized;
public GroupRow() {
init();
}
private void init() {
groupsCount += 1;
groupName = createTextField("textfield.name", UIComponentIdProvider.ROLLOUT_GROUP_LIST_GRID_ID);
groupName.setValue(i18n.get("textfield.rollout.group.default.name", groupsCount));
groupName.setStyleName("rollout-group-name");
targetFilterQueryCombo = createTargetFilterQueryCombo();
populateTargetFilterQuery();
targetFilterQuery = createTargetFilterQuery();
targetPercentage = createPercentageWithDecimalsField("textfield.target.percentage",
UIComponentIdProvider.ROLLOUT_GROUP_TARGET_PERC_ID);
targetPercentage.setValue("100");
triggerThreshold = createPercentageField("prompt.tigger.threshold",
UIComponentIdProvider.ROLLOUT_TRIGGER_THRESOLD_ID);
triggerThreshold.setValue(defaultTriggerThreshold);
errorThreshold = createPercentageField("prompt.error.threshold",
UIComponentIdProvider.ROLLOUT_ERROR_THRESOLD_ID);
errorThreshold.setValue(defaultErrorThreshold);
optionsLayout = new HorizontalLayout();
optionsLayout.addComponent(createRemoveButton());
initialized = true;
}
private TextField createTextField(final String in18Key, final String id) {
final TextField textField = new TextFieldBuilder().prompt(i18n.get(in18Key)).immediate(true).id(id)
.buildTextComponent();
textField.setSizeUndefined();
textField.addValidator(
new StringLengthValidator(i18n.get("message.rollout.group.name.invalid"), 1, 64, false));
textField.addValueChangeListener(event -> valueChanged());
return textField;
}
private TextField createPercentageField(final String in18Key, final String id) {
final TextField textField = new TextFieldBuilder().prompt(i18n.get(in18Key)).immediate(true).id(id)
.buildTextComponent();
textField.setWidth(80, Unit.PIXELS);
textField.setNullRepresentation("");
textField.setConverter(new StringToIntegerConverter());
textField.addValidator(this::validateMandatoryPercentage);
textField.addValueChangeListener(event -> valueChanged());
return textField;
}
private TextField createPercentageWithDecimalsField(final String in18Key, final String id) {
final TextField textField = createPercentageField(in18Key, id);
textField.setConverter(new StringToFloatConverter());
return textField;
}
private void removeGroupRow(final GroupRow groupRow) {
groupRows.remove(groupRow);
updateValidation();
}
private void validateMandatoryPercentage(final Object value) {
if (value != null) {
final String message = i18n.get("message.rollout.field.value.range", 0, 100);
if (value instanceof Float) {
new FloatRangeValidator(message, 0F, 100F).validate(value);
}
if (value instanceof Integer) {
new IntegerRangeValidator(message, 0, 100).validate(value);
}
} else {
throw new Validator.EmptyValueException(i18n.get("message.enter.number"));
}
}
private void valueChanged() {
if (initialized) {
updateValidation();
}
}
private ComboBox createTargetFilterQueryCombo() {
return new ComboBoxBuilder().setId(UIComponentIdProvider.ROLLOUT_TARGET_FILTER_COMBO_ID)
.setPrompt(i18n.get("prompt.target.filter")).setValueChangeListener(event -> valueChanged())
.buildCombBox();
}
private TextArea createTargetFilterQuery() {
final TextArea filterField = new TextAreaBuilder().style("text-area-style")
.id(UIComponentIdProvider.ROLLOUT_TARGET_FILTER_QUERY_FIELD)
.maxLengthAllowed(SPUILabelDefinitions.TARGET_FILTER_QUERY_TEXT_FIELD_LENGTH).buildTextComponent();
filterField.setNullRepresentation(StringUtils.EMPTY);
filterField.setEnabled(false);
filterField.setSizeUndefined();
return filterField;
}
private void populateTargetFilterQuery() {
final Container container = createTargetFilterComboContainer();
targetFilterQueryCombo.setContainerDataSource(container);
}
private void populateTargetFilterQuery(final RolloutGroup group) {
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
targetFilterQueryCombo.setValue(null);
} else {
final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement
.findTargetFilterQueryByQuery(new PageRequest(0, 1), group.getTargetFilterQuery());
if (filterQueries.getTotalElements() > 0) {
final TargetFilterQuery filterQuery = filterQueries.getContent().get(0);
targetFilterQueryCombo.setValue(filterQuery.getName());
}
}
}
private Container createTargetFilterComboContainer() {
final BeanQueryFactory<TargetFilterBeanQuery> targetFilterQF = new BeanQueryFactory<>(
TargetFilterBeanQuery.class);
return new LazyQueryContainer(
new LazyQueryDefinition(true, SPUIDefinitions.PAGE_SIZE, SPUILabelDefinitions.VAR_NAME),
targetFilterQF);
}
private Button createRemoveButton() {
Button button = SPUIComponentProvider.getButton(UIComponentIdProvider.ROLLOUT_GROUP_REMOVE_ID, "", "", "",
true, FontAwesome.MINUS, SPUIButtonStyleNoBorderWithIcon.class);
button.setSizeUndefined();
button.addStyleName("default-color");
button.setEnabled(true);
button.setVisible(true);
button.addClickListener(event -> onRemove());
return button;
}
private void onRemove() {
int index = findRowIndexFor(groupName, 0);
if (index != -1) {
removeRow(index);
}
removeGroupRow(this);
}
private int findRowIndexFor(final Component component, final int col) {
final int rows = getRows();
for (int i = 0; i < rows; i++) {
Component rowComponent = getComponent(col, i);
if (component.equals(rowComponent)) {
return i;
}
}
return -1;
}
private String getTargetFilterQuery() {
if (null != targetFilterQueryCombo.getValue()
&& HawkbitCommonUtil.trimAndNullIfEmpty((String) targetFilterQueryCombo.getValue()) != null) {
final Item filterItem = targetFilterQueryCombo.getContainerDataSource()
.getItem(targetFilterQueryCombo.getValue());
return (String) filterItem.getItemProperty("query").getValue();
}
return null;
}
/**
* Adds this group row to a grid layout
*
* @param layout
* the grid layout
* @param rowIndex
* the row of the grid layout
*/
public void addToGridRow(GridLayout layout, int rowIndex) {
layout.addComponent(groupName, 0, rowIndex);
if (populated) {
layout.addComponent(targetFilterQuery, 1, rowIndex);
} else {
layout.addComponent(targetFilterQueryCombo, 1, rowIndex);
}
layout.addComponent(targetPercentage, 2, rowIndex);
layout.addComponent(triggerThreshold, 3, rowIndex);
layout.addComponent(errorThreshold, 4, rowIndex);
layout.addComponent(optionsLayout, 5, rowIndex);
}
/**
* Builds a group definition from this group row
*
* @return the RolloutGroupCreate definition
*/
public RolloutGroupCreate getGroupEntity() {
final RolloutGroupConditionBuilder conditionBuilder = new RolloutGroupConditionBuilder()
.successAction(RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP, null)
.successCondition(RolloutGroup.RolloutGroupSuccessCondition.THRESHOLD, triggerThreshold.getValue());
if (StringUtils.isNotEmpty(errorThreshold.getValue())) {
conditionBuilder
.errorCondition(RolloutGroup.RolloutGroupErrorCondition.THRESHOLD, errorThreshold.getValue())
.errorAction(RolloutGroup.RolloutGroupErrorAction.PAUSE, null);
}
String percentageString = targetPercentage.getValue().replace(",", ".");
Float percentage = Float.parseFloat(percentageString);
return entityFactory.rolloutGroup().create().name(groupName.getValue()).description(groupName.getValue())
.targetFilterQuery(getTargetFilterQuery()).targetPercentage(percentage)
.conditions(conditionBuilder.build());
}
/**
* Populates the row with the data from the provided groups.
*
* @param group
* the data source
*/
public void populateByGroup(final RolloutGroup group) {
groupName.setValue(group.getName());
targetFilterQuery.setValue(group.getTargetFilterQuery());
populateTargetFilterQuery(group);
targetPercentage.setValue(String.format("%.2f", group.getTargetPercentage()));
triggerThreshold.setValue(group.getSuccessConditionExp());
errorThreshold.setValue(group.getErrorConditionExp());
populated = true;
}
/**
* @return whether the data entered in this row is valid
*/
public boolean isValid() {
return StringUtils.isNotEmpty(groupName.getValue()) && targetPercentage.isValid()
&& triggerThreshold.isValid() && errorThreshold.isValid();
}
/**
* Displays an error for the row
*/
public void markWithLastGroupError() {
targetPercentage.setComponentError(new UserError(i18n.get("message.rollout.remaining.targets.error")));
}
/**
* Hides an error of the row
*/
public void hideLastGroupError() {
targetPercentage.setComponentError(null);
}
}
}

View File

@@ -0,0 +1,245 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.ui.rollout.rollout;
import com.vaadin.ui.Component;
import com.vaadin.ui.Label;
import com.vaadin.ui.VerticalLayout;
import org.eclipse.hawkbit.repository.builder.RolloutGroupCreate;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroupsValidation;
import org.eclipse.hawkbit.ui.common.builder.LabelBuilder;
import org.eclipse.hawkbit.ui.utils.I18N;
import java.util.Collections;
import java.util.List;
/**
* Displays a legend for the Groups of a Rollout with the count of targets in
* each group. On top of the group list, the total targets are displayed. If
* there are unassigned targets, they get display on top of the groups list.
*/
public class GroupsLegendLayout extends VerticalLayout {
private static final long serialVersionUID = 5483206203739308677L;
private final I18N i18n;
private Label totalTargetsLabel;
private Label loadingLabel;
private Label unassignedTargetsLabel;
private VerticalLayout groupsLegend;
/**
* Initializes a new GroupsLegendLayout
*/
GroupsLegendLayout(final I18N i18n) {
this.i18n = i18n;
init();
}
private void init() {
totalTargetsLabel = createTotalTargetsLabel();
unassignedTargetsLabel = createUnassignedTargetsLabel();
loadingLabel = createLoadingLabel();
loadingLabel.setVisible(false);
groupsLegend = new VerticalLayout();
groupsLegend.setStyleName("groups-legend");
addComponent(totalTargetsLabel);
addComponent(loadingLabel);
addComponent(unassignedTargetsLabel);
addComponent(groupsLegend);
for (int i = 0; i < 8; i++) {
groupsLegend.addComponent(createGroupTargetsLabel());
}
}
/**
* Resets the display of the legend and total targets.
*/
public void reset() {
totalTargetsLabel.setVisible(false);
populateGroupsLegendByTargetCounts(Collections.emptyList());
}
private static Label createTotalTargetsLabel() {
final Label label = new LabelBuilder().visible(false).name("").buildLabel();
label.addStyleName("rollout-target-count-title");
label.setImmediate(true);
label.setSizeUndefined();
return label;
}
private Label createLoadingLabel() {
final Label label = new LabelBuilder().visible(false).name("").buildLabel();
label.addStyleName("rollout-target-count-loading");
label.setImmediate(true);
label.setSizeUndefined();
label.setValue(i18n.get("label.rollout.calculating"));
return label;
}
private static Label createUnassignedTargetsLabel() {
final Label label = new LabelBuilder().visible(false).name("").buildLabel();
label.addStyleName("rollout-group-unassigned");
label.setSizeUndefined();
return label;
}
private static Label createGroupTargetsLabel() {
final Label label = new LabelBuilder().visible(false).name("").buildLabel();
label.addStyleName("rollout-group-count");
label.setSizeUndefined();
return label;
}
private String getTotalTargetMessage(final long totalTargetsCount) {
return i18n.get("label.target.filter.count") + totalTargetsCount;
}
/**
* Display an indication that the legend is being calculated.
* When the loading process is done one of the populate methods should be called.
*/
public void displayLoading() {
populateGroupsLegendByTargetCounts(Collections.emptyList());
loadingLabel.setVisible(true);
}
/**
* Displays the total targets or hides the label when null is supplied.
*
* @param totalTargets
* null to hide the label or a count to be displayed as total
* targets message
*/
public void populateTotalTargets(Long totalTargets) {
if (totalTargets == null) {
totalTargetsLabel.setVisible(false);
} else {
totalTargetsLabel.setVisible(true);
totalTargetsLabel.setValue(getTotalTargetMessage(totalTargets));
}
}
/**
* Populates the legend based on a list of anonymous groups. They can't have
* unassigned targets.
*
* @param targetsPerGroup
* list of target counts
*/
public void populateGroupsLegendByTargetCounts(final List<Long> targetsPerGroup) {
loadingLabel.setVisible(false);
for (int i = 0; i < groupsLegend.getComponentCount(); i++) {
final Component component = groupsLegend.getComponent(i);
final Label label = (Label) component;
if (targetsPerGroup.size() > i) {
final Long targetCount = targetsPerGroup.get(i);
label.setValue(
getTargetsInGroupMessage(targetCount, i18n.get("textfield.rollout.group.default.name", i + 1)));
label.setVisible(true);
} else {
label.setValue("");
label.setVisible(false);
}
}
unassignedTargetsLabel.setValue("");
unassignedTargetsLabel.setVisible(false);
}
/**
* Populates the legend based on a groups validation and a list of groups
* that is used for resolving their names. Positions of the groups in the
* groups list and the validation need to be in correct order. Can have
* unassigned targets that are displayed on top of the groups list which
* results in one group less to be displayed
*
* @param validation
* A rollout validation object that contains a list of target
* counts and the total targets
* @param groups
* List of groups with their name
*/
public void populateGroupsLegendByValidation(final RolloutGroupsValidation validation, final List<RolloutGroupCreate> groups) {
loadingLabel.setVisible(false);
if (validation == null) {
return;
}
List<Long> targetsPerGroup = validation.getTargetsPerGroup();
final long unassigned = validation.getTotalTargets() - validation.getTargetsInGroups();
final int labelsToUpdate = (unassigned > 0) ? (groupsLegend.getComponentCount() - 1)
: groupsLegend.getComponentCount();
for (int i = 0; i < groupsLegend.getComponentCount(); i++) {
final Component component = groupsLegend.getComponent(i);
final Label label = (Label) component;
if (targetsPerGroup.size() > i && groups.size() > i && labelsToUpdate > i) {
final Long targetCount = targetsPerGroup.get(i);
final String groupName = groups.get(i).build().getName();
label.setValue(getTargetsInGroupMessage(targetCount, groupName));
label.setVisible(true);
} else {
label.setValue("");
label.setVisible(false);
}
}
if (unassigned > 0) {
unassignedTargetsLabel.setValue(getTargetsInGroupMessage(unassigned, "Unassigned"));
unassignedTargetsLabel.setVisible(true);
} else {
unassignedTargetsLabel.setValue("");
unassignedTargetsLabel.setVisible(false);
}
}
/**
* Populates the legend based on a list of groups.
*
* @param groups
* List of groups with their name
*/
public void populateGroupsLegendByGroups(final List<RolloutGroup> groups) {
loadingLabel.setVisible(false);
for (int i = 0; i < groupsLegend.getComponentCount(); i++) {
final Component component = groupsLegend.getComponent(i);
final Label label = (Label) component;
if (groups.size() > i) {
final int targetCount = groups.get(i).getTotalTargets();
final String groupName = groups.get(i).getName();
label.setValue(getTargetsInGroupMessage((long) targetCount, groupName));
label.setVisible(true);
} else {
label.setValue("");
label.setVisible(false);
}
}
}
private String getTargetsInGroupMessage(final Long targets, final String groupName) {
return i18n.get("label.rollout.targets.in.group", targets, groupName);
}
}

View File

@@ -37,6 +37,7 @@ import java.util.Set;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
@@ -87,6 +88,8 @@ public class RolloutListGrid extends AbstractGrid {
private static final String UPDATE_OPTION = "Update";
private static final String COPY_OPTION = "Copy";
private static final String PAUSE_OPTION = "Pause";
private static final String RUN_OPTION = "Run";
@@ -130,11 +133,12 @@ public class RolloutListGrid extends AbstractGrid {
RolloutListGrid(final I18N i18n, final UIEventBus eventBus, final RolloutManagement rolloutManagement,
final UINotification uiNotification, final RolloutUIState rolloutUIState,
final SpPermissionChecker permissionChecker, final TargetManagement targetManagement,
final EntityFactory entityFactory, final UiProperties uiProperties) {
final EntityFactory entityFactory, final UiProperties uiProperties,
final TargetFilterQueryManagement targetFilterQueryManagement) {
super(i18n, eventBus, permissionChecker);
this.rolloutManagement = rolloutManagement;
this.addUpdateRolloutWindow = new AddUpdateRolloutWindowLayout(rolloutManagement, targetManagement,
uiNotification, uiProperties, entityFactory, i18n, eventBus);
uiNotification, uiProperties, entityFactory, i18n, eventBus, targetFilterQueryManagement);
this.uiNotification = uiNotification;
this.rolloutUIState = rolloutUIState;
}
@@ -230,6 +234,10 @@ public class RolloutListGrid extends AbstractGrid {
rolloutGridContainer.addContainerProperty(UPDATE_OPTION, String.class, FontAwesome.EDIT.getHtml(), false,
false);
}
if (permissionChecker.hasRolloutCreatePermission()) {
rolloutGridContainer.addContainerProperty(COPY_OPTION, String.class, FontAwesome.COPY.getHtml(), false,
false);
}
}
@Override
@@ -262,6 +270,10 @@ public class RolloutListGrid extends AbstractGrid {
} else {
getColumn(PAUSE_OPTION).setMaximumWidth(60);
}
if (permissionChecker.hasRolloutCreatePermission()) {
getColumn(COPY_OPTION).setMinimumWidth(25);
getColumn(COPY_OPTION).setMaximumWidth(25);
}
getColumn(VAR_TOTAL_TARGETS_COUNT_STATUS).setMinimumWidth(280);
}
@@ -289,10 +301,13 @@ public class RolloutListGrid extends AbstractGrid {
if (permissionChecker.hasRolloutUpdatePermission()) {
getColumn(UPDATE_OPTION).setHeaderCaption(i18n.get("header.action.update"));
}
if (permissionChecker.hasRolloutCreatePermission()) {
getColumn(COPY_OPTION).setHeaderCaption(i18n.get("header.action.copy"));
}
HeaderCell join;
if (permissionChecker.hasRolloutUpdatePermission()) {
join = getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION, UPDATE_OPTION);
join = getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION, UPDATE_OPTION, COPY_OPTION);
} else {
join = getDefaultHeaderRow().join(RUN_OPTION, PAUSE_OPTION);
}
@@ -323,6 +338,9 @@ public class RolloutListGrid extends AbstractGrid {
if (permissionChecker.hasRolloutUpdatePermission()) {
columnList.add(UPDATE_OPTION);
}
if (permissionChecker.hasRolloutCreatePermission()) {
columnList.add(COPY_OPTION);
}
columnList.add(VAR_CREATED_DATE);
columnList.add(VAR_CREATED_USER);
@@ -377,6 +395,10 @@ public class RolloutListGrid extends AbstractGrid {
getColumn(UPDATE_OPTION)
.setRenderer(new HtmlButtonRenderer(clickEvent -> updateRollout((Long) clickEvent.getItemId())));
}
if (permissionChecker.hasRolloutCreatePermission()) {
getColumn(COPY_OPTION)
.setRenderer(new HtmlButtonRenderer(clickEvent -> copyRollout((Long) clickEvent.getItemId())));
}
}
@@ -430,12 +452,19 @@ public class RolloutListGrid extends AbstractGrid {
}
private void updateRollout(final Long rolloutId) {
final CommonDialogWindow addTargetWindow = addUpdateRolloutWindow.getWindow(rolloutId);
final CommonDialogWindow addTargetWindow = addUpdateRolloutWindow.getWindow(rolloutId, false);
addTargetWindow.setCaption(i18n.get("caption.update.rollout"));
UI.getCurrent().addWindow(addTargetWindow);
addTargetWindow.setVisible(Boolean.TRUE);
}
private void copyRollout(final Long rolloutId) {
final CommonDialogWindow addTargetWindow = addUpdateRolloutWindow.getWindow(rolloutId, true);
addTargetWindow.setCaption(i18n.get("caption.create.rollout"));
UI.getCurrent().addWindow(addTargetWindow);
addTargetWindow.setVisible(Boolean.TRUE);
}
private String getDescription(final CellReference cell) {
String description = null;

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.ui.rollout.rollout;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.UiProperties;
@@ -43,11 +44,12 @@ public class RolloutListHeader extends AbstractGridHeader {
RolloutListHeader(final SpPermissionChecker permissionChecker, final RolloutUIState rolloutUIState,
final UIEventBus eventBus, final RolloutManagement rolloutManagement,
final TargetManagement targetManagement, final UINotification uiNotification,
final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n) {
final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n,
final TargetFilterQueryManagement targetFilterQueryManagement) {
super(permissionChecker, rolloutUIState, i18n);
this.eventBus = eventBus;
this.addUpdateRolloutWindow = new AddUpdateRolloutWindowLayout(rolloutManagement, targetManagement,
uiNotification, uiProperties, entityFactory, i18n, eventBus);
uiNotification, uiProperties, entityFactory, i18n, eventBus, targetFilterQueryManagement);
}
@Override

View File

@@ -10,6 +10,7 @@ package org.eclipse.hawkbit.ui.rollout.rollout;
import org.eclipse.hawkbit.repository.EntityFactory;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.UiProperties;
@@ -31,11 +32,12 @@ public class RolloutListView extends AbstractGridLayout {
public RolloutListView(final SpPermissionChecker permissionChecker, final RolloutUIState rolloutUIState,
final UIEventBus eventBus, final RolloutManagement rolloutManagement,
final TargetManagement targetManagement, final UINotification uiNotification,
final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n) {
final UiProperties uiProperties, final EntityFactory entityFactory, final I18N i18n,
final TargetFilterQueryManagement targetFilterQueryManagement) {
super(new RolloutListHeader(permissionChecker, rolloutUIState, eventBus, rolloutManagement, targetManagement,
uiNotification, uiProperties, entityFactory, i18n),
uiNotification, uiProperties, entityFactory, i18n, targetFilterQueryManagement),
new RolloutListGrid(i18n, eventBus, rolloutManagement, uiNotification, rolloutUIState,
permissionChecker, targetManagement, entityFactory, uiProperties));
permissionChecker, targetManagement, entityFactory, uiProperties, targetFilterQueryManagement));
buildLayout();
}

View File

@@ -212,6 +212,14 @@ public final class SPUIStyleDefinitions {
* Query validator icon -error style.
*/
public static final String ERROR_ICON = "error-icon";
/**
* Rollout groups
*/
public static final String ROLLOUT_GROUPS = "rollout-groups";
/**
* Rollout groups pie chart
*/
public static final String ROLLOUT_GROUPS_CHART = "groups-pie-chart";
/**
* Rollout action type layout style.
*/

View File

@@ -174,6 +174,21 @@ public final class UIComponentIdProvider {
*/
public static final String ACTION_DETAILS_SOFT_ID = "action.details.soft.group";
/**
* Start type of rollout manual radio button
*/
public static final String ROLLOUT_START_MANUAL_ID = "rollout.start.manual.radio";
/**
* Start type of rollout auto start radio button
*/
public static final String ROLLOUT_START_AUTO_ID = "rollout.start.auto.radio";
/**
* Start type of rollout scheduled start radio button
*/
public static final String ROLLOUT_START_SCHEDULED_ID = "rollout.start.scheduled.start.radio";
/**
* ID - Label.
*/
@@ -723,6 +738,21 @@ public final class UIComponentIdProvider {
*/
public static final String ROLLOUT_ERROR_THRESOLD_ID = "rollout.error.thresold.id";
/**
* Rollout group target percentage id.
*/
public static final String ROLLOUT_GROUP_TARGET_PERC_ID = "rollout.group.target.percentage.id";
/**
* Rollout group add button id
*/
public static final String ROLLOUT_GROUP_ADD_ID = "rollout.group.add.id";
/**
* Rollout group remove button id
*/
public static final String ROLLOUT_GROUP_REMOVE_ID = "rollout.group.remove.id";
/**
* Rollout distribution set combo id.
*/
@@ -735,7 +765,18 @@ public final class UIComponentIdProvider {
* Rollout target filter query combo id.
*/
public static final String ROLLOUT_TARGET_FILTER_COMBO_ID = "rollout.target.filter.combo.id";
/**
* Rollout groups id
*/
public static final String ROLLOUT_GROUPS = "rollout.groups";
/**
* Rollout simple tab id
*/
public static final String ROLLOUT_SIMPLE_TAB = "rollout.create.tabs.simple";
/**
* Rollout advanced tab id
*/
public static final String ROLLOUT_ADVANCED_TAB = "rollout.create.tabs.advanced";
/**
* Rollout action button id.
*/
@@ -752,10 +793,15 @@ public final class UIComponentIdProvider {
public static final String ROLLOUT_PAUSE_BUTTON_ID = ROLLOUT_ACTION_ID + ".10";
/**
* Rollout resume button id.
* Rollout update button id.
*/
public static final String ROLLOUT_UPDATE_BUTTON_ID = ROLLOUT_ACTION_ID + ".11";
/**
* Rollout copy button id.
*/
public static final String ROLLOUT_COPY_BUTTON_ID = ROLLOUT_ACTION_ID + ".12";
/**
* Rollout status label id.
*/
@@ -771,6 +817,16 @@ public final class UIComponentIdProvider {
*/
public static final String ROLLOUT_ERROR_THRESOLD_OPTION_ID = "rollout.error.thresold.option.id";
/**
* Rollout groups options id.
*/
public static final String ROLLOUT_GROUPS_OPTION_ID = "rollout.groups.option.id";
/**
* Rollout groups definition button
*/
public static final String ROLLOUT_GROUPS_DEF_BUTTON_ID = "rollout.groups.definition.button.id";
/**
* Rollout target filter query value text area id.
*/

View File

@@ -7,95 +7,207 @@
* http://www.eclipse.org/legal/epl-v10.html
*/
@mixin rollout {
.rollout-option-group{
font-size:12px;
font-weight:400;
margin-left:8px;
}
.rollout-action-type-layout {
.v-caption-padding-right-style{
padding-right:0px !important;
}
}
.v-context-menu .v-context-menu-item-basic-icon-container{
height:0px !important;
width:0px !important;
}
.v-context-menu .v-context-menu-item-basic{
background-color: #feffff !important;
border-radius: 4px;
font-family : $app-font-family;
font-size : $app-text-font-size;
font-weight : normal;
font-style : normal;
}
.v-context-menu{
background-color: #feffff !important;
border-radius: 4px;
}
.v-context-menu .v-context-menu-item-basic:focus, .v-context-menu .v-context-menu-item-basic-submenu:focus, .v-context-menu .v-context-menu-item-basic-open {
@include valo-gradient($color: $hawkbit-primary-color);
background-color: $hawkbit-primary-color !important;
color: #e8eef3;
height: 30px;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.05);
}
.disable-action-type-layout{
opacity: 0.5;
}
.action-type-padding{
padding: 0 0px !important;
}
.rollout-caption-links{
font-weight: 400;
height: 25px ;
padding: 0px 4px ;
}
.rollout-target-count-message{
color: $info-message-color-grey;
}
.rollout-table{
.v-table-cell-wrapper {
cursor: default;
.rollout-option-group {
font-size: 12px;
font-weight: 400;
margin-left: 8px;
}
.rollout-action-type-layout {
.v-caption-padding-right-style {
padding-right: 0px !important;
}
}
.v-context-menu .v-context-menu-item-basic-icon-container {
height: 0px !important;
width: 0px !important;
}
.v-context-menu .v-context-menu-item-basic {
background-color: #feffff !important;
border-radius: 4px;
font-family: $app-font-family;
font-size: $app-text-font-size;
font-weight: normal;
font-style: normal;
}
.v-context-menu {
background-color: #feffff !important;
border-radius: 4px;
}
.v-context-menu .v-context-menu-item-basic:focus, .v-context-menu .v-context-menu-item-basic-submenu:focus, .v-context-menu .v-context-menu-item-basic-open {
@include valo-gradient($color: $hawkbit-primary-color);
background-color: $hawkbit-primary-color !important;
color: #e8eef3;
height: 30px;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.05);
}
.disable-action-type-layout {
opacity: 0.5;
}
.action-type-padding {
padding: 0 0px !important;
}
.rollout-caption-links {
font-weight: 400;
height: 25px;
padding: 0px 4px;
}
.rollout-target-count-message {
color: $info-message-color-grey;
}
.rollout-table {
.v-table-cell-wrapper {
cursor: default;
}
}
.v-grid-cell.centeralign {
text-align: center;
}
.v-grid-cell {
font-size: $v-font-size--small !important;
height: 34px !important;
}
.v-grid-row {
height: 34px !important;
}
.v-grid-cell.frozen {
box-shadow: none !important;
}
.v-grid-cell.frozen + th {
border-left: $v-grid-border-size solid $widget-border-color;
}
.v-button-boldhide {
text-decoration: none;
}
.groups-pie-chart {
float: right;
svg {
.pie {
stroke: #ffffff;
}
}
.v-grid-cell.centeralign {
text-align: center;
}
.v-grid-cell {
font-size: $v-font-size--small !important;
height: 34px !important;
}
.v-grid-row{
height: 34px !important;
}
.v-grid-cell.frozen{
box-shadow: none !important;
}
.v-grid-cell.frozen + th{
border-left: $v-grid-border-size solid $widget-border-color ;
}
.v-button-boldhide{
text-decoration:none;
}
/*
First child is intended for unassigned targets and should not be repeated.
*/
.pie:nth-child(1) { fill: #CBCBCB }
.pie:nth-child(16n+2) { fill: #E20015 }
.pie:nth-child(16n+3) { fill: #B90276 }
.pie:nth-child(16n+4) { fill: #50237F }
.pie:nth-child(16n+5) { fill: #005691 }
.pie:nth-child(16n+6) { fill: #008ECF }
.pie:nth-child(16n+7) { fill: #00A8B0 }
.pie:nth-child(16n+8) { fill: #78BE20 }
.pie:nth-child(16n+9) { fill: #006249 }
.pie:nth-child(16n+10) { fill: #F07F8A }
.pie:nth-child(16n+11) { fill: #DC80BA }
.pie:nth-child(16n+12) { fill: #A791BF }
.pie:nth-child(16n+13) { fill: #7FAAC8 }
.pie:nth-child(16n+14) { fill: #7FC6E7 }
.pie:nth-child(16n+15) { fill: #7FD3D7 }
.pie:nth-child(16n+16) { fill: #BBDE8F }
.pie:nth-child(16n+17) { fill: #7FB0A4 }
.pie-info {
pointer-events: none;
rect {
fill: rgba(255, 255, 255, 0.6);
}
text {
font-size: 12px;
font-weight: bold;
fill: $hawkbit-primary-color;
}
}
}
}
.rollout-groups {
.v-button {
margin: 0 3px 3px 3px;
}
.rollout-group-name {
border-left: 5px solid #000000;
}
.v-gridlayout-slot:nth-child(16n+8) .rollout-group-name { border-left-color: #E20015 }
.v-gridlayout-slot:nth-child(16n+14) .rollout-group-name { border-left-color: #B90276 }
.v-gridlayout-slot:nth-child(16n+20) .rollout-group-name { border-left-color: #50237F }
.v-gridlayout-slot:nth-child(16n+26) .rollout-group-name { border-left-color: #005691 }
.v-gridlayout-slot:nth-child(16n+32) .rollout-group-name { border-left-color: #008ECF }
.v-gridlayout-slot:nth-child(16n+38) .rollout-group-name { border-left-color: #00A8B0 }
.v-gridlayout-slot:nth-child(16n+44) .rollout-group-name { border-left-color: #78BE20 }
.v-gridlayout-slot:nth-child(16n+50) .rollout-group-name { border-left-color: #006249 }
.v-gridlayout-slot:nth-child(16n+56) .rollout-group-name { border-left-color: #F07F8A }
.v-gridlayout-slot:nth-child(16n+62) .rollout-group-name { border-left-color: #DC80BA }
.v-gridlayout-slot:nth-child(16n+68) .rollout-group-name { border-left-color: #A791BF }
.v-gridlayout-slot:nth-child(16n+74) .rollout-group-name { border-left-color: #7FAAC8 }
.v-gridlayout-slot:nth-child(16n+80) .rollout-group-name { border-left-color: #7FC6E7 }
.v-gridlayout-slot:nth-child(16n+86) .rollout-group-name { border-left-color: #7FD3D7 }
.v-gridlayout-slot:nth-child(16n+92) .rollout-group-name { border-left-color: #BBDE8F }
.v-gridlayout-slot:nth-child(16n+98) .rollout-group-name { border-left-color: #7FB0A4 }
}
.rollout-target-count-title {
}
.rollout-target-count-loading {
color: #ed473b;
}
.rollout-group-unassigned {
border-left: 5px solid #CBCBCB;
padding-left: 5px;
margin-bottom: 2px;
font-size: $v-font-size * .7;
}
.groups-legend {
.v-slot .rollout-group-count {
border-left: 5px solid transparent;
padding-left: 5px;
font-size: $v-font-size * .7;
}
.v-slot:nth-child(16n+1) .rollout-group-count { border-left-color: #E20015 }
.v-slot:nth-child(16n+2) .rollout-group-count { border-left-color: #B90276 }
.v-slot:nth-child(16n+3) .rollout-group-count { border-left-color: #50237F }
.v-slot:nth-child(16n+4) .rollout-group-count { border-left-color: #005691 }
.v-slot:nth-child(16n+5) .rollout-group-count { border-left-color: #008ECF }
.v-slot:nth-child(16n+6) .rollout-group-count { border-left-color: #00A8B0 }
.v-slot:nth-child(16n+7) .rollout-group-count { border-left-color: #78BE20 }
.v-slot:nth-child(16n+8) .rollout-group-count { border-left-color: #006249 }
.v-slot:nth-child(16n+9) .rollout-group-count { border-left-color: #F07F8A }
.v-slot:nth-child(16n+10) .rollout-group-count { border-left-color: #DC80BA }
.v-slot:nth-child(16n+11) .rollout-group-count { border-left-color: #A791BF }
.v-slot:nth-child(16n+12) .rollout-group-count { border-left-color: #7FAAC8 }
.v-slot:nth-child(16n+13) .rollout-group-count { border-left-color: #7FC6E7 }
.v-slot:nth-child(16n+14) .rollout-group-count { border-left-color: #7FD3D7 }
.v-slot:nth-child(16n+15) .rollout-group-count { border-left-color: #BBDE8F }
.v-slot:nth-child(16n+16) .rollout-group-count { border-left-color: #7FB0A4 }
}
}

View File

@@ -60,6 +60,7 @@ header.action=Actions
header.action.run=Run
header.action.pause=Pause
header.action.update=Edit
header.action.copy=Copy
header.status=Status
# event container
@@ -514,12 +515,17 @@ header.rolloutgroup.target.message = Messages
rollout.group.label.target.truncated = {0} targets has been truncated in the list due the target size limit of {1}
prompt.groups = Groups
prompt.number.of.groups = Number of groups
prompt.tigger.threshold = Trigger threshold
prompt.error.threshold = Error threshold
prompt.distribution.set = Distribution Set
button.rollout.groups.def.button = Define groups
button.rollout.add.group = Add Group
caption.configure.rollout = Configure Rollout
caption.configure.rollout.groups = Configure Deployment Groups
caption.update.rollout = Update Rollout
caption.create.rollout = Create new Rollout
prompt.target.filter = Custom Target Filter
message.rollout.nonzero.group.number = Number of groups must be greater than zero
message.rollout.max.group.number = Number of groups must not be greater than 500
@@ -536,6 +542,26 @@ label.target.per.group = Targets per group :
message.dist.already.assigned = Distribution {0} is already assigned to target
message.error.creating.rollout = Server error. Error creating Rollout. Please contact the administrator
message.error.starting.rollout = Server error. Error starting Rollout. Please contact the administrator
caption.rollout.group.definition.desc = Define which groups the Rollout should have.
header.target.percentage = Target percentage
textfield.target.percentage = Target percentage
textfield.rollout.group.default.name = Group {0}
message.rollout.group.name.invalid = Enter a group name with a length between 1 and 64
caption.rollout.tabs.simple = Number of Groups
caption.rollout.tabs.advanced = Advanced Group definition
caption.rollout.generate.groups = Generate the groups automatically with the specified thresholds.
caption.rollout.action.type = Action type
message.rollout.remaining.targets.error = Not all targets are addressed
textfield.rollout.copied.name = Copy of {0}
label.rollout.targets.in.group = {0} in {1}
caption.rollout.start.type = Start type
caption.rollout.start.manual = Manual
caption.rollout.start.manual.desc = The user starts the rollout manually.
caption.rollout.start.auto = Auto
caption.rollout.start.auto.desc = The rollout is started as soon as it is created.
caption.rollout.start.scheduled = Scheduled
caption.rollout.start.scheduled.desc = The rollout starts as soon as it is ready and the set time has passed.
label.rollout.calculating = Calculating groups ...
#rollout - end
#Menu