From 7f289737b69c4fe60d661532eadaf8d457563971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20F=C4=85derski?= Date: Tue, 19 Nov 2024 11:31:27 +0100 Subject: [PATCH] Improve timerange validation for retransmission from view (#1916) * Improve timerange validation for retransmission from view * Google java format fix * Add button/link to the retransmission from view documentation, shown in the retransmission popup * Fix code docs * Fix code docs * Remove accidental changes * Fix ui code * Linter fixes * Review fixes * CR fixes * Introduce retransmission type * Fix hermes-console --- ...OfflineRetransmissionFromTopicRequest.java | 59 ++++++ .../OfflineRetransmissionFromViewRequest.java | 33 ++++ .../api/OfflineRetransmissionRequest.java | 92 +++------ .../hermes/api/OfflineRetransmissionTask.java | 114 +++++++---- .../OneSourceRetransmissionValidator.java | 26 --- ...a => TimeRangeForTopicRetransmission.java} | 6 +- ...eRangeForTopicRetransmissionValidator.java | 23 +++ .../jackson/OptionalInstantIsoSerializer.java | 23 +++ ...neSourceRetransmissionValidatorTest.groovy | 37 ---- ...etransmissionTimeRangeValidatorTest.groovy | 43 ++++ hermes-console/json-server/db.json | 3 +- hermes-console/src/api/app-configuration.ts | 1 + .../src/api/offline-retransmission.ts | 1 + hermes-console/src/dummy/app-config.ts | 1 + hermes-console/src/i18n/en-US/index.ts | 1 + .../OfflineRetransmissionForm.spec.ts | 30 ++- .../OfflineRetransmissionForm.vue | 17 +- .../views/topic/topic-header/TopicHeader.vue | 2 + .../api/OfflineRetransmissionEndpoint.java | 66 +++++-- .../config/console/ConsoleProperties.java | 10 + .../OfflineRetransmissionService.java | 71 ++++--- .../client/integration/HermesTestClient.java | 10 +- .../OfflineRetransmissionManagementTest.java | 185 ++++++++++++++---- 23 files changed, 601 insertions(+), 253 deletions(-) create mode 100644 hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionFromTopicRequest.java create mode 100644 hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionFromViewRequest.java delete mode 100644 hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmissionValidator.java rename hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/{OneSourceRetransmission.java => TimeRangeForTopicRetransmission.java} (64%) create mode 100644 hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/TimeRangeForTopicRetransmissionValidator.java create mode 100644 hermes-api/src/main/java/pl/allegro/tech/hermes/api/jackson/OptionalInstantIsoSerializer.java delete mode 100644 hermes-api/src/test/groovy/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmissionValidatorTest.groovy create mode 100644 hermes-api/src/test/groovy/pl/allegro/tech/hermes/api/constraints/RetransmissionTimeRangeValidatorTest.groovy diff --git a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionFromTopicRequest.java b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionFromTopicRequest.java new file mode 100644 index 0000000000..dc257f9b19 --- /dev/null +++ b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionFromTopicRequest.java @@ -0,0 +1,59 @@ +package pl.allegro.tech.hermes.api; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import jakarta.validation.constraints.NotNull; +import java.time.Instant; +import pl.allegro.tech.hermes.api.constraints.TimeRangeForTopicRetransmission; +import pl.allegro.tech.hermes.api.jackson.InstantIsoSerializer; + +@TimeRangeForTopicRetransmission +public final class OfflineRetransmissionFromTopicRequest extends OfflineRetransmissionRequest { + + @NotNull private final String sourceTopic; + @NotNull private final Instant startTimestamp; + @NotNull private final Instant endTimestamp; + + @JsonCreator + public OfflineRetransmissionFromTopicRequest( + @JsonProperty("sourceTopic") String sourceTopic, + @JsonProperty("targetTopic") String targetTopic, + @JsonProperty("startTimestamp") String startTimestamp, + @JsonProperty("endTimestamp") String endTimestamp) { + super(RetransmissionType.TOPIC, targetTopic); + this.sourceTopic = sourceTopic; + this.startTimestamp = initializeTimestamp(startTimestamp); + this.endTimestamp = initializeTimestamp(endTimestamp); + } + + public String getSourceTopic() { + return sourceTopic; + } + + @JsonSerialize(using = InstantIsoSerializer.class) + public Instant getStartTimestamp() { + return startTimestamp; + } + + @JsonSerialize(using = InstantIsoSerializer.class) + public Instant getEndTimestamp() { + return endTimestamp; + } + + @Override + public String toString() { + return "OfflineRetransmissionFromTopicRequest{" + + "sourceTopic='" + + sourceTopic + + '\'' + + ", targetTopic='" + + getTargetTopic() + + '\'' + + ", startTimestamp=" + + startTimestamp + + ", endTimestamp=" + + endTimestamp + + '}'; + } +} diff --git a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionFromViewRequest.java b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionFromViewRequest.java new file mode 100644 index 0000000000..c46edc4906 --- /dev/null +++ b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionFromViewRequest.java @@ -0,0 +1,33 @@ +package pl.allegro.tech.hermes.api; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public final class OfflineRetransmissionFromViewRequest extends OfflineRetransmissionRequest { + + private final String sourceViewPath; + + @JsonCreator + public OfflineRetransmissionFromViewRequest( + @JsonProperty("sourceViewPath") String sourceViewPath, + @JsonProperty("targetTopic") String targetTopic) { + super(RetransmissionType.VIEW, targetTopic); + this.sourceViewPath = sourceViewPath; + } + + public String getSourceViewPath() { + return sourceViewPath; + } + + @Override + public String toString() { + return "OfflineRetransmissionFromViewRequest{" + + "sourceViewPath='" + + sourceViewPath + + '\'' + + ", targetTopic='" + + getTargetTopic() + + '\'' + + '}'; + } +} diff --git a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionRequest.java b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionRequest.java index dfa21e8569..d49e0211aa 100644 --- a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionRequest.java +++ b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionRequest.java @@ -1,23 +1,23 @@ package pl.allegro.tech.hermes.api; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; import java.util.List; -import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import pl.allegro.tech.hermes.api.constraints.OneSourceRetransmission; -import pl.allegro.tech.hermes.api.jackson.InstantIsoSerializer; -@OneSourceRetransmission -public class OfflineRetransmissionRequest { +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = OfflineRetransmissionFromViewRequest.class, name = "view"), + @JsonSubTypes.Type(value = OfflineRetransmissionFromTopicRequest.class, name = "topic") +}) +public sealed class OfflineRetransmissionRequest + permits OfflineRetransmissionFromTopicRequest, OfflineRetransmissionFromViewRequest { private static final List formatters = List.of( @@ -26,27 +26,28 @@ public class OfflineRetransmissionRequest { DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm'Z'").withZone(ZoneId.of("UTC"))); private static final Logger logger = LoggerFactory.getLogger(OfflineRetransmissionRequest.class); - private final String sourceViewPath; - private final String sourceTopic; + private final RetransmissionType type; @NotEmpty private final String targetTopic; - @NotNull private Instant startTimestamp; - @NotNull private Instant endTimestamp; - @JsonCreator - public OfflineRetransmissionRequest( - @JsonProperty("sourceViewPath") String sourceViewPath, - @JsonProperty("sourceTopic") String sourceTopic, - @JsonProperty("targetTopic") String targetTopic, - @JsonProperty("startTimestamp") String startTimestamp, - @JsonProperty("endTimestamp") String endTimestamp) { - this.sourceViewPath = sourceViewPath; - this.sourceTopic = sourceTopic; + public OfflineRetransmissionRequest(RetransmissionType type, String targetTopic) { + this.type = type; this.targetTopic = targetTopic; - this.startTimestamp = initializeTimestamp(startTimestamp); - this.endTimestamp = initializeTimestamp(endTimestamp); } - private Instant initializeTimestamp(String timestamp) { + public RetransmissionType getType() { + return type; + } + + public String getTargetTopic() { + return targetTopic; + } + + public enum RetransmissionType { + VIEW, + TOPIC + } + + public static Instant initializeTimestamp(String timestamp) { if (timestamp == null) { return null; } @@ -62,45 +63,4 @@ private Instant initializeTimestamp(String timestamp) { logger.warn("Provided date [{}] has an invalid format", timestamp); return null; } - - public Optional getSourceViewPath() { - return Optional.ofNullable(sourceViewPath); - } - - public Optional getSourceTopic() { - return Optional.ofNullable(sourceTopic); - } - - public String getTargetTopic() { - return targetTopic; - } - - @JsonSerialize(using = InstantIsoSerializer.class) - public Instant getStartTimestamp() { - return startTimestamp; - } - - @JsonSerialize(using = InstantIsoSerializer.class) - public Instant getEndTimestamp() { - return endTimestamp; - } - - @Override - public String toString() { - return "OfflineRetransmissionRequest{" - + "sourceTopic='" - + sourceTopic - + '\'' - + ", sourceViewPath='" - + sourceViewPath - + '\'' - + ", targetTopic='" - + targetTopic - + '\'' - + ", startTimestamp=" - + startTimestamp - + ", endTimestamp=" - + endTimestamp - + '}'; - } } diff --git a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionTask.java b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionTask.java index a94a6d54c9..9bc3382fe1 100644 --- a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionTask.java +++ b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/OfflineRetransmissionTask.java @@ -1,43 +1,67 @@ package pl.allegro.tech.hermes.api; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import jakarta.annotation.Nullable; import java.time.Instant; +import java.util.Objects; import java.util.Optional; +import pl.allegro.tech.hermes.api.OfflineRetransmissionRequest.RetransmissionType; import pl.allegro.tech.hermes.api.jackson.InstantIsoSerializer; +import pl.allegro.tech.hermes.api.jackson.OptionalInstantIsoSerializer; public class OfflineRetransmissionTask { + private final RetransmissionType type; private final String taskId; - private final OfflineRetransmissionRequest request; + @Nullable private final String sourceViewPath; + @Nullable private final String sourceTopic; + private final String targetTopic; + @Nullable private final Instant startTimestamp; + @Nullable private final Instant endTimestamp; private final Instant createdAt; - @JsonCreator public OfflineRetransmissionTask( - @JsonProperty("taskId") String taskId, - @JsonProperty("sourceViewPath") String sourceViewPath, - @JsonProperty("sourceTopic") String sourceTopic, - @JsonProperty("targetTopic") String targetTopic, - @JsonProperty("startTimestamp") Instant startTimestamp, - @JsonProperty("endTimestamp") Instant endTimestamp, - @JsonProperty("createdAt") Instant createdAt) { - this( - taskId, - new OfflineRetransmissionRequest( - sourceViewPath, - sourceTopic, - targetTopic, - startTimestamp.toString(), - endTimestamp.toString()), - createdAt); + RetransmissionType type, + String taskId, + @Nullable String sourceViewPath, + @Nullable String sourceTopic, + String targetTopic, + @Nullable Instant startTimestamp, + @Nullable Instant endTimestamp, + Instant createdAt) { + this.taskId = taskId; + this.sourceViewPath = sourceViewPath; + this.sourceTopic = sourceTopic; + this.targetTopic = targetTopic; + this.startTimestamp = startTimestamp; + this.endTimestamp = endTimestamp; + this.createdAt = createdAt; + this.type = type; } + @JsonCreator public OfflineRetransmissionTask( - String taskId, OfflineRetransmissionRequest request, Instant createdAt) { + @JsonProperty("type") @Nullable RetransmissionType type, + @JsonProperty("taskId") String taskId, + @JsonProperty("sourceViewPath") @Nullable String sourceViewPath, + @JsonProperty("sourceTopic") @Nullable String sourceTopic, + @JsonProperty("targetTopic") String targetTopic, + @JsonProperty("startTimestamp") @Nullable String startTimestamp, + @JsonProperty("endTimestamp") @Nullable String endTimestamp, + @JsonProperty("createdAt") String createdAt) { + /* + TODO: Needed for backward compatibility when reading existing retransmissions from zookeeper, remove this once the + new version is rolled out to all environments. + */ + this.type = Objects.requireNonNullElse(type, RetransmissionType.TOPIC); this.taskId = taskId; - this.request = request; - this.createdAt = createdAt; + this.sourceViewPath = sourceViewPath; + this.sourceTopic = sourceTopic; + this.targetTopic = targetTopic; + this.startTimestamp = OfflineRetransmissionFromTopicRequest.initializeTimestamp(startTimestamp); + this.endTimestamp = OfflineRetransmissionFromTopicRequest.initializeTimestamp(endTimestamp); + this.createdAt = OfflineRetransmissionFromTopicRequest.initializeTimestamp(createdAt); } public String getTaskId() { @@ -45,25 +69,25 @@ public String getTaskId() { } public Optional getSourceTopic() { - return request.getSourceTopic(); + return Optional.ofNullable(sourceTopic); } public Optional getSourceViewPath() { - return request.getSourceViewPath(); + return Optional.ofNullable(sourceViewPath); } public String getTargetTopic() { - return request.getTargetTopic(); + return targetTopic; } - @JsonSerialize(using = InstantIsoSerializer.class) - public Instant getStartTimestamp() { - return request.getStartTimestamp(); + @JsonSerialize(using = OptionalInstantIsoSerializer.class) + public Optional getStartTimestamp() { + return Optional.ofNullable(startTimestamp); } - @JsonSerialize(using = InstantIsoSerializer.class) - public Instant getEndTimestamp() { - return request.getEndTimestamp(); + @JsonSerialize(using = OptionalInstantIsoSerializer.class) + public Optional getEndTimestamp() { + return Optional.ofNullable(endTimestamp); } @JsonSerialize(using = InstantIsoSerializer.class) @@ -71,13 +95,33 @@ public Instant getCreatedAt() { return createdAt; } - @JsonIgnore - public OfflineRetransmissionRequest getRequest() { - return request; + public RetransmissionType getType() { + return type; } @Override public String toString() { - return "OfflineRetransmissionTask{" + "taskId='" + taskId + '\'' + ", request=" + request + '}'; + return "OfflineRetransmissionTask{" + + "type=" + + type + + ", taskId='" + + taskId + + '\'' + + ", sourceViewPath='" + + sourceViewPath + + '\'' + + ", sourceTopic='" + + sourceTopic + + '\'' + + ", targetTopic='" + + targetTopic + + '\'' + + ", startTimestamp=" + + startTimestamp + + ", endTimestamp=" + + endTimestamp + + ", createdAt=" + + createdAt + + '}'; } } diff --git a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmissionValidator.java b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmissionValidator.java deleted file mode 100644 index f0a89a888c..0000000000 --- a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmissionValidator.java +++ /dev/null @@ -1,26 +0,0 @@ -package pl.allegro.tech.hermes.api.constraints; - -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; -import pl.allegro.tech.hermes.api.OfflineRetransmissionRequest; - -public class OneSourceRetransmissionValidator - implements ConstraintValidator { - - public static final String EMPTY_STRING = ""; - - @Override - public boolean isValid( - OfflineRetransmissionRequest offlineRetransmissionRequest, - ConstraintValidatorContext context) { - var sourceViewPath = offlineRetransmissionRequest.getSourceViewPath(); - var sourceTopic = offlineRetransmissionRequest.getSourceTopic(); - - return (nonBlank(sourceViewPath.orElse(EMPTY_STRING)) && sourceTopic.isEmpty()) - || (nonBlank(sourceTopic.orElse(EMPTY_STRING)) && sourceViewPath.isEmpty()); - } - - private static boolean nonBlank(String value) { - return value != null && !value.isBlank(); - } -} diff --git a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmission.java b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/TimeRangeForTopicRetransmission.java similarity index 64% rename from hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmission.java rename to hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/TimeRangeForTopicRetransmission.java index 4c7fda0a5d..f33bb3af57 100644 --- a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmission.java +++ b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/TimeRangeForTopicRetransmission.java @@ -11,10 +11,10 @@ @Documented @Retention(RetentionPolicy.RUNTIME) @Target({TYPE}) -@Constraint(validatedBy = OneSourceRetransmissionValidator.class) -public @interface OneSourceRetransmission { +@Constraint(validatedBy = TimeRangeForTopicRetransmissionValidator.class) +public @interface TimeRangeForTopicRetransmission { String message() default - "must contain one defined source of retransmission data - source topic or source view"; + "Must contain both startTimestamp and endTimestamp for topic retransmission. StartTimestamp must be lower than endTimestamp"; Class[] groups() default {}; diff --git a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/TimeRangeForTopicRetransmissionValidator.java b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/TimeRangeForTopicRetransmissionValidator.java new file mode 100644 index 0000000000..aca9a89d8b --- /dev/null +++ b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/constraints/TimeRangeForTopicRetransmissionValidator.java @@ -0,0 +1,23 @@ +package pl.allegro.tech.hermes.api.constraints; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import pl.allegro.tech.hermes.api.OfflineRetransmissionFromTopicRequest; + +public class TimeRangeForTopicRetransmissionValidator + implements ConstraintValidator< + TimeRangeForTopicRetransmission, OfflineRetransmissionFromTopicRequest> { + + @Override + public boolean isValid( + OfflineRetransmissionFromTopicRequest offlineRetransmissionRequest, + ConstraintValidatorContext context) { + var startTimestamp = offlineRetransmissionRequest.getStartTimestamp(); + var endTimestamp = offlineRetransmissionRequest.getEndTimestamp(); + + if (startTimestamp == null || endTimestamp == null) { + return false; + } + return startTimestamp.isBefore(endTimestamp); + } +} diff --git a/hermes-api/src/main/java/pl/allegro/tech/hermes/api/jackson/OptionalInstantIsoSerializer.java b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/jackson/OptionalInstantIsoSerializer.java new file mode 100644 index 0000000000..90b59d3337 --- /dev/null +++ b/hermes-api/src/main/java/pl/allegro/tech/hermes/api/jackson/OptionalInstantIsoSerializer.java @@ -0,0 +1,23 @@ +package pl.allegro.tech.hermes.api.jackson; + +import static java.time.format.DateTimeFormatter.ISO_INSTANT; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import java.io.IOException; +import java.time.Instant; +import java.util.Optional; + +public class OptionalInstantIsoSerializer extends JsonSerializer> { + + @Override + public void serialize(Optional value, JsonGenerator jgen, SerializerProvider provider) + throws IOException { + if (value.isPresent()) { + jgen.writeString(ISO_INSTANT.format(value.get())); + } else { + jgen.writeNull(); + } + } +} diff --git a/hermes-api/src/test/groovy/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmissionValidatorTest.groovy b/hermes-api/src/test/groovy/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmissionValidatorTest.groovy deleted file mode 100644 index e7aa7a42c3..0000000000 --- a/hermes-api/src/test/groovy/pl/allegro/tech/hermes/api/constraints/OneSourceRetransmissionValidatorTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package pl.allegro.tech.hermes.api.constraints - - -import jakarta.validation.ConstraintValidatorContext -import pl.allegro.tech.hermes.api.OfflineRetransmissionRequest -import spock.lang.Specification - -class OneSourceRetransmissionValidatorTest extends Specification { - - OneSourceRetransmissionValidator validator = new OneSourceRetransmissionValidator() - ConstraintValidatorContext mockContext = Mock() - - def "Validator should validate retransmission request when sourceViewPath is '#sourceViewPath' and sourceTopic is '#sourceTopic'"() { - given: - def request = new OfflineRetransmissionRequest( - sourceViewPath, - sourceTopic, - "someTargetTopic", - "2024-07-08T12:00:00", - "2024-07-08T13:00:00" - ) - expect: - validator.isValid(request, mockContext) == isValid - - where: - sourceViewPath | sourceTopic | isValid - null | "testTopic" | true - "testView" | null | true - null | null | false - "testView" | "testTopic" | false - "" | "" | false - " " | " " | false - "" | "testTopic" | false - "testView" | " " | false - } - -} diff --git a/hermes-api/src/test/groovy/pl/allegro/tech/hermes/api/constraints/RetransmissionTimeRangeValidatorTest.groovy b/hermes-api/src/test/groovy/pl/allegro/tech/hermes/api/constraints/RetransmissionTimeRangeValidatorTest.groovy new file mode 100644 index 0000000000..8773b2fa10 --- /dev/null +++ b/hermes-api/src/test/groovy/pl/allegro/tech/hermes/api/constraints/RetransmissionTimeRangeValidatorTest.groovy @@ -0,0 +1,43 @@ +package pl.allegro.tech.hermes.api.constraints + + +import jakarta.validation.ConstraintValidatorContext +import pl.allegro.tech.hermes.api.OfflineRetransmissionFromTopicRequest +import spock.lang.Shared +import spock.lang.Specification +import spock.lang.Unroll + +import java.time.Instant + +class RetransmissionTimeRangeValidatorTest extends Specification { + + ConstraintValidatorContext mockContext = Mock() + + @Shared + def lowerTimestamp = Instant.now().toString() + @Shared + def higherTimestamp = Instant.now().plusSeconds(5).toString() + + @Unroll + def "Time range validator should validate topic retransmission request when startTimestamp is '#startTimestamp', endTimestamp is '#endTimestamp'"() { + given: + TimeRangeForTopicRetransmissionValidator validator = new TimeRangeForTopicRetransmissionValidator() + def request = new OfflineRetransmissionFromTopicRequest( + "someSourceTopic", + "someTargetTopic", + startTimestamp, + endTimestamp + ) + expect: + validator.isValid(request, mockContext) == isValid + + where: + startTimestamp | endTimestamp | isValid + lowerTimestamp | higherTimestamp | true + null | higherTimestamp | false + lowerTimestamp | null | false + null | null | false + higherTimestamp | lowerTimestamp | false + lowerTimestamp | lowerTimestamp | false + } +} diff --git a/hermes-console/json-server/db.json b/hermes-console/json-server/db.json index 23dab13ee0..cb179446ae 100644 --- a/hermes-console/json-server/db.json +++ b/hermes-console/json-server/db.json @@ -662,7 +662,8 @@ } ], "offlineRetransmissionEnabled": true, - "offlineRetransmissionDescription": "Offline retransmission" + "offlineRetransmissionDescription": "Offline retransmission", + "offlineRetransmissionFromViewDocsUrl": "https://hermes-pubsub.rtfd.org" }, "subscription": { "endpointAddressResolverMetadata": { diff --git a/hermes-console/src/api/app-configuration.ts b/hermes-console/src/api/app-configuration.ts index e1eb50b32f..f158aea20b 100644 --- a/hermes-console/src/api/app-configuration.ts +++ b/hermes-console/src/api/app-configuration.ts @@ -84,6 +84,7 @@ export interface TopicViewConfiguration { retentionUnits: RetentionUnit[]; offlineRetransmissionEnabled: boolean; offlineRetransmissionDescription: string; + offlineRetransmissionFromViewDocsUrl: string; } export interface DefaultTopicViewConfiguration { diff --git a/hermes-console/src/api/offline-retransmission.ts b/hermes-console/src/api/offline-retransmission.ts index c6c9a146f6..4030773730 100644 --- a/hermes-console/src/api/offline-retransmission.ts +++ b/hermes-console/src/api/offline-retransmission.ts @@ -1,4 +1,5 @@ export interface OfflineRetransmissionTask { + type: string; sourceTopic: string; targetTopic: string; startTimestamp: string; diff --git a/hermes-console/src/dummy/app-config.ts b/hermes-console/src/dummy/app-config.ts index a5166433e7..99e46e85c4 100644 --- a/hermes-console/src/dummy/app-config.ts +++ b/hermes-console/src/dummy/app-config.ts @@ -88,6 +88,7 @@ export const dummyAppConfig: AppConfiguration = { offlineRetransmissionEnabled: true, offlineRetransmissionDescription: 'Offline retransmission allows retransmitting events from GCP (BigQuery) to Hermes.', + offlineRetransmissionFromViewDocsUrl: 'https://hermes-pubsub.rtfd.org', }, subscription: { endpointAddressResolverMetadata: { diff --git a/hermes-console/src/i18n/en-US/index.ts b/hermes-console/src/i18n/en-US/index.ts index d8f9876672..1b2447e162 100644 --- a/hermes-console/src/i18n/en-US/index.ts +++ b/hermes-console/src/i18n/en-US/index.ts @@ -647,6 +647,7 @@ const en_US = { }, offlineRetransmission: { title: 'Offline retransmission', + titleRetransmissionFromView: 'Docs for retransmission from View', subtitle: 'Offline retransmission allows retransmitting events from GCP (BigQuery) to Hermes.', targetTopic: 'Target topic', diff --git a/hermes-console/src/views/topic/offline-retransmission/OfflineRetransmissionForm.spec.ts b/hermes-console/src/views/topic/offline-retransmission/OfflineRetransmissionForm.spec.ts index 0f18354710..8cdcf85c8e 100644 --- a/hermes-console/src/views/topic/offline-retransmission/OfflineRetransmissionForm.spec.ts +++ b/hermes-console/src/views/topic/offline-retransmission/OfflineRetransmissionForm.spec.ts @@ -1,4 +1,6 @@ +import { createTestingPinia } from '@pinia/testing'; import { describe } from 'vitest'; +import { dummyAppConfig } from '@/dummy/app-config'; import { renderWithEmits } from '@/utils/test-utils'; import { waitFor } from '@testing-library/vue'; import OfflineRetransmissionForm from '@/views/topic/offline-retransmission/OfflineRetransmissionForm.vue'; @@ -6,7 +8,10 @@ import OfflineRetransmissionForm from '@/views/topic/offline-retransmission/Offl describe('OfflineRetransmissionForm', () => { it('should emit a cancel event when user clicks cancel button', async () => { // given - const wrapper = renderWithEmits(OfflineRetransmissionForm, {}); + const wrapper = renderWithEmits( + OfflineRetransmissionForm, + createInitialState(), + ); // when await wrapper @@ -22,7 +27,10 @@ describe('OfflineRetransmissionForm', () => { it('should emit a retransmit event when user clicks retransmit button', async () => { // given - const wrapper = renderWithEmits(OfflineRetransmissionForm, {}); + const wrapper = renderWithEmits( + OfflineRetransmissionForm, + createInitialState(), + ); // when await wrapper @@ -55,3 +63,21 @@ describe('OfflineRetransmissionForm', () => { }); }); }); + +function createInitialState() { + return { + testPinia: createTestingPinia({ + initialState: { + appConfig: { + appConfig: { + ...dummyAppConfig, + }, + loading: false, + error: { + loadConfig: null, + }, + }, + }, + }), + }; +} diff --git a/hermes-console/src/views/topic/offline-retransmission/OfflineRetransmissionForm.vue b/hermes-console/src/views/topic/offline-retransmission/OfflineRetransmissionForm.vue index 5aea28ce69..35cce1354d 100644 --- a/hermes-console/src/views/topic/offline-retransmission/OfflineRetransmissionForm.vue +++ b/hermes-console/src/views/topic/offline-retransmission/OfflineRetransmissionForm.vue @@ -1,11 +1,13 @@