Integrated Maintenance Window fields in Management API and UI (#677)

* Added Maintenance Window properties to API and UI

* extended Management API with Maintenance Window schedule, duration, timezone and nextAt properties
* extended integration tests for the above properties
* extended Management UI with Maintenance Window column in Action History grid, added tooltip for next execution
* general refactoring

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>

* fixed Sonar issues

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>

* changed the documentation help link for maintenance window

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>

* added licence header, first refactoring after partial PR review

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>

* changes related to PR review findings

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>

* last PR review findings

Signed-off-by: Bogdan Bondar <Bogdan.Bondar@bosch-si.com>
This commit is contained in:
Bondar Bogdan
2018-05-15 11:12:33 +02:00
committed by Dominic Schabel
parent 1deb47a4db
commit b5114081be
22 changed files with 474 additions and 210 deletions

View File

@@ -8,88 +8,32 @@
*/ */
package org.eclipse.hawkbit.mgmt.json.model; package org.eclipse.hawkbit.mgmt.json.model;
import java.util.concurrent.TimeUnit;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.annotation.JsonProperty;
/** /**
* JSON model for Management API to define the maintenance window based on a * JSON model for Management API to define the maintenance window.
* schedule defined as cron expression, duration in HH:mm:ss format and time
* zone as offset from UTC.
*/ */
@JsonInclude(Include.NON_NULL) @JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtMaintenanceWindow { public class MgmtMaintenanceWindow extends MgmtMaintenanceWindowRequestBody {
private String maintenanceSchedule;
private String maintenanceWindowDuration;
private String maintenanceWindowTimeZone;
/** /**
* Sets the maintenance schedule. * Time in {@link TimeUnit#MILLISECONDS} of the next maintenance window
* * start
* @param maintenanceSchedule
* is the cron expression to be used for scheduling maintenance
* window(s). Expression has 6 mandatory fields and a last
* optional field: "second minute hour dayofmonth month weekday
* year".
*/ */
@JsonSetter("schedule") @JsonProperty
public void setMaintenanceSchedule(final String maintenanceSchedule) { private long nextStartAt;
this.maintenanceSchedule = maintenanceSchedule;
public long getNextStartAt() {
return nextStartAt;
} }
/** public void setNextStartAt(final long nextStartAt) {
* Sets the maintenance window duration. this.nextStartAt = nextStartAt;
*
* @param maintenanceWindowDuration
* in HH:mm:ss format specifying the duration of a maintenance
* window, for example 00:30:00 for 30 minutes.
*/
@JsonSetter("duration")
public void setMaintenanceWindowDuration(final String maintenanceWindowDuration) {
this.maintenanceWindowDuration = maintenanceWindowDuration;
}
/**
* Sets the maintenance window timezone.
*
* @param maintenanceWindowTimeZone
* is the time zone specified as +/-hh:mm offset from UTC. For
* example +02:00 for CET summer time and +00:00 for UTC. The
* start time of a maintenance window calculated based on the
* cron expression is relative to this time zone.
*/
@JsonSetter("timezone")
public void setMaintenanceWindowTimeZone(final String maintenanceWindowTimeZone) {
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
}
/**
* Returns the maintenance schedule for the {@link Action}.
*
* @return cron expression as {@link String}.
*/
public String getMaintenanceSchedule() {
return maintenanceSchedule;
}
/**
* Returns the duration of maintenance window for the {@link Action}.
*
* @return duration in HH:mm:ss format as {@link String}.
*/
public String getMaintenanceWindowDuration() {
return maintenanceWindowDuration;
}
/**
* Returns the timezone of maintenance window for the {@link Action}.
*
* @return the timezone offset from UTC in +/-hh:mm as {@link String}.
*/
public String getMaintenanceWindowTimeZone() {
return maintenanceWindowTimeZone;
} }
} }

View File

@@ -0,0 +1,76 @@
/**
* Copyright (c) 2018 Bosch Software Innovations GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.mgmt.json.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
/**
* Request body for maintenance window PUT/POST commands, based on a schedule
* defined as cron expression, duration in HH:mm:ss format and time zone as
* offset from UTC.
*
*/
@JsonInclude(Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class MgmtMaintenanceWindowRequestBody {
@JsonProperty
private String schedule;
@JsonProperty
private String duration;
@JsonProperty
private String timezone;
public String getSchedule() {
return schedule;
}
/**
* @param schedule
* is the cron expression to be used for scheduling maintenance
* window(s). Expression has 6 mandatory fields and a last
* optional field: "second minute hour dayofmonth month weekday
* year".
*/
public void setSchedule(final String schedule) {
this.schedule = schedule;
}
public String getDuration() {
return duration;
}
/**
* @param duration
* in HH:mm:ss format specifying the duration of a maintenance
* window, for example 00:30:00 for 30 minutes.
*/
public void setDuration(final String duration) {
this.duration = duration;
}
public String getTimezone() {
return timezone;
}
/**
* @param timezone
* is the time zone specified as +/-hh:mm offset from UTC. For
* example +02:00 for CET summer time and +00:00 for UTC. The
* start time of a maintenance window calculated based on the
* cron expression is relative to this time zone.
*/
public void setTimezone(final String timezone) {
this.timezone = timezone;
}
}

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.mgmt.json.model.action; package org.eclipse.hawkbit.mgmt.json.model.action;
import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity; import org.eclipse.hawkbit.mgmt.json.model.MgmtBaseEntity;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@@ -59,6 +60,17 @@ public class MgmtAction extends MgmtBaseEntity {
@JsonProperty @JsonProperty
private MgmtActionType forceType; private MgmtActionType forceType;
@JsonProperty
private MgmtMaintenanceWindow maintenanceWindow;
public MgmtMaintenanceWindow getMaintenanceWindow() {
return maintenanceWindow;
}
public void setMaintenanceWindow(final MgmtMaintenanceWindow maintenanceWindow) {
this.maintenanceWindow = maintenanceWindow;
}
public Long getForceTime() { public Long getForceTime() {
return forceTime; return forceTime;
} }
@@ -75,47 +87,26 @@ public class MgmtAction extends MgmtBaseEntity {
this.forceType = forceType; this.forceType = forceType;
} }
/**
* @return the status
*/
public String getStatus() { public String getStatus() {
return status; return status;
} }
/**
* @param status
* the status to set
*/
public void setStatus(final String status) { public void setStatus(final String status) {
this.status = status; this.status = status;
} }
/**
* @return the actionId
*/
public Long getActionId() { public Long getActionId() {
return actionId; return actionId;
} }
/**
* @param actionId
* the actionId to set
*/
public void setActionId(final Long actionId) { public void setActionId(final Long actionId) {
this.actionId = actionId; this.actionId = actionId;
} }
/**
* @return the type
*/
public String getType() { public String getType() {
return type; return type;
} }
/**
* @param type
* the type to set
*/
public void setType(final String type) { public void setType(final String type) {
this.type = type; this.type = type;
} }

View File

@@ -8,7 +8,7 @@
*/ */
package org.eclipse.hawkbit.mgmt.json.model.distributionset; package org.eclipse.hawkbit.mgmt.json.model.distributionset;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow; import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindowRequestBody;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@@ -28,10 +28,10 @@ public class MgmtTargetAssignmentRequestBody {
private MgmtActionType type; private MgmtActionType type;
/** /**
* {@link MgmtMaintenanceWindow} object containing schedule, duration and * {@link MgmtMaintenanceWindowRequestBody} object containing schedule,
* timezone. * duration and timezone.
*/ */
private MgmtMaintenanceWindow maintenanceWindow; private MgmtMaintenanceWindowRequestBody maintenanceWindow;
public String getId() { public String getId() {
return id; return id;
@@ -57,11 +57,11 @@ public class MgmtTargetAssignmentRequestBody {
this.forcetime = forcetime; this.forcetime = forcetime;
} }
public MgmtMaintenanceWindow getMaintenanceWindow() { public MgmtMaintenanceWindowRequestBody getMaintenanceWindow() {
return maintenanceWindow; return maintenanceWindow;
} }
public void setMaintenanceWindow(final MgmtMaintenanceWindow maintenanceWindow) { public void setMaintenanceWindow(final MgmtMaintenanceWindowRequestBody maintenanceWindow) {
this.maintenanceWindow = maintenanceWindow; this.maintenanceWindow = maintenanceWindow;
} }

View File

@@ -4,7 +4,7 @@
package org.eclipse.hawkbit.mgmt.json.model.target; package org.eclipse.hawkbit.mgmt.json.model.target;
import org.eclipse.hawkbit.mgmt.json.model.MgmtId; import org.eclipse.hawkbit.mgmt.json.model.MgmtId;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow; import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindowRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType; import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
/** /**
@@ -16,10 +16,10 @@ public class MgmtDistributionSetAssignment extends MgmtId {
private MgmtActionType type; private MgmtActionType type;
/** /**
* {@link MgmtMaintenanceWindow} object defining a schedule, duration and * {@link MgmtMaintenanceWindowRequestBody} object defining a schedule,
* timezone. * duration and timezone.
*/ */
private MgmtMaintenanceWindow maintenanceWindow; private MgmtMaintenanceWindowRequestBody maintenanceWindow;
public MgmtActionType getType() { public MgmtActionType getType() {
return type; return type;
@@ -38,21 +38,23 @@ public class MgmtDistributionSetAssignment extends MgmtId {
} }
/** /**
* Returns {@link MgmtMaintenanceWindow} for distribution set assignment. * Returns {@link MgmtMaintenanceWindowRequestBody} for distribution set
* assignment.
* *
* @return {@link MgmtMaintenanceWindow}. * @return {@link MgmtMaintenanceWindowRequestBody}.
*/ */
public MgmtMaintenanceWindow getMaintenanceWindow() { public MgmtMaintenanceWindowRequestBody getMaintenanceWindow() {
return maintenanceWindow; return maintenanceWindow;
} }
/** /**
* Sets {@link MgmtMaintenanceWindow} for distribution set assignment. * Sets {@link MgmtMaintenanceWindowRequestBody} for distribution set
* assignment.
* *
* @param maintenanceWindow * @param maintenanceWindow
* as {@link MgmtMaintenanceWindow}. * as {@link MgmtMaintenanceWindowRequestBody}.
*/ */
public void setMaintenanceWindow(final MgmtMaintenanceWindow maintenanceWindow) { public void setMaintenanceWindow(final MgmtMaintenanceWindowRequestBody maintenanceWindow) {
this.maintenanceWindow = maintenanceWindow; this.maintenanceWindow = maintenanceWindow;
} }
} }

View File

@@ -12,6 +12,7 @@ import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindowRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadata;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut; import org.eclipse.hawkbit.mgmt.json.model.MgmtMetadataBodyPut;
import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.PagedList;
@@ -252,14 +253,16 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
final DistributionSetAssignmentResult assignDistributionSet = this.deployManagament final DistributionSetAssignmentResult assignDistributionSet = this.deployManagament
.assignDistributionSet(distributionSetId, assignments.stream().map(t -> { .assignDistributionSet(distributionSetId, assignments.stream().map(t -> {
if (t.getMaintenanceWindow() == null) { final MgmtMaintenanceWindowRequestBody maintenanceWindow = t.getMaintenanceWindow();
if (maintenanceWindow == null) {
return new TargetWithActionType(t.getId(), MgmtRestModelMapper.convertActionType(t.getType()), return new TargetWithActionType(t.getId(), MgmtRestModelMapper.convertActionType(t.getType()),
t.getForcetime()); t.getForcetime());
} }
final String cronSchedule = t.getMaintenanceWindow().getMaintenanceSchedule(); final String cronSchedule = maintenanceWindow.getSchedule();
final String duration = t.getMaintenanceWindow().getMaintenanceWindowDuration(); final String duration = maintenanceWindow.getDuration();
final String timezone = t.getMaintenanceWindow().getMaintenanceWindowTimeZone(); final String timezone = maintenanceWindow.getTimezone();
MaintenanceScheduleHelper.validateMaintenanceSchedule(cronSchedule, duration, timezone); MaintenanceScheduleHelper.validateMaintenanceSchedule(cronSchedule, duration, timezone);

View File

@@ -19,6 +19,7 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow;
import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus; import org.eclipse.hawkbit.mgmt.json.model.MgmtPollStatus;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionStatus;
@@ -194,6 +195,16 @@ public final class MgmtTargetMapper {
result.setStatus(MgmtAction.ACTION_FINISHED); result.setStatus(MgmtAction.ACTION_FINISHED);
} }
if (action.hasMaintenanceSchedule()) {
final MgmtMaintenanceWindow maintenanceWindow = new MgmtMaintenanceWindow();
maintenanceWindow.setSchedule(action.getMaintenanceWindowSchedule());
maintenanceWindow.setDuration(action.getMaintenanceWindowDuration());
maintenanceWindow.setTimezone(action.getMaintenanceWindowTimeZone());
action.getMaintenanceWindowStartTime()
.ifPresent(nextStart -> maintenanceWindow.setNextStartAt(nextStart.toInstant().toEpochMilli()));
result.setMaintenanceWindow(maintenanceWindow);
}
MgmtRestModelMapper.mapBaseToBase(result, action); MgmtRestModelMapper.mapBaseToBase(result, action);
result.add(linkTo(methodOn(MgmtTargetRestApi.class).getAction(targetId, action.getId())).withSelfRel()); result.add(linkTo(methodOn(MgmtTargetRestApi.class).getAction(targetId, action.getId())).withSelfRel());

View File

@@ -15,7 +15,7 @@ import java.util.Map;
import javax.validation.ValidationException; import javax.validation.ValidationException;
import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindow; import org.eclipse.hawkbit.mgmt.json.model.MgmtMaintenanceWindowRequestBody;
import org.eclipse.hawkbit.mgmt.json.model.PagedList; import org.eclipse.hawkbit.mgmt.json.model.PagedList;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtAction;
import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut; import org.eclipse.hawkbit.mgmt.json.model.action.MgmtActionRequestBodyPut;
@@ -276,7 +276,7 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
} }
findTargetWithExceptionIfNotFound(controllerId); findTargetWithExceptionIfNotFound(controllerId);
final MgmtMaintenanceWindow maintenanceWindow = dsId.getMaintenanceWindow(); final MgmtMaintenanceWindowRequestBody maintenanceWindow = dsId.getMaintenanceWindow();
if (maintenanceWindow == null) { if (maintenanceWindow == null) {
return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(this.deploymentManagement return ResponseEntity.ok(MgmtDistributionSetMapper.toResponse(this.deploymentManagement
@@ -284,9 +284,9 @@ public class MgmtTargetResource implements MgmtTargetRestApi {
MgmtRestModelMapper.convertActionType(dsId.getType()), dsId.getForcetime()))))); MgmtRestModelMapper.convertActionType(dsId.getType()), dsId.getForcetime())))));
} }
final String cronSchedule = maintenanceWindow.getMaintenanceSchedule(); final String cronSchedule = maintenanceWindow.getSchedule();
final String duration = maintenanceWindow.getMaintenanceWindowDuration(); final String duration = maintenanceWindow.getDuration();
final String timezone = maintenanceWindow.getMaintenanceWindowTimeZone(); final String timezone = maintenanceWindow.getTimezone();
MaintenanceScheduleHelper.validateMaintenanceSchedule(cronSchedule, duration, timezone); MaintenanceScheduleHelper.validateMaintenanceSchedule(cronSchedule, duration, timezone);

View File

@@ -853,17 +853,97 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
} }
@Test @Test
@Description("Ensures that the expected response is return when update was cancelled.") @Description("Ensures that the expected response is returned for update action.")
public void getUpdateAction() throws Exception {
final String knownTargetId = "targetId";
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/"
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/" + actions.get(1).getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("id", equalTo(actions.get(1).getId().intValue())))
.andExpect(jsonPath("type", equalTo("update"))).andExpect(jsonPath("status", equalTo("pending")))
.andExpect(jsonPath("forceType", equalTo("forced")))
.andExpect(jsonPath("maintenanceWindow").doesNotExist())
.andExpect(jsonPath("_links.self.href",
equalTo(generateActionSelfLink(knownTargetId, actions.get(1).getId()))))
.andExpect(jsonPath("_links.distributionset.href",
equalTo(generateActionDsLink(actions.get(1).getDistributionSet().getId()))))
.andExpect(jsonPath("_links.status.href",
equalTo(generateStatusreferenceLink(knownTargetId, actions.get(1).getId()))));
}
@Test
@Description("Ensures that the expected response is returned for update action with maintenance window.")
public void getUpdateActionWithMaintenanceWindow() throws Exception {
final String knownTargetId = "targetId";
final String schedule = getTestSchedule(10);
final String duration = getTestDuration(10);
final String timezone = getTestTimeZone();
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverrideWithMaintenanceWindow(knownTargetId,
schedule, duration, timezone);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/"
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/" + actions.get(1).getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("id", equalTo(actions.get(1).getId().intValue())))
.andExpect(jsonPath("type", equalTo("update"))).andExpect(jsonPath("status", equalTo("pending")))
.andExpect(jsonPath("forceType", equalTo("forced")))
.andExpect(jsonPath("maintenanceWindow.schedule", equalTo(schedule)))
.andExpect(jsonPath("maintenanceWindow.duration", equalTo(duration)))
.andExpect(jsonPath("maintenanceWindow.timezone", equalTo(timezone)))
.andExpect(jsonPath("maintenanceWindow.nextStartAt",
equalTo(actions.get(1).getMaintenanceWindowStartTime().get().toInstant().toEpochMilli())))
.andExpect(jsonPath("_links.self.href",
equalTo(generateActionSelfLink(knownTargetId, actions.get(1).getId()))))
.andExpect(jsonPath("_links.distributionset.href",
equalTo(generateActionDsLink(actions.get(1).getDistributionSet().getId()))))
.andExpect(jsonPath("_links.status.href",
equalTo(generateStatusreferenceLink(knownTargetId, actions.get(1).getId()))));
}
@Test
@Description("Ensures that the expected response is returned when update action was cancelled.")
public void getCancelAction() throws Exception { public void getCancelAction() throws Exception {
final String knownTargetId = "targetId"; final String knownTargetId = "targetId";
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId); final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/"
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/" + actions.get(0).getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("id", equalTo(actions.get(0).getId().intValue())))
.andExpect(jsonPath("type", equalTo("cancel"))).andExpect(jsonPath("status", equalTo("pending")))
.andExpect(jsonPath("forceType", equalTo("forced")))
.andExpect(jsonPath("maintenanceWindow").doesNotExist())
.andExpect(jsonPath("_links.self.href",
equalTo(generateActionSelfLink(knownTargetId, actions.get(0).getId()))))
.andExpect(jsonPath("_links.canceledaction.href",
equalTo(generateCanceledactionreferenceLink(knownTargetId, actions.get(0)))))
.andExpect(jsonPath("_links.status.href",
equalTo(generateStatusreferenceLink(knownTargetId, actions.get(0).getId()))));
}
@Test
@Description("Ensures that the expected response is returned when update action with maintenance window was cancelled.")
public void getCancelActionWithMaintenanceWindow() throws Exception {
final String knownTargetId = "targetId";
final String schedule = getTestSchedule(10);
final String duration = getTestDuration(10);
final String timezone = getTestTimeZone();
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverrideWithMaintenanceWindow(knownTargetId,
schedule, duration, timezone);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/"
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/" + actions.get(0).getId())) + MgmtRestConstants.TARGET_V1_ACTIONS + "/" + actions.get(0).getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk()) .andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("id", equalTo(actions.get(0).getId().intValue()))) .andExpect(jsonPath("id", equalTo(actions.get(0).getId().intValue())))
.andExpect(jsonPath("forceType", equalTo("forced"))).andExpect(jsonPath("type", equalTo("cancel"))) .andExpect(jsonPath("forceType", equalTo("forced"))).andExpect(jsonPath("type", equalTo("cancel")))
.andExpect(jsonPath("status", equalTo("pending"))) .andExpect(jsonPath("status", equalTo("pending")))
.andExpect(jsonPath("maintenanceWindow.schedule", equalTo(schedule)))
.andExpect(jsonPath("maintenanceWindow.duration", equalTo(duration)))
.andExpect(jsonPath("maintenanceWindow.timezone", equalTo(timezone)))
.andExpect(jsonPath("maintenanceWindow.nextStartAt",
equalTo(actions.get(0).getMaintenanceWindowStartTime().get().toInstant().toEpochMilli())))
.andExpect(jsonPath("_links.self.href", .andExpect(jsonPath("_links.self.href",
equalTo(generateActionSelfLink(knownTargetId, actions.get(0).getId())))) equalTo(generateActionSelfLink(knownTargetId, actions.get(0).getId()))))
.andExpect(jsonPath("_links.canceledaction.href", .andExpect(jsonPath("_links.canceledaction.href",
@@ -896,6 +976,44 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(2))); .andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(2)));
} }
@Test
@Description("Ensures that the expected response of getting actions with maintenance window of a target is returned.")
public void getMultipleActionsWithMaintenanceWindow() throws Exception {
final String knownTargetId = "targetId";
final String schedule = getTestSchedule(10);
final String duration = getTestDuration(10);
final String timezone = getTestTimeZone();
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverrideWithMaintenanceWindow(knownTargetId,
schedule, duration, timezone);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/"
+ MgmtRestConstants.TARGET_V1_ACTIONS).param(MgmtRestConstants.REQUEST_PARAMETER_SORTING, "ID:ASC"))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("content.[1].id", equalTo(actions.get(1).getId().intValue())))
.andExpect(jsonPath("content.[1].type", equalTo("update")))
.andExpect(jsonPath("content.[1].status", equalTo("pending")))
.andExpect(jsonPath("content.[1].maintenanceWindow.schedule", equalTo(schedule)))
.andExpect(jsonPath("content.[1].maintenanceWindow.duration", equalTo(duration)))
.andExpect(jsonPath("content.[1].maintenanceWindow.timezone", equalTo(timezone)))
.andExpect(jsonPath("content.[1].maintenanceWindow.nextStartAt",
equalTo(actions.get(1).getMaintenanceWindowStartTime().get().toInstant().toEpochMilli())))
.andExpect(jsonPath("content.[1]._links.self.href",
equalTo(generateActionSelfLink(knownTargetId, actions.get(1).getId()))))
.andExpect(jsonPath("content.[0].id", equalTo(actions.get(0).getId().intValue())))
.andExpect(jsonPath("content.[0].type", equalTo("cancel")))
.andExpect(jsonPath("content.[0].status", equalTo("pending")))
.andExpect(jsonPath("content.[0].maintenanceWindow.schedule", equalTo(schedule)))
.andExpect(jsonPath("content.[0].maintenanceWindow.duration", equalTo(duration)))
.andExpect(jsonPath("content.[0].maintenanceWindow.timezone", equalTo(timezone)))
.andExpect(jsonPath("content.[0].maintenanceWindow.nextStartAt",
equalTo(actions.get(0).getMaintenanceWindowStartTime().get().toInstant().toEpochMilli())))
.andExpect(jsonPath("content.[0]._links.self.href",
equalTo(generateActionSelfLink(knownTargetId, actions.get(0).getId()))))
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_TOTAL, equalTo(2)))
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_SIZE, equalTo(2)))
.andExpect(jsonPath(JSON_PATH_PAGED_LIST_CONTENT, hasSize(2)));
}
@Test @Test
@Description("Verifies that the API returns the status list with expected content.") @Description("Verifies that the API returns the status list with expected content.")
public void getMultipleActionStatus() throws Exception { public void getMultipleActionStatus() throws Exception {
@@ -1054,6 +1172,10 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/" + actionId; + MgmtRestConstants.TARGET_V1_ACTIONS + "/" + actionId;
} }
private String generateActionDsLink(final Long dsId) {
return "http://localhost" + MgmtRestConstants.DISTRIBUTIONSET_V1_REQUEST_MAPPING + "/" + dsId;
}
private String generateCanceledactionreferenceLink(final String knownTargetId, final Action action) { private String generateCanceledactionreferenceLink(final String knownTargetId, final Action action) {
return "http://localhost" + MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/" return "http://localhost" + MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/"
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/" + action.getId(); + MgmtRestConstants.TARGET_V1_ACTIONS + "/" + action.getId();
@@ -1067,20 +1189,33 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
private List<Action> generateTargetWithTwoUpdatesWithOneOverride(final String knownTargetId) private List<Action> generateTargetWithTwoUpdatesWithOneOverride(final String knownTargetId)
throws InterruptedException { throws InterruptedException {
return generateTargetWithTwoUpdatesWithOneOverrideWithMaintenanceWindow(knownTargetId, null, null, null);
}
private List<Action> generateTargetWithTwoUpdatesWithOneOverrideWithMaintenanceWindow(final String knownTargetId,
final String schedule, final String duration, final String timezone) throws InterruptedException {
final Target target = testdataFactory.createTarget(knownTargetId); final Target target = testdataFactory.createTarget(knownTargetId);
final List<Target> targets = Arrays.asList(target);
final Iterator<DistributionSet> sets = testdataFactory.createDistributionSets(2).iterator(); final Iterator<DistributionSet> sets = testdataFactory.createDistributionSets(2).iterator();
final DistributionSet one = sets.next(); final DistributionSet one = sets.next();
final DistributionSet two = sets.next(); final DistributionSet two = sets.next();
// Update // Update
final List<Target> updatedTargets = assignDistributionSet(one, targets).getAssignedEntity(); if (schedule == null) {
final List<Target> updatedTargets = assignDistributionSet(one, Arrays.asList(target)).getAssignedEntity();
// 2nd update // 2nd update
// sleep 10ms to ensure that we can sort by reportedAt // sleep 10ms to ensure that we can sort by reportedAt
Thread.sleep(10); Thread.sleep(10);
assignDistributionSet(two, updatedTargets); assignDistributionSet(two, updatedTargets);
} else {
final List<Target> updatedTargets = assignDistributionSetWithMaintenanceWindow(one.getId(),
target.getControllerId(), schedule, duration, timezone).getAssignedEntity();
// 2nd update
// sleep 10ms to ensure that we can sort by reportedAt
Thread.sleep(10);
assignDistributionSetWithMaintenanceWindow(two.getId(), updatedTargets.get(0).getControllerId(), schedule,
duration, timezone);
}
// two updates, one cancellation // two updates, one cancellation
final List<Action> actions = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE) final List<Action> actions = deploymentManagement.findActionsByTarget(target.getControllerId(), PAGE)
@@ -1090,26 +1225,6 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
return actions; return actions;
} }
@Test
public void getUpdateAction() throws Exception {
final String knownTargetId = "targetId";
final List<Action> actions = generateTargetWithTwoUpdatesWithOneOverride(knownTargetId);
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + knownTargetId + "/"
+ MgmtRestConstants.TARGET_V1_ACTIONS + "/" + actions.get(1).getId()))
.andDo(MockMvcResultPrinter.print()).andExpect(status().isOk())
.andExpect(jsonPath("id", equalTo(actions.get(1).getId().intValue())))
.andExpect(jsonPath("type", equalTo("update"))).andExpect(jsonPath("status", equalTo("pending")))
.andExpect(jsonPath("forceType", equalTo("forced")))
.andExpect(jsonPath("_links.self.href",
equalTo(generateActionSelfLink(knownTargetId, actions.get(1).getId()))))
.andExpect(jsonPath("_links.distributionset.href",
equalTo("http://localhost/rest/v1/distributionsets/"
+ actions.get(1).getDistributionSet().getId())))
.andExpect(jsonPath("_links.status.href",
equalTo(generateStatusreferenceLink(knownTargetId, actions.get(1).getId()))));
}
@Test @Test
@Description("Verfies that an action is switched from soft to forced if requested by management API") @Description("Verfies that an action is switched from soft to forced if requested by management API")
public void updateAction() throws Exception { public void updateAction() throws Exception {
@@ -1266,6 +1381,28 @@ public class MgmtTargetResourceTest extends AbstractManagementApiIntegrationTest
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
@Description("Assigns distribution set to target with maintenance window next execution start (should be ignored, calculated automaticaly based on schedule, duration and timezone)")
public void assignDistributionSetToTargetWithMaintenanceWindowNextExecutionStart() throws Exception {
final Target target = testdataFactory.createTarget("fsdfsd");
final DistributionSet set = testdataFactory.createDistributionSet("one");
final long nextExecutionStart = System.currentTimeMillis();
final String body = new JSONObject().put("id", set.getId())
.put("maintenanceWindow", getMaintenanceWindowWithNextStart(getTestSchedule(10), getTestDuration(10),
getTestTimeZone(), nextExecutionStart))
.toString();
mvc.perform(post(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId() + "/assignedDS")
.content(body).contentType(MediaType.APPLICATION_JSON)).andDo(MockMvcResultPrinter.print())
.andExpect(status().isOk());
mvc.perform(get(MgmtRestConstants.TARGET_V1_REQUEST_MAPPING + "/" + target.getControllerId() + "/"
+ MgmtRestConstants.TARGET_V1_ACTIONS)).andExpect(status().isOk()).andDo(MockMvcResultPrinter.print())
.andExpect(jsonPath("content.[0].maintenanceWindow.nextStartAt", not(nextExecutionStart)));
}
@Test @Test
@Description("Assigns distribution set to target with last maintenance window scheduled before current time.") @Description("Assigns distribution set to target with last maintenance window scheduled before current time.")
public void assignDistributionSetToTargetWithMaintenanceWindowEndTimeBeforeStartTime() throws Exception { public void assignDistributionSetToTargetWithMaintenanceWindowEndTimeBeforeStartTime() throws Exception {

View File

@@ -8,6 +8,8 @@
*/ */
package org.eclipse.hawkbit.repository.model; package org.eclipse.hawkbit.repository.model;
import java.time.ZonedDateTime;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@@ -18,7 +20,7 @@ public interface Action extends TenantAwareBaseEntity {
/** /**
* Maximum length of controllerId. * Maximum length of controllerId.
*/ */
int MAINTENANCE_SCHEDULE_CRON_LENGTH = 128; int MAINTENANCE_WINDOW_SCHEDULE_LENGTH = 128;
/** /**
* Maximum length of controllerId. * Maximum length of controllerId.
@@ -81,6 +83,21 @@ public interface Action extends TenantAwareBaseEntity {
*/ */
Rollout getRollout(); Rollout getRollout();
/**
* @return maintenance window schedule related to this {@link Action}.
*/
String getMaintenanceWindowSchedule();
/**
* @return maintenance window duration related to this {@link Action}.
*/
String getMaintenanceWindowDuration();
/**
* @return maintenance window time zone related to this {@link Action}.
*/
String getMaintenanceWindowTimeZone();
/** /**
* checks if the {@link #getForcedTime()} is hit by the given * checks if the {@link #getForcedTime()} is hit by the given
* {@code hitTimeMillis}, by means if the given milliseconds are greater * {@code hitTimeMillis}, by means if the given milliseconds are greater
@@ -215,6 +232,15 @@ public interface Action extends TenantAwareBaseEntity {
TIMEFORCED; TIMEFORCED;
} }
/**
* Returns the start time of next available maintenance window for the
* {@link Action} as {@link ZonedDateTime}. If a maintenance window is
* already active, the start time of currently active window is returned.
*
* @return the start time as { @link Optional<ZonedDateTime>}.
*/
Optional<ZonedDateTime> getMaintenanceWindowStartTime();
/** /**
* The method checks whether the action has a maintenance schedule defined * The method checks whether the action has a maintenance schedule defined
* for it. A maintenance schedule defines a set of maintenance windows * for it. A maintenance schedule defines a set of maintenance windows

View File

@@ -229,7 +229,7 @@ public abstract class AbstractDsAssignmentStrategy {
actionForTarget.setActive(true); actionForTarget.setActive(true);
actionForTarget.setTarget(target); actionForTarget.setTarget(target);
actionForTarget.setDistributionSet(set); actionForTarget.setDistributionSet(set);
actionForTarget.setMaintenanceSchedule(targetWithActionType.getMaintenanceSchedule()); actionForTarget.setMaintenanceWindowSchedule(targetWithActionType.getMaintenanceSchedule());
actionForTarget.setMaintenanceWindowDuration(targetWithActionType.getMaintenanceWindowDuration()); actionForTarget.setMaintenanceWindowDuration(targetWithActionType.getMaintenanceWindowDuration());
actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone()); actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone());
return actionForTarget; return actionForTarget;

View File

@@ -117,8 +117,8 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@JoinColumn(name = "rollout", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout")) @JoinColumn(name = "rollout", updatable = false, foreignKey = @ForeignKey(value = ConstraintMode.CONSTRAINT, name = "fk_action_rollout"))
private JpaRollout rollout; private JpaRollout rollout;
@Column(name = "maintenance_cron_schedule", updatable = false, length = Action.MAINTENANCE_SCHEDULE_CRON_LENGTH) @Column(name = "maintenance_cron_schedule", updatable = false, length = Action.MAINTENANCE_WINDOW_SCHEDULE_LENGTH)
private String maintenanceSchedule; private String maintenanceWindowSchedule;
@Column(name = "maintenance_duration", updatable = false, length = Action.MAINTENANCE_WINDOW_DURATION_LENGTH) @Column(name = "maintenance_duration", updatable = false, length = Action.MAINTENANCE_WINDOW_DURATION_LENGTH)
private String maintenanceWindowDuration; private String maintenanceWindowDuration;
@@ -231,14 +231,24 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
// there is no action deletion // there is no action deletion
} }
@Override
public String getMaintenanceWindowSchedule() {
return maintenanceWindowSchedule;
}
/** /**
* Sets the maintenance schedule. * Sets the maintenance schedule.
* *
* @param maintenanceSchedule * @param maintenanceWindowSchedule
* is a cron expression to be used for scheduling. * is a cron expression to be used for scheduling.
*/ */
public void setMaintenanceSchedule(final String maintenanceSchedule) { public void setMaintenanceWindowSchedule(final String maintenanceWindowSchedule) {
this.maintenanceSchedule = maintenanceSchedule; this.maintenanceWindowSchedule = maintenanceWindowSchedule;
}
@Override
public String getMaintenanceWindowDuration() {
return maintenanceWindowDuration;
} }
/** /**
@@ -252,6 +262,11 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
this.maintenanceWindowDuration = maintenanceWindowDuration; this.maintenanceWindowDuration = maintenanceWindowDuration;
} }
@Override
public String getMaintenanceWindowTimeZone() {
return maintenanceWindowTimeZone;
}
/** /**
* Sets the time zone to be used for maintenance window. * Sets the time zone to be used for maintenance window.
* *
@@ -265,15 +280,9 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
this.maintenanceWindowTimeZone = maintenanceWindowTimeZone; this.maintenanceWindowTimeZone = maintenanceWindowTimeZone;
} }
/** @Override
* Returns the start time of next available maintenance window for the
* {@link Action} as {@link ZonedDateTime}. If a maintenance window is
* already active, the start time of currently active window is returned.
*
* @return the start time as {@link Optional<ZonedDateTime>}.
*/
public Optional<ZonedDateTime> getMaintenanceWindowStartTime() { public Optional<ZonedDateTime> getMaintenanceWindowStartTime() {
return MaintenanceScheduleHelper.getNextMaintenanceWindow(maintenanceSchedule, maintenanceWindowDuration, return MaintenanceScheduleHelper.getNextMaintenanceWindow(maintenanceWindowSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone); maintenanceWindowTimeZone);
} }
@@ -291,7 +300,7 @@ public class JpaAction extends AbstractJpaTenantAwareBaseEntity implements Actio
@Override @Override
public boolean hasMaintenanceSchedule() { public boolean hasMaintenanceSchedule() {
return this.maintenanceSchedule != null; return this.maintenanceWindowSchedule != null;
} }
@Override @Override

View File

@@ -225,7 +225,7 @@ public abstract class AbstractIntegrationTest {
} }
}; };
protected DistributionSetAssignmentResult assignDistributionSet(final Long dsID, final String controllerId) { protected DistributionSetAssignmentResult assignDistributionSet(final long dsID, final String controllerId) {
return deploymentManagement.assignDistributionSet(dsID, Arrays.asList( return deploymentManagement.assignDistributionSet(dsID, Arrays.asList(
new TargetWithActionType(controllerId, ActionType.FORCED, RepositoryModelConstants.NO_FORCE_TIME))); new TargetWithActionType(controllerId, ActionType.FORCED, RepositoryModelConstants.NO_FORCE_TIME)));
} }
@@ -253,18 +253,26 @@ public abstract class AbstractIntegrationTest {
* start time of a maintenance window calculated based on the * start time of a maintenance window calculated based on the
* cron expression is relative to this time zone * cron expression is relative to this time zone
* *
* @return result of the assignment as * @return result of the assignment as { @link
* {@link DistributionSetAssignmentResult}. * DistributionSetAssignmentResult}.
*/ */
protected DistributionSetAssignmentResult assignDistributionSetWithMaintenanceWindow(final Long dsID, protected DistributionSetAssignmentResult assignDistributionSetWithMaintenanceWindow(final long dsID,
final String controllerId, final String maintenanceSchedule, final String maintenanceWindowDuration, final String controllerId, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) { final String maintenanceWindowTimeZone) {
return deploymentManagement.assignDistributionSet(dsID, return deploymentManagement.assignDistributionSet(dsID,
Arrays.asList(new TargetWithActionType(controllerId, ActionType.FORCED, Arrays.asList(new TargetWithActionType(controllerId, ActionType.FORCED,
RepositoryModelConstants.NO_FORCE_TIME, maintenanceSchedule, maintenanceWindowDuration, RepositoryModelConstants.NO_FORCE_TIME, maintenanceWindowSchedule, maintenanceWindowDuration,
maintenanceWindowTimeZone))); maintenanceWindowTimeZone)));
} }
protected DistributionSetAssignmentResult assignDistributionSetWithMaintenanceWindowTimeForced(final long dsID,
final String controllerId, final String maintenanceWindowSchedule, final String maintenanceWindowDuration,
final String maintenanceWindowTimeZone) {
return deploymentManagement.assignDistributionSet(dsID,
Arrays.asList(new TargetWithActionType(controllerId, ActionType.TIMEFORCED, System.currentTimeMillis(),
maintenanceWindowSchedule, maintenanceWindowDuration, maintenanceWindowTimeZone)));
}
protected DistributionSetAssignmentResult assignDistributionSet(final DistributionSet pset, protected DistributionSetAssignmentResult assignDistributionSet(final DistributionSet pset,
final List<Target> targets) { final List<Target> targets) {
return deploymentManagement.assignDistributionSet(pset.getId(), return deploymentManagement.assignDistributionSet(pset.getId(),
@@ -275,11 +283,17 @@ public abstract class AbstractIntegrationTest {
return assignDistributionSet(pset, Arrays.asList(target)); return assignDistributionSet(pset, Arrays.asList(target));
} }
protected DistributionSetMetadata createDistributionSetMetadata(final Long dsId, final MetaData md) { protected DistributionSetAssignmentResult assignDistributionSetTimeForced(final DistributionSet pset,
final Target target) {
return deploymentManagement.assignDistributionSet(pset.getId(), Arrays.asList(
new TargetWithActionType(target.getControllerId(), ActionType.TIMEFORCED, System.currentTimeMillis())));
}
protected DistributionSetMetadata createDistributionSetMetadata(final long dsId, final MetaData md) {
return createDistributionSetMetadata(dsId, Collections.singletonList(md)).get(0); return createDistributionSetMetadata(dsId, Collections.singletonList(md)).get(0);
} }
protected List<DistributionSetMetadata> createDistributionSetMetadata(final Long dsId, final List<MetaData> md) { protected List<DistributionSetMetadata> createDistributionSetMetadata(final long dsId, final List<MetaData> md) {
return distributionSetManagement.createMetaData(dsId, md); return distributionSetManagement.createMetaData(dsId, md);
} }
@@ -378,23 +392,23 @@ public abstract class AbstractIntegrationTest {
* *
* @return {@link String} containing a valid cron expression. * @return {@link String} containing a valid cron expression.
*/ */
public static String getTestSchedule(final int minutesToAdd) { protected static String getTestSchedule(final int minutesToAdd) {
ZonedDateTime currentTime = ZonedDateTime.now(); ZonedDateTime currentTime = ZonedDateTime.now();
currentTime = currentTime.plusMinutes(minutesToAdd); currentTime = currentTime.plusMinutes(minutesToAdd);
return String.format("0 %d %d %d %d ? %d", currentTime.getMinute(), currentTime.getHour(), return String.format("0 %d %d %d %d ? %d", currentTime.getMinute(), currentTime.getHour(),
currentTime.getDayOfMonth(), currentTime.getMonthValue(), currentTime.getYear()); currentTime.getDayOfMonth(), currentTime.getMonthValue(), currentTime.getYear());
} }
public static String getTestDuration(final int duration) { protected static String getTestDuration(final int duration) {
return String.format("%02d:%02d:00", duration / 60, duration % 60); return String.format("%02d:%02d:00", duration / 60, duration % 60);
} }
public static String getTestTimeZone() { protected static String getTestTimeZone() {
final ZonedDateTime currentTime = ZonedDateTime.now(); final ZonedDateTime currentTime = ZonedDateTime.now();
return currentTime.getOffset().getId().replace("Z", "+00:00"); return currentTime.getOffset().getId().replace("Z", "+00:00");
} }
public static Map<String, String> getMaintenanceWindow(final String schedule, final String duration, protected static Map<String, String> getMaintenanceWindow(final String schedule, final String duration,
final String timezone) { final String timezone) {
final Map<String, String> maintenanceWindowMap = new HashMap<>(); final Map<String, String> maintenanceWindowMap = new HashMap<>();
maintenanceWindowMap.put("schedule", schedule); maintenanceWindowMap.put("schedule", schedule);
@@ -402,4 +416,11 @@ public abstract class AbstractIntegrationTest {
maintenanceWindowMap.put("timezone", timezone); maintenanceWindowMap.put("timezone", timezone);
return maintenanceWindowMap; return maintenanceWindowMap;
} }
protected static Map<String, String> getMaintenanceWindowWithNextStart(final String schedule, final String duration,
final String timezone, final long nextStartAt) {
final Map<String, String> maintenanceWindowMap = getMaintenanceWindow(schedule, duration, timezone);
maintenanceWindowMap.put("nextStartAt", String.valueOf(nextStartAt));
return maintenanceWindowMap;
}
} }

View File

@@ -8,12 +8,16 @@
*/ */
package org.eclipse.hawkbit.ui.common.grid; package org.eclipse.hawkbit.ui.common.grid;
import java.time.ZonedDateTime;
import java.util.Arrays; import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
import java.util.Objects; import java.util.Objects;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.ui.SpPermissionChecker; import org.eclipse.hawkbit.ui.SpPermissionChecker;
import org.eclipse.hawkbit.ui.components.RefreshableContainer; import org.eclipse.hawkbit.ui.components.RefreshableContainer;
import org.eclipse.hawkbit.ui.management.actionhistory.ProxyAction;
import org.eclipse.hawkbit.ui.management.actionhistory.ProxyActionStatus;
import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil; import org.eclipse.hawkbit.ui.utils.SPDateTimeUtil;
import org.eclipse.hawkbit.ui.utils.SPUIDefinitions; import org.eclipse.hawkbit.ui.utils.SPUIDefinitions;
import org.eclipse.hawkbit.ui.utils.VaadinMessageSource; import org.eclipse.hawkbit.ui.utils.VaadinMessageSource;
@@ -284,8 +288,8 @@ public abstract class AbstractGrid<T extends Indexed> extends Grid implements Re
/** /**
* Template method invoked by {@link this#addNewContainerDS()} for adding * Template method invoked by {@link this#addNewContainerDS()} for adding
* properties to the container (usually by invoing * properties to the container (usually by invoking { @link
* {@link Container#addContainerProperty(Object, Class, Object))}) * Container#addContainerProperty(Object, Class, Object))})
*/ */
protected abstract void addContainerProperties(); protected abstract void addContainerProperties();
@@ -581,7 +585,7 @@ public abstract class AbstractGrid<T extends Indexed> extends Grid implements Re
* CellStyleGenerator that concerns about alignment in the grid cells. * CellStyleGenerator that concerns about alignment in the grid cells.
*/ */
protected static class AlignCellStyleGenerator implements CellStyleGenerator { protected static class AlignCellStyleGenerator implements CellStyleGenerator {
private static final long serialVersionUID = 5573570647129792429L; private static final long serialVersionUID = 1L;
private final String[] left; private final String[] left;
private final String[] center; private final String[] center;
@@ -621,29 +625,40 @@ public abstract class AbstractGrid<T extends Indexed> extends Grid implements Re
} }
/** /**
* Adds a tooltip to the 'Date and time' column in detailed format. * Adds a tooltip to the 'Date and time' and 'Maintenance Window' columns in
* detailed format.
*/ */
public static class ModifiedTimeTooltipGenerator implements CellDescriptionGenerator { protected static class TooltipGenerator implements CellDescriptionGenerator {
private static final long serialVersionUID = -6617911967167729195L; private static final long serialVersionUID = 1L;
private final String datePropertyId; private final VaadinMessageSource i18n;
/** public TooltipGenerator(final VaadinMessageSource i18n) {
* Constructor. this.i18n = i18n;
*
* @param datePropertyId
*/
public ModifiedTimeTooltipGenerator(final String datePropertyId) {
this.datePropertyId = datePropertyId;
} }
@Override @Override
public String getDescription(final CellReference cell) { public String getDescription(final CellReference cell) {
if (!datePropertyId.equals(cell.getPropertyId())) { final String propertyId = (String) cell.getPropertyId();
switch (propertyId) {
case ProxyAction.PXY_ACTION_LAST_MODIFIED_AT:
case ProxyActionStatus.PXY_AS_CREATED_AT:
final Long timestamp = (Long) cell.getItem().getItemProperty(propertyId).getValue();
return SPDateTimeUtil.getFormattedDate(timestamp);
case ProxyAction.PXY_ACTION_MAINTENANCE_WINDOW:
final Action action = (Action) cell.getItem().getItemProperty(ProxyAction.PXY_ACTION).getValue();
return action.getMaintenanceWindowStartTime().map(this::getFormattedNextMaintenanceWindow).orElse(null);
default:
return null; return null;
} }
final Long timestamp = (Long) cell.getItem().getItemProperty(datePropertyId).getValue(); }
return SPDateTimeUtil.getFormattedDate(timestamp);
private String getFormattedNextMaintenanceWindow(final ZonedDateTime nextAt) {
final long nextAtMilli = nextAt.toInstant().toEpochMilli();
return i18n.getMessage("tooltip.next.maintenancewindow",
SPDateTimeUtil.getFormattedDate(nextAtMilli, SPUIDefinitions.LAST_QUERY_DATE_FORMAT_SHORT));
} }
} }

View File

@@ -115,12 +115,19 @@ public class ActionBeanQuery extends AbstractBeanQuery<ProxyAction> {
proxyAction.setLastModifiedAt(action.getLastModifiedAt()); proxyAction.setLastModifiedAt(action.getLastModifiedAt());
proxyAction.setRolloutName(action.getRollout() != null ? action.getRollout().getName() : ""); proxyAction.setRolloutName(action.getRollout() != null ? action.getRollout().getName() : "");
proxyAction.setStatus(action.getStatus()); proxyAction.setStatus(action.getStatus());
proxyAction.setMaintenanceWindow(
action.hasMaintenanceSchedule() ? buildMaintenanceWindowDisplayText(action) : "");
proxyActions.add(proxyAction); proxyActions.add(proxyAction);
} }
return proxyActions; return proxyActions;
} }
private static String buildMaintenanceWindowDisplayText(final Action action) {
return action.getMaintenanceWindowSchedule() + "/" + action.getMaintenanceWindowDuration() + "/"
+ action.getMaintenanceWindowTimeZone();
}
/** /**
* Generates a virtual IsActiveDecoration for the presentation layer that is * Generates a virtual IsActiveDecoration for the presentation layer that is
* calculated from {@link Action#isActive()} and * calculated from {@link Action#isActive()} and

View File

@@ -78,13 +78,14 @@ public class ActionHistoryGrid extends AbstractGrid<LazyQueryContainer> {
private static final Object[] maxColumnOrder = new Object[] { ProxyAction.PXY_ACTION_IS_ACTIVE_DECO, private static final Object[] maxColumnOrder = new Object[] { ProxyAction.PXY_ACTION_IS_ACTIVE_DECO,
ProxyAction.PXY_ACTION_ID, ProxyAction.PXY_ACTION_DS_NAME_VERSION, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT, ProxyAction.PXY_ACTION_ID, ProxyAction.PXY_ACTION_DS_NAME_VERSION, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT,
ProxyAction.PXY_ACTION_STATUS, ProxyAction.PXY_ACTION_ROLLOUT_NAME, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, ProxyAction.PXY_ACTION_STATUS, ProxyAction.PXY_ACTION_MAINTENANCE_WINDOW,
VIRT_PROP_ACTION_CANCEL, VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT }; ProxyAction.PXY_ACTION_ROLLOUT_NAME, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, VIRT_PROP_ACTION_CANCEL,
VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT };
private static final Object[] minColumnOrder = new Object[] { ProxyAction.PXY_ACTION_IS_ACTIVE_DECO, private static final Object[] minColumnOrder = new Object[] { ProxyAction.PXY_ACTION_IS_ACTIVE_DECO,
ProxyAction.PXY_ACTION_DS_NAME_VERSION, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT, ProxyAction.PXY_ACTION_DS_NAME_VERSION, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT,
ProxyAction.PXY_ACTION_STATUS, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, VIRT_PROP_ACTION_CANCEL, ProxyAction.PXY_ACTION_STATUS, ProxyAction.PXY_ACTION_MAINTENANCE_WINDOW, VIRT_PROP_FORCED,
VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT }; VIRT_PROP_TIMEFORCED, VIRT_PROP_ACTION_CANCEL, VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT };
private static final String[] leftAlignedColumns = new String[] { VIRT_PROP_TIMEFORCED }; private static final String[] leftAlignedColumns = new String[] { VIRT_PROP_TIMEFORCED };
@@ -99,7 +100,7 @@ public class ActionHistoryGrid extends AbstractGrid<LazyQueryContainer> {
private Target selectedTarget; private Target selectedTarget;
private final AlignCellStyleGenerator alignGenerator; private final AlignCellStyleGenerator alignGenerator;
private final ModifiedTimeTooltipGenerator modTimetooltipGenerator; private final TooltipGenerator tooltipGenerator;
private final Map<Action.Status, StatusFontIcon> states; private final Map<Action.Status, StatusFontIcon> states;
private final Map<IsActiveDecoration, StatusFontIcon> activeStates; private final Map<IsActiveDecoration, StatusFontIcon> activeStates;
@@ -139,7 +140,7 @@ public class ActionHistoryGrid extends AbstractGrid<LazyQueryContainer> {
activeStates = conf activeStates = conf
.createActiveStatusLabelConfig(UIComponentIdProvider.ACTION_HISTORY_TABLE_ACTIVESTATE_LABEL_ID); .createActiveStatusLabelConfig(UIComponentIdProvider.ACTION_HISTORY_TABLE_ACTIVESTATE_LABEL_ID);
alignGenerator = new AlignCellStyleGenerator(leftAlignedColumns, centerAlignedColumns, rightAlignedColumns); alignGenerator = new AlignCellStyleGenerator(leftAlignedColumns, centerAlignedColumns, rightAlignedColumns);
modTimetooltipGenerator = new ModifiedTimeTooltipGenerator(ProxyAction.PXY_ACTION_LAST_MODIFIED_AT); tooltipGenerator = new TooltipGenerator(i18n);
init(); init();
} }
@@ -208,6 +209,7 @@ public class ActionHistoryGrid extends AbstractGrid<LazyQueryContainer> {
rawCont.addContainerProperty(ProxyAction.PXY_ACTION_ID, String.class, null, true, true); rawCont.addContainerProperty(ProxyAction.PXY_ACTION_ID, String.class, null, true, true);
rawCont.addContainerProperty(ProxyAction.PXY_ACTION_ROLLOUT_NAME, String.class, null, true, true); rawCont.addContainerProperty(ProxyAction.PXY_ACTION_ROLLOUT_NAME, String.class, null, true, true);
rawCont.addContainerProperty(ProxyAction.PXY_ACTION_MAINTENANCE_WINDOW, String.class, null, true, false);
} }
@Override @Override
@@ -436,11 +438,14 @@ public class ActionHistoryGrid extends AbstractGrid<LazyQueryContainer> {
getColumn(VIRT_PROP_ACTION_CANCEL).setHidable(false); getColumn(VIRT_PROP_ACTION_CANCEL).setHidable(false);
getColumn(VIRT_PROP_ACTION_FORCE).setHidable(false); getColumn(VIRT_PROP_ACTION_FORCE).setHidable(false);
getColumn(VIRT_PROP_ACTION_FORCE_QUIT).setHidable(false); getColumn(VIRT_PROP_ACTION_FORCE_QUIT).setHidable(false);
getColumn(ProxyAction.PXY_ACTION_MAINTENANCE_WINDOW).setHidden(true);
getColumn(ProxyAction.PXY_ACTION_MAINTENANCE_WINDOW).setHidable(true);
} }
@Override @Override
protected CellDescriptionGenerator getDescriptionGenerator() { protected CellDescriptionGenerator getDescriptionGenerator() {
return modTimetooltipGenerator; return tooltipGenerator;
} }
@Override @Override
@@ -451,6 +456,8 @@ public class ActionHistoryGrid extends AbstractGrid<LazyQueryContainer> {
getColumn(ProxyAction.PXY_ACTION_DS_NAME_VERSION).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_DIST); getColumn(ProxyAction.PXY_ACTION_DS_NAME_VERSION).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_DIST);
getColumn(ProxyAction.PXY_ACTION_LAST_MODIFIED_AT).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_DATETIME); getColumn(ProxyAction.PXY_ACTION_LAST_MODIFIED_AT).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_DATETIME);
getColumn(ProxyAction.PXY_ACTION_STATUS).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_STATUS); getColumn(ProxyAction.PXY_ACTION_STATUS).setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_STATUS);
getColumn(ProxyAction.PXY_ACTION_MAINTENANCE_WINDOW)
.setHeaderCaption(SPUIDefinitions.ACTION_HIS_TBL_MAINTENANCE_WINDOW);
getColumn(VIRT_PROP_FORCED).setHeaderCaption(String.valueOf(forceClientRefreshToggle)); getColumn(VIRT_PROP_FORCED).setHeaderCaption(String.valueOf(forceClientRefreshToggle));
forceClientRefreshToggle = !forceClientRefreshToggle; forceClientRefreshToggle = !forceClientRefreshToggle;
@@ -463,8 +470,9 @@ public class ActionHistoryGrid extends AbstractGrid<LazyQueryContainer> {
protected void setColumnExpandRatio() { protected void setColumnExpandRatio() {
setColumnsSize(50.0, 50.0, ProxyAction.PXY_ACTION_IS_ACTIVE_DECO); setColumnsSize(50.0, 50.0, ProxyAction.PXY_ACTION_IS_ACTIVE_DECO);
setColumnsSize(107.0, 500.0, ProxyAction.PXY_ACTION_DS_NAME_VERSION); setColumnsSize(107.0, 500.0, ProxyAction.PXY_ACTION_DS_NAME_VERSION);
setColumnsSize(100.0, 120.0, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT); setColumnsSize(100.0, 130.0, ProxyAction.PXY_ACTION_LAST_MODIFIED_AT);
setColumnsSize(53.0, 55.0, ProxyAction.PXY_ACTION_STATUS); setColumnsSize(53.0, 55.0, ProxyAction.PXY_ACTION_STATUS);
setColumnsSize(150.0, 200.0, ProxyAction.PXY_ACTION_MAINTENANCE_WINDOW);
setColumnsSize(FIXED_PIX_MIN, FIXED_PIX_MIN, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, VIRT_PROP_ACTION_CANCEL, setColumnsSize(FIXED_PIX_MIN, FIXED_PIX_MIN, VIRT_PROP_FORCED, VIRT_PROP_TIMEFORCED, VIRT_PROP_ACTION_CANCEL,
VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT); VIRT_PROP_ACTION_FORCE, VIRT_PROP_ACTION_FORCE_QUIT);
} }

View File

@@ -36,7 +36,7 @@ public class ActionStatusGrid extends AbstractGrid<LazyQueryContainer> {
private static final String[] centerAlignedColumns = new String[] { ProxyActionStatus.PXY_AS_STATUS }; private static final String[] centerAlignedColumns = new String[] { ProxyActionStatus.PXY_AS_STATUS };
private final AlignCellStyleGenerator alignGenerator; private final AlignCellStyleGenerator alignGenerator;
private final ModifiedTimeTooltipGenerator modTimetooltipGenerator; private final TooltipGenerator tooltipGenerator;
private final Map<Action.Status, StatusFontIcon> states; private final Map<Action.Status, StatusFontIcon> states;
@@ -58,7 +58,7 @@ public class ActionStatusGrid extends AbstractGrid<LazyQueryContainer> {
final LabelConfig conf = new ActionHistoryGrid.LabelConfig(); final LabelConfig conf = new ActionHistoryGrid.LabelConfig();
states = conf.createStatusLabelConfig(i18n, UIComponentIdProvider.ACTION_STATUS_GRID_STATUS_LABEL_ID); states = conf.createStatusLabelConfig(i18n, UIComponentIdProvider.ACTION_STATUS_GRID_STATUS_LABEL_ID);
alignGenerator = new AlignCellStyleGenerator(leftAlignedColumns, centerAlignedColumns, null); alignGenerator = new AlignCellStyleGenerator(leftAlignedColumns, centerAlignedColumns, null);
modTimetooltipGenerator = new ModifiedTimeTooltipGenerator(ProxyActionStatus.PXY_AS_CREATED_AT); tooltipGenerator = new TooltipGenerator(i18n);
init(); init();
} }
@@ -153,7 +153,7 @@ public class ActionStatusGrid extends AbstractGrid<LazyQueryContainer> {
@Override @Override
protected CellDescriptionGenerator getDescriptionGenerator() { protected CellDescriptionGenerator getDescriptionGenerator() {
return modTimetooltipGenerator; return tooltipGenerator;
} }
} }

View File

@@ -26,6 +26,7 @@ public class ProxyAction implements Serializable {
public static final String PXY_ACTION = "action"; public static final String PXY_ACTION = "action";
public static final String PXY_ACTION_LAST_MODIFIED_AT = "lastModifiedAt"; public static final String PXY_ACTION_LAST_MODIFIED_AT = "lastModifiedAt";
public static final String PXY_ACTION_ROLLOUT_NAME = "rolloutName"; public static final String PXY_ACTION_ROLLOUT_NAME = "rolloutName";
public static final String PXY_ACTION_MAINTENANCE_WINDOW = "maintenanceWindow";
private Action.Status status; private Action.Status status;
private boolean isActive; private boolean isActive;
@@ -35,6 +36,15 @@ public class ProxyAction implements Serializable {
private Action action; private Action action;
private Long lastModifiedAt; private Long lastModifiedAt;
private String rolloutName; private String rolloutName;
private String maintenanceWindow;
public String getMaintenanceWindow() {
return maintenanceWindow;
}
public void setMaintenanceWindow(final String maintenanceWindow) {
this.maintenanceWindow = maintenanceWindow;
}
/** /**
* Get id for the entry. * Get id for the entry.
@@ -51,7 +61,7 @@ public class ProxyAction implements Serializable {
* @param id * @param id
* of the action entry. * of the action entry.
*/ */
public void setId(Long id) { public void setId(final Long id) {
this.id = id; this.id = id;
} }
@@ -70,7 +80,7 @@ public class ProxyAction implements Serializable {
* @param status * @param status
* literal * literal
*/ */
public void setStatus(Action.Status status) { public void setStatus(final Action.Status status) {
this.status = status; this.status = status;
} }
@@ -91,7 +101,7 @@ public class ProxyAction implements Serializable {
* <code>true</code> if the action is active, otherwise * <code>true</code> if the action is active, otherwise
* <code>false</code> * <code>false</code>
*/ */
public void setActive(boolean isActive) { public void setActive(final boolean isActive) {
this.isActive = isActive; this.isActive = isActive;
} }
@@ -113,7 +123,7 @@ public class ProxyAction implements Serializable {
* @param isActiveDecoration * @param isActiveDecoration
* pre-calculated literal * pre-calculated literal
*/ */
public void setIsActiveDecoration(IsActiveDecoration isActiveDecoration) { public void setIsActiveDecoration(final IsActiveDecoration isActiveDecoration) {
this.isActiveDecoration = isActiveDecoration; this.isActiveDecoration = isActiveDecoration;
} }
@@ -133,7 +143,7 @@ public class ProxyAction implements Serializable {
* @param dsNameVersion * @param dsNameVersion
* combined value * combined value
*/ */
public void setDsNameVersion(String dsNameVersion) { public void setDsNameVersion(final String dsNameVersion) {
this.dsNameVersion = dsNameVersion; this.dsNameVersion = dsNameVersion;
} }
@@ -151,7 +161,7 @@ public class ProxyAction implements Serializable {
* *
* @param action * @param action
*/ */
public void setAction(Action action) { public void setAction(final Action action) {
this.action = action; this.action = action;
} }
@@ -170,7 +180,7 @@ public class ProxyAction implements Serializable {
* @param lastModifiedAt * @param lastModifiedAt
* raw long-value for lastModifiedAt-date * raw long-value for lastModifiedAt-date
*/ */
public void setLastModifiedAt(Long lastModifiedAt) { public void setLastModifiedAt(final Long lastModifiedAt) {
this.lastModifiedAt = lastModifiedAt; this.lastModifiedAt = lastModifiedAt;
} }
@@ -188,7 +198,7 @@ public class ProxyAction implements Serializable {
* *
* @param rolloutName * @param rolloutName
*/ */
public void setRolloutName(String rolloutName) { public void setRolloutName(final String rolloutName) {
this.rolloutName = rolloutName; this.rolloutName = rolloutName;
} }

View File

@@ -84,7 +84,7 @@ public class MaintenanceWindowLayout extends VerticalLayout {
* Text field to specify the schedule. * Text field to specify the schedule.
*/ */
private void createMaintenanceScheduleControl() { private void createMaintenanceScheduleControl() {
schedule = new TextFieldBuilder(Action.MAINTENANCE_SCHEDULE_CRON_LENGTH) schedule = new TextFieldBuilder(Action.MAINTENANCE_WINDOW_SCHEDULE_LENGTH)
.id(UIComponentIdProvider.MAINTENANCE_WINDOW_SCHEDULE_ID) .id(UIComponentIdProvider.MAINTENANCE_WINDOW_SCHEDULE_ID)
.caption(i18n.getMessage("caption.maintenancewindow.schedule")).validator(new CronValidator()) .caption(i18n.getMessage("caption.maintenancewindow.schedule")).validator(new CronValidator())
.prompt("0 0 3 ? * 6").required(true, i18n).buildTextComponent(); .prompt("0 0 3 ? * 6").required(true, i18n).buildTextComponent();
@@ -259,6 +259,7 @@ public class MaintenanceWindowLayout extends VerticalLayout {
schedule.setValue(""); schedule.setValue("");
duration.setValue(""); duration.setValue("");
timeZone.setValue(getClientTimeZone()); timeZone.setValue(getClientTimeZone());
scheduleTranslator.setValue(i18n.getMessage(CRON_VALIDATION_ERROR));
} }
/** /**

View File

@@ -561,6 +561,8 @@ public final class SPUIDefinitions {
*/ */
public static final String SP_BUTTON_STATUS_STYLE = "targetStatusBtn"; public static final String SP_BUTTON_STATUS_STYLE = "targetStatusBtn";
public static final String ACTION_HIS_TBL_MAINTENANCE_WINDOW = "Maintenance Window";
/** /**
* /** Constructor. * /** Constructor.
*/ */

View File

@@ -251,6 +251,7 @@ tooltip.check.for.mandatory=Check to make Mandatory
tooltip.artifact.icon=Show Artifact Details tooltip.artifact.icon=Show Artifact Details
tooltip.click.to.edit = Click to edit tooltip.click.to.edit = Click to edit
tooltip.metadata.icon = Manage Metadata tooltip.metadata.icon = Manage Metadata
tooltip.next.maintenancewindow = next on {0}
#rollout action #rollout action
tooltip.rollout.run = Run tooltip.rollout.run = Run
tooltip.rollout.pause = Pause tooltip.rollout.pause = Pause