Add timestamp to Actions (#2113)

* Add timestamp to Actions

Signed-off-by: Vasil Ilchev <Vasil.Ilchev@bosch.com>

* Add Timestamp to All Actions Feedback DDI/DMF

* After review

* Removed Action timestamp as we have timestamp in each ActionStatus so use that instead

* Unify to use everywhere System.currentTimeMillis()

* Add constructor w/o timestamp to DmfActionUpdateStatus

---------

Signed-off-by: Vasil Ilchev <Vasil.Ilchev@bosch.com>
Co-authored-by: vasilchev <vasil.ilchev@bosch.com>
This commit is contained in:
Vasil Ilchev
2024-12-04 09:53:10 +02:00
committed by GitHub
parent 58e427d1ea
commit b4215a9d76
17 changed files with 96 additions and 64 deletions

View File

@@ -31,7 +31,7 @@ import lombok.Data;
*
* <p>
* The answer header would look like: {
* "time": "20140511T121314",
* "timestamp": "1733218554123",
* "status": {
* "execution": "closed",
* "result": {
@@ -47,8 +47,8 @@ import lombok.Data;
@JsonIgnoreProperties(ignoreUnknown = true)
public class DdiActionFeedback {
@Schema(description = "Timestamp of the action", example = "2023-08-03T12:31:41.890992967Z")
private final String time;
@Schema(description = "Timestamp of the action in milliseconds since epoch", example = "1627997501890")
private final Long timestamp;
@NotNull
@Valid
@@ -57,14 +57,19 @@ public class DdiActionFeedback {
/**
* Constructs an action-feedback
*
* @param time time of feedback
* @param timestamp time of feedback
* @param status status to be appended to the action
*/
@JsonCreator
public DdiActionFeedback(
@JsonProperty(value = "time") final String time,
@JsonProperty(value = "status", required = true) final DdiStatus status) {
this.time = time;
@JsonProperty(value = "status", required = true) final DdiStatus status,
@JsonProperty(value = "timestamp") final Long timestamp) {
this.status = status;
this.timestamp = timestamp != null ? timestamp : System.currentTimeMillis();
}
public DdiActionFeedback(final DdiStatus status) {
this(status, null);
}
}

View File

@@ -39,7 +39,7 @@ class DdiActionFeedbackTest {
void shouldSerializeAndDeserializeObjectWithoutOptionalValues() throws IOException {
// Setup
final DdiStatus ddiStatus = new DdiStatus(DdiStatus.ExecutionStatus.CLOSED, null, null, Collections.emptyList());
final DdiActionFeedback ddiActionFeedback = new DdiActionFeedback(null, ddiStatus);
final DdiActionFeedback ddiActionFeedback = new DdiActionFeedback(ddiStatus);
// Test
final String serializedDdiActionFeedback = mapper.writeValueAsString(ddiActionFeedback);
@@ -53,17 +53,16 @@ class DdiActionFeedbackTest {
@Description("Verify the correct serialization and deserialization of the model with all values provided")
void shouldSerializeAndDeserializeObjectWithOptionalValues() throws IOException {
// Setup
final String time = Instant.now().toString();
final Long timestamp = System.currentTimeMillis();
final DdiResult ddiResult = new DdiResult(DdiResult.FinalResult.SUCCESS, new DdiProgress(10, 10));
final DdiStatus ddiStatus = new DdiStatus(DdiStatus.ExecutionStatus.CLOSED, ddiResult, 200, Collections.singletonList("myMessage"));
final DdiActionFeedback ddiActionFeedback = new DdiActionFeedback(time, ddiStatus);
final DdiActionFeedback ddiActionFeedback = new DdiActionFeedback(ddiStatus, timestamp);
// Test
final String serializedDdiActionFeedback = mapper.writeValueAsString(ddiActionFeedback);
final DdiActionFeedback deserializedDdiActionFeedback = mapper.readValue(serializedDdiActionFeedback, DdiActionFeedback.class);
assertThat(serializedDdiActionFeedback).contains(time);
assertThat(deserializedDdiActionFeedback.getTime()).isEqualTo(time);
assertThat(deserializedDdiActionFeedback.getTimestamp()).isEqualTo(timestamp);
assertThat(deserializedDdiActionFeedback.getStatus()).hasToString(ddiStatus.toString());
}
@@ -71,28 +70,38 @@ class DdiActionFeedbackTest {
@Description("Verify that deserialization fails for known properties with a wrong datatype")
void shouldFailForObjectWithWrongDataTypes() throws IOException {
// Setup
final String serializedDdiActionFeedback = "{\"time\":\"20190809T121314\",\"status\":{\"execution\": [closed],\"result\":null,\"details\":[]}}";
final String serializedDdiActionFeedback = """
{
"timestamp" : "1627997501890",
"status" : {
"execution" : "[closed]",
"result" : null,
"details" : []
}
}
""";
assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(
() -> mapper.readValue(serializedDdiActionFeedback, DdiActionFeedback.class));
}
@Test
@Description("Verify that deserialization works if optional fields are not parsed")
void shouldConvertItWithoutOptionalFieldTime() throws JsonProcessingException {
void shouldConvertItWithoutOptionalFieldTimestamp() throws JsonProcessingException {
// Setup
final String serializedDdiActionFeedback = "{\n" + //
" \"status\" : {\n" + //
" \"result\" : {\n" + //
" \"finished\" : \"none\"\n" + //
" },\n" + //
" \"execution\" : \"download\",\n" + //
" \"details\" : [ \"Some message\" ]\n" + //
" }\n" + //
"}";//
final String serializedDdiActionFeedback = """
{
"status" : {
"result" : {
"finished" : "none"
},
"execution" : "download",
"details" : [ "Some message" ]
}
}
""";
assertThat(mapper.readValue(serializedDdiActionFeedback, DdiActionFeedback.class)).satisfies(deserializedDdiActionFeedback -> {
assertThat(deserializedDdiActionFeedback.getTime()).isNull();
assertThat(deserializedDdiActionFeedback.getTimestamp()).isNotNull();
assertThat(deserializedDdiActionFeedback.getStatus()).isNotNull();
assertThat(deserializedDdiActionFeedback.getStatus().getResult()).isNotNull();
assertThat(deserializedDdiActionFeedback.getStatus().getResult().getFinished()).isEqualTo(DdiResult.FinalResult.NONE);

View File

@@ -503,7 +503,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
private static ActionStatusCreate generateActionCancelStatus(
final DdiActionFeedback feedback, final Target target, final Long actionId, final EntityFactory entityFactory) {
final ActionStatusCreate actionStatusCreate = entityFactory.actionStatus().create(actionId);
final ActionStatusCreate actionStatusCreate = entityFactory.actionStatus().create(actionId).occurredAt(feedback.getTimestamp());
final List<String> messages = new ArrayList<>();
final Status status;
switch (feedback.getStatus().getExecution()) {
@@ -616,7 +616,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
}
private ActionStatusCreate generateUpdateStatus(final DdiActionFeedback feedback, final String controllerId, final Long actionId) {
final ActionStatusCreate actionStatusCreate = entityFactory.actionStatus().create(actionId);
final ActionStatusCreate actionStatusCreate = entityFactory.actionStatus().create(actionId).occurredAt(feedback.getTimestamp());
final List<String> messages = new ArrayList<>();
if (!CollectionUtils.isEmpty(feedback.getStatus().getDetails())) {

View File

@@ -295,7 +295,7 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
final DdiStatus.ExecutionStatus executionStatus, final DdiResult ddiResult,
final List<String> messages) throws JsonProcessingException {
final DdiStatus ddiStatus = new DdiStatus(executionStatus, ddiResult, null, messages);
return OBJECT_MAPPER.writeValueAsString(new DdiActionFeedback(Instant.now().toString(), ddiStatus));
return OBJECT_MAPPER.writeValueAsString(new DdiActionFeedback(ddiStatus));
}
protected String getJsonActionFeedback(
@@ -308,7 +308,7 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
final DdiStatus.ExecutionStatus executionStatus,
final DdiResult.FinalResult finalResult, final Integer code, final List<String> messages) throws JsonProcessingException {
final DdiStatus ddiStatus = new DdiStatus(executionStatus, new DdiResult(finalResult, new DdiProgress(2, 5)), code, messages);
return OBJECT_MAPPER.writeValueAsString(new DdiActionFeedback(Instant.now().toString(), ddiStatus));
return OBJECT_MAPPER.writeValueAsString(new DdiActionFeedback(ddiStatus));
}
protected String getJsonConfirmationFeedback(