diff --git a/Dockerfile.core b/Dockerfile.core
index b7e5cc8..0aa42d3 100644
--- a/Dockerfile.core
+++ b/Dockerfile.core
@@ -1,5 +1,5 @@
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/maven:3.8.3-openjdk-17-slim as builder
-ARG ARTIFACT_VERSION=2.0.0-SNAPSHOT
+ARG ARTIFACT_VERSION=2.0.1-RELEASE
RUN mkdir -p /build
WORKDIR /build
COPY . /build
@@ -7,7 +7,7 @@ RUN mvn clean install -DskipTests
RUN mvn clean verify -DskipTests -Dartifact.version=${ARTIFACT_VERSION}
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/openjdk:17-slim-bullseye
-ARG ARTIFACT_VERSION=2.0.0-SNAPSHOT
+ARG ARTIFACT_VERSION=2.0.1-RELEASE
RUN mkdir -p /usr/local
RUN apt-get update -y && apt-get -y install fontconfig libpng-dev
WORKDIR /usr/local/
diff --git a/Dockerfile.proxy b/Dockerfile.proxy
index bfdc858..3d6f5a6 100644
--- a/Dockerfile.proxy
+++ b/Dockerfile.proxy
@@ -1,5 +1,5 @@
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/maven:3.8.3-openjdk-17-slim as builder
-ARG ARTIFACT_VERSION=2.0.0-SNAPSHOT
+ARG ARTIFACT_VERSION=2.0.1-RELEASE
RUN mkdir -p /build
WORKDIR /build
COPY . /build
@@ -7,7 +7,7 @@ RUN mvn clean install
RUN mvn clean verify -DskipTests -Dartifact.version=${ARTIFACT_VERSION}
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/openjdk:17-slim-bullseye
-ARG ARTIFACT_VERSION=2.0.0-SNAPSHOT
+ARG ARTIFACT_VERSION=2.0.1-RELEASE
RUN mkdir -p /usr/local
RUN apt-get update -y && apt-get -y install fontconfig libpng-dev
WORKDIR /usr/local/
diff --git a/litmus-cache/pom.xml b/litmus-cache/pom.xml
index 580f940..878741f 100644
--- a/litmus-cache/pom.xml
+++ b/litmus-cache/pom.xml
@@ -4,11 +4,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-cache
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
jar
litmus-cache
diff --git a/litmus-client/pom.xml b/litmus-client/pom.xml
index 3a18d02..7772cbd 100644
--- a/litmus-client/pom.xml
+++ b/litmus-client/pom.xml
@@ -5,11 +5,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-client
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
jar
litmus-client
@@ -43,7 +43,7 @@
com.navi.medici
litmus-model
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
diff --git a/litmus-client/src/main/java/com/navi/medici/client/ClickStreamClient.java b/litmus-client/src/main/java/com/navi/medici/client/ClickStreamClient.java
index ed14b31..857d6a6 100644
--- a/litmus-client/src/main/java/com/navi/medici/client/ClickStreamClient.java
+++ b/litmus-client/src/main/java/com/navi/medici/client/ClickStreamClient.java
@@ -6,6 +6,7 @@ import com.navi.medici.config.LitmusConfig;
import com.navi.medici.response.LitmusExperimentResponse;
import com.navi.medici.util.JacksonUtils;
import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.log4j.Log4j2;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@@ -18,28 +19,33 @@ public class ClickStreamClient {
public static final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
- private final LitmusConfig litmusConfig;
- private final OkHttpClient client;
+ private final String clickStreamAPI;
+ private final String appName;
+ private final String vertical;
+ private final MeterRegistry meterRegistry;
- public ClickStreamClient(LitmusConfig litmusConfig) {
- this.litmusConfig = litmusConfig;
- this.client = new OkHttpClient();
+ public ClickStreamClient(String clickStreamAPI, String appName,
+ String vertical, MeterRegistry meterRegistry) {
+ this.clickStreamAPI = clickStreamAPI;
+ this.appName = appName;
+ this.vertical = vertical;
+ this.meterRegistry = meterRegistry;
}
public void publish(String experimentName, ClickStreamPayload payload) {
String requestBody = JacksonUtils.objectToString(payload);
Request request = new Request.Builder()
- .url(this.litmusConfig.getClickStreamAPI())
+ .url(clickStreamAPI)
.post(RequestBody.create(JSON, requestBody))
.build();
- try(Response response = client.newCall(request).execute()) {
+ try(Response response = OkHttpClientContainer.getInstance().newCall(request).execute()) {
Counter.builder("litmus_client_click_stream_event_ingestion")
- .tag("vertical", litmusConfig.getVertical())
+ .tag("vertical", vertical)
.tag("status", String.valueOf(response.code()))
.tag("experiment_name", experimentName)
- .tag("app_name", litmusConfig.getAppName())
- .register(this.litmusConfig.getMeterRegistry())
+ .tag("app_name", appName)
+ .register(meterRegistry)
.increment();
if (response.code() < 300) {
var responseBody = response.body();
@@ -51,11 +57,11 @@ public class ClickStreamClient {
}
} catch (Exception e) {
Counter.builder("litmus_client_click_stream_event_ingestion")
- .tag("vertical", litmusConfig.getVertical())
+ .tag("vertical", vertical)
.tag("experiment_name", experimentName)
- .tag("app_name", litmusConfig.getAppName())
+ .tag("app_name", appName)
.tag("exception", e.getMessage())
- .register(this.litmusConfig.getMeterRegistry())
+ .register(meterRegistry)
.increment();
log.error("clickstream event ingestion failed. ", e);
diff --git a/litmus-client/src/main/java/com/navi/medici/client/ExperimentBackupHandlerFile.java b/litmus-client/src/main/java/com/navi/medici/client/ExperimentBackupHandlerFile.java
index 7266408..0ed4247 100644
--- a/litmus-client/src/main/java/com/navi/medici/client/ExperimentBackupHandlerFile.java
+++ b/litmus-client/src/main/java/com/navi/medici/client/ExperimentBackupHandlerFile.java
@@ -45,9 +45,8 @@ public class ExperimentBackupHandlerFile implements ExperimentBackupHandler {
} catch (IOException | IllegalStateException e) {
log.error("");
}
- List emptyList = Collections.emptyList();
return LitmusExperimentCollection.builder()
- .litmusExperiments(emptyList)
+ .litmusExperiments(Collections.emptyList())
.build();
}
diff --git a/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java b/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java
index ff8f17b..b9394dc 100644
--- a/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java
+++ b/litmus-client/src/main/java/com/navi/medici/client/HttpExperimentFetcher.java
@@ -6,7 +6,9 @@ import com.navi.medici.response.LitmusExperimentCollection;
import com.navi.medici.response.LitmusExperimentResponse;
import com.navi.medici.response.LitmusResponse;
import com.navi.medici.util.JacksonUtils;
+import com.navi.medici.util.LitmusURLs;
import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.MeterRegistry;
import java.net.URL;
import lombok.extern.log4j.Log4j2;
import okhttp3.OkHttpClient;
@@ -15,31 +17,41 @@ import okhttp3.Response;
@Log4j2
public class HttpExperimentFetcher implements ExperimentFetcher {
- private final OkHttpClient client;
- private final URL experimentUrl;
- private final URL segmentIdUrl;
- private final LitmusConfig litmusConfig;
- public HttpExperimentFetcher(LitmusConfig litmusConfig) {
- this.client = new OkHttpClient();
- this.experimentUrl =
- litmusConfig.getLitmusURLs()
- .getLitmusExperimentsURL(litmusConfig.getProjectName(), litmusConfig.getNamePrefix());
- this.segmentIdUrl = litmusConfig.getLitmusURLs().getSegmentIdURL();
- this.litmusConfig = litmusConfig;
+ private final LitmusURLs litmusURLs;
+ private final String appName;
+ private final String projectNamePrefix;
+ private final String projectName;
+ private final String vertical;
+ private final MeterRegistry meterRegistry;
+
+ public HttpExperimentFetcher(LitmusURLs litmusURLs,
+ String appName,
+ String projectNamePrefix,
+ String projectName,
+ String vertical,
+ MeterRegistry meterRegistry) {
+ this.litmusURLs = litmusURLs;
+ this.appName = appName;
+ this.projectNamePrefix = projectNamePrefix;
+ this.projectName = projectName;
+ this.vertical = vertical;
+ this.meterRegistry = meterRegistry;
}
@Override
public LitmusExperimentResponse fetchExperiments(String vertical, Long pollingTime) throws LitmusException {
+ URL experimentUrl = litmusURLs
+ .getLitmusExperimentsURL(projectName, projectNamePrefix);
Request request = new Request.Builder()
- .url(String.format("%s?vertical=%s&polling_time=%s", this.experimentUrl, vertical, pollingTime))
+ .url(String.format("%s?vertical=%s&polling_time=%s", experimentUrl, vertical, pollingTime))
.build();
- try (Response response = client.newCall(request).execute()) {
+ try (Response response = OkHttpClientContainer.getInstance().newCall(request).execute()) {
Counter.builder("litmus_client_fetch_experiment_polling")
- .tag("vertical", vertical)
- .tag("app_name", litmusConfig.getAppName())
- .register(this.litmusConfig.getMeterRegistry())
+ .tag("vertical", this.vertical)
+ .tag("app_name", this.appName)
+ .register(this.meterRegistry)
.increment();
if (response.code() < 300) {
@@ -52,27 +64,27 @@ public class HttpExperimentFetcher implements ExperimentFetcher {
.tag("vertical", vertical)
.tag("status", String.valueOf(response.code()))
.tag("experiment_state", LitmusExperimentResponse.Status.CHANGED.name())
- .tag("app_name", litmusConfig.getAppName())
- .register(this.litmusConfig.getMeterRegistry())
+ .tag("app_name", this.appName)
+ .register(this.meterRegistry)
.increment();
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.CHANGED, experiments);
} else if (response.code() == 304) {
Counter.builder("litmus_client_fetch_experiment_polling_request_status")
.tag("vertical", vertical)
- .tag("app_name", litmusConfig.getAppName())
+ .tag("app_name", this.appName)
.tag("status", String.valueOf(response.code()))
.tag("experiment_state", LitmusExperimentResponse.Status.NOT_CHANGED.name())
- .register(this.litmusConfig.getMeterRegistry())
+ .register(this.meterRegistry)
.increment();
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.NOT_CHANGED, response.code());
} else {
Counter.builder("litmus_client_fetch_experiment_polling_request_status")
- .tag("vertical", vertical)
- .tag("app_name", litmusConfig.getAppName())
+ .tag("vertical", this.vertical)
+ .tag("app_name", this.appName)
.tag("status", String.valueOf(response.code()))
.tag("experiment_state", LitmusExperimentResponse.Status.NOT_CHANGED.name())
- .register(this.litmusConfig.getMeterRegistry())
+ .register(this.meterRegistry)
.increment();
return new LitmusExperimentResponse(LitmusExperimentResponse.Status.UNAVAILABLE, response.code());
@@ -80,37 +92,38 @@ public class HttpExperimentFetcher implements ExperimentFetcher {
} catch (Exception e) {
Counter.builder("litmus_client_fetch_experiment_polling_request_failed")
- .tag("vertical", vertical)
- .tag("app_name", litmusConfig.getAppName())
- .register(this.litmusConfig.getMeterRegistry())
+ .tag("vertical", this.vertical)
+ .tag("app_name", this.appName)
+ .register(this.meterRegistry)
.increment();
throw new LitmusException("fetch experiments failed.", e);
}
}
public LitmusResponse> segmentIdExists(String segmentName, String id) {
+ URL segmentIdUrl = this.litmusURLs.getSegmentIdURL();
Request request = new Request.Builder()
- .url(String.format("%s?segment_name=%s&id=%s", this.segmentIdUrl, segmentName, id))
+ .url(String.format("%s?segment_name=%s&id=%s", segmentIdUrl, segmentName, id))
.build();
- try (Response response = client.newCall(request).execute()) {
+ try (Response response = OkHttpClientContainer.getInstance().newCall(request).execute()) {
var responseBody = response.body();
assert responseBody != null;
Counter.builder("litmus_client_segment_id_exist_request")
- .tag("vertical", litmusConfig.getVertical())
- .tag("app_name", litmusConfig.getAppName())
+ .tag("vertical", this.vertical)
+ .tag("app_name", this.appName)
.tag("status", String.valueOf(response.code()))
- .register(this.litmusConfig.getMeterRegistry())
+ .register(this.meterRegistry)
.increment();
return JacksonUtils.stringToObject(responseBody.string(), LitmusResponse.class);
} catch (Exception e) {
Counter.builder("litmus_client_segment_id_exist_request_failed")
- .tag("vertical", litmusConfig.getVertical())
+ .tag("vertical", this.vertical)
.tag("segment_name", segmentName)
- .tag("app_name", litmusConfig.getAppName())
+ .tag("app_name", this.appName)
.tag("exception", e.getMessage())
- .register(this.litmusConfig.getMeterRegistry())
+ .register(this.meterRegistry)
.increment();
throw new LitmusException("segment name to id matching exists.", e);
diff --git a/litmus-client/src/main/java/com/navi/medici/client/OkHttpClientContainer.java b/litmus-client/src/main/java/com/navi/medici/client/OkHttpClientContainer.java
new file mode 100644
index 0000000..7f4ba94
--- /dev/null
+++ b/litmus-client/src/main/java/com/navi/medici/client/OkHttpClientContainer.java
@@ -0,0 +1,28 @@
+package com.navi.medici.client;
+
+import java.util.concurrent.TimeUnit;
+import okhttp3.OkHttpClient;
+
+public class OkHttpClientContainer {
+
+ private static OkHttpClient okHttpClient;
+
+ private OkHttpClientContainer() {
+ }
+
+ public static OkHttpClient getInstance() {
+ if (okHttpClient == null) {
+ synchronized (OkHttpClientContainer.class) {
+ if (okHttpClient == null) {
+ okHttpClient = new OkHttpClient.Builder()
+ .readTimeout(10, TimeUnit.MILLISECONDS)
+ .callTimeout(1, TimeUnit.SECONDS)
+ .connectTimeout(1, TimeUnit.SECONDS)
+ .build();
+ }
+ }
+ }
+ return okHttpClient;
+ }
+
+}
diff --git a/litmus-client/src/main/java/com/navi/medici/event/EventDispatcher.java b/litmus-client/src/main/java/com/navi/medici/event/EventDispatcher.java
index 164ffe2..60f0f7e 100644
--- a/litmus-client/src/main/java/com/navi/medici/event/EventDispatcher.java
+++ b/litmus-client/src/main/java/com/navi/medici/event/EventDispatcher.java
@@ -12,12 +12,15 @@ import com.navi.medici.util.JacksonUtils;
import com.navi.medici.variants.Variant;
import java.util.List;
import java.util.concurrent.CompletableFuture;
+import lombok.extern.log4j.Log4j2;
+@Log4j2
public class EventDispatcher {
private final ClickStreamClient clickStreamClient;
public EventDispatcher(LitmusConfig litmusConfig) {
- this.clickStreamClient = new ClickStreamClient(litmusConfig);
+ this.clickStreamClient = new ClickStreamClient(litmusConfig.getClickStreamAPI(), litmusConfig.getAppName(),
+ litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
}
public void publish(LitmusContext litmusContext, LitmusExperiment litmusExperiment, Boolean result, Variant variant) {
@@ -25,10 +28,10 @@ public class EventDispatcher {
var clickStreamPayload = clickStreamPayloadOptional
.map(payload -> JacksonUtils.stringToObject(payload, ClickStreamPayload.class))
- .orElse(null);
+ .orElse(ClickStreamPayload.builder().build());
String variantName = variant != null ? variant.getName() : null;
- if (clickStreamPayload != null && litmusExperiment != null) {
+ if (litmusExperiment != null) {
var litmusExperimentEvent = LitmusExperimentEvent.builder()
.experimentId(litmusExperiment.getExperimentId())
.experimentName(litmusExperiment.getName())
@@ -52,7 +55,6 @@ public class EventDispatcher {
clickStreamClient.publish(litmusExperiment.getName(), clickStreamPayload);
return null;
});
-
}
}
}
diff --git a/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java b/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java
index c839409..6f61292 100644
--- a/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java
+++ b/litmus-client/src/main/java/com/navi/medici/litmus/DefaultLitmus.java
@@ -50,7 +50,8 @@ public class DefaultLitmus implements Litmus {
private static ExperimentRepository defaultExperimentRepository(LitmusConfig litmusConfig) {
return new LitmusExperimentRepository(
litmusConfig,
- new HttpExperimentFetcher(litmusConfig),
+ new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
+ litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry()),
new ExperimentBackupHandlerFile(litmusConfig)) {
};
}
@@ -175,8 +176,13 @@ public class DefaultLitmus implements Litmus {
List BUILTIN_STRATEGIES =
Arrays.asList(
new DefaultStrategy(),
- new UserWithIdStrategy(new HttpExperimentFetcher(litmusConfig)),
- new DeviceWithIdStrategy(new HttpExperimentFetcher(litmusConfig)),
+
+ new UserWithIdStrategy(new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
+ litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry())),
+
+ new DeviceWithIdStrategy(new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
+ litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry())),
+
new FlexibleRolloutStrategy());
BUILTIN_STRATEGIES.forEach(strategy -> map.put(strategy.getName(), strategy));
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/ConstraintUtil.java b/litmus-client/src/main/java/com/navi/medici/strategy/ConstraintUtil.java
index ab5a15a..6e62ac9 100644
--- a/litmus-client/src/main/java/com/navi/medici/strategy/ConstraintUtil.java
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/ConstraintUtil.java
@@ -1,12 +1,48 @@
package com.navi.medici.strategy;
+import static com.navi.medici.enums.Operator.DATE_AFTER;
+import static com.navi.medici.enums.Operator.DATE_BEFORE;
+import static com.navi.medici.enums.Operator.IN;
+import static com.navi.medici.enums.Operator.NOT_IN;
+import static com.navi.medici.enums.Operator.NUM_EQ;
+import static com.navi.medici.enums.Operator.NUM_GT;
+import static com.navi.medici.enums.Operator.NUM_GTE;
+import static com.navi.medici.enums.Operator.NUM_LT;
+import static com.navi.medici.enums.Operator.NUM_LTE;
+import static com.navi.medici.enums.Operator.STR_CONTAINS;
+import static com.navi.medici.enums.Operator.STR_ENDS_WITH;
+import static com.navi.medici.enums.Operator.STR_STARTS_WITH;
+
import com.navi.medici.constraint.Constraint;
import com.navi.medici.context.LitmusContext;
import com.navi.medici.enums.Operator;
+import com.navi.medici.strategy.constraints.ConstraintOperator;
+import com.navi.medici.strategy.constraints.DateConstraintOperator;
+import com.navi.medici.strategy.constraints.NumberConstraintOperator;
+import com.navi.medici.strategy.constraints.StringConstraintOperator;
+import java.util.HashMap;
import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import java.util.Optional;
public class ConstraintUtil {
+ private static final Map operators = new HashMap<>();
+
+ static {
+ operators.put(STR_CONTAINS, new StringConstraintOperator(Locale.ROOT));
+ operators.put(STR_ENDS_WITH, new StringConstraintOperator(Locale.ROOT));
+ operators.put(STR_STARTS_WITH, new StringConstraintOperator(Locale.ROOT));
+ operators.put(IN, new StringConstraintOperator(Locale.ROOT));
+ operators.put(NOT_IN, new StringConstraintOperator(Locale.ROOT));
+ operators.put(NUM_LT, new NumberConstraintOperator());
+ operators.put(NUM_LTE, new NumberConstraintOperator());
+ operators.put(NUM_EQ, new NumberConstraintOperator());
+ operators.put(NUM_GTE, new NumberConstraintOperator());
+ operators.put(NUM_GT, new NumberConstraintOperator());
+ operators.put(DATE_BEFORE, new DateConstraintOperator());
+ operators.put(DATE_AFTER, new DateConstraintOperator());
+ }
public static boolean validate(List constraints, LitmusContext context) {
if (constraints != null && constraints.size() > 0) {
@@ -21,6 +57,6 @@ public class ConstraintUtil {
boolean isIn =
contextValue.isPresent()
&& constraint.getValues().contains(contextValue.get().trim());
- return (constraint.getOperator() == Operator.IN) == isIn;
+ return (constraint.getOperator() == IN) == isIn;
}
}
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/constraints/ConstraintOperator.java b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/ConstraintOperator.java
new file mode 100644
index 0000000..a8e60cc
--- /dev/null
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/ConstraintOperator.java
@@ -0,0 +1,8 @@
+package com.navi.medici.strategy.constraints;
+
+import com.navi.medici.constraint.Constraint;
+import com.navi.medici.context.LitmusContext;
+
+public interface ConstraintOperator {
+ boolean evaluate(Constraint constraint, LitmusContext context);
+}
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/constraints/DateConstraintOperator.java b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/DateConstraintOperator.java
new file mode 100644
index 0000000..5815ac8
--- /dev/null
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/DateConstraintOperator.java
@@ -0,0 +1,30 @@
+package com.navi.medici.strategy.constraints;
+
+import com.navi.medici.constraint.Constraint;
+import com.navi.medici.context.LitmusContext;
+import com.navi.medici.enums.Operator;
+import java.time.ZonedDateTime;
+
+public class DateConstraintOperator implements ConstraintOperator {
+
+ @Override
+ public boolean evaluate(Constraint constraint, LitmusContext context) {
+ try {
+ ZonedDateTime value = DateParser.parseDate(constraint.getValue());
+ return eval(constraint.getOperator(), value, ZonedDateTime.now());
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private boolean eval(Operator op, ZonedDateTime value, ZonedDateTime toMatch) {
+ switch (op) {
+ case DATE_AFTER:
+ return toMatch.isAfter(value);
+ case DATE_BEFORE:
+ return toMatch.isBefore(value);
+ default:
+ return false;
+ }
+ }
+}
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/constraints/DateParser.java b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/DateParser.java
new file mode 100644
index 0000000..c46bfab
--- /dev/null
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/DateParser.java
@@ -0,0 +1,49 @@
+package com.navi.medici.strategy.constraints;
+
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+public class DateParser {
+ private static final List formatters = new ArrayList<>();
+
+ static {
+ formatters.add(DateTimeFormatter.ISO_INSTANT);
+ formatters.add(DateTimeFormatter.ISO_DATE_TIME);
+ formatters.add(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+ formatters.add(DateTimeFormatter.ISO_ZONED_DATE_TIME);
+ }
+
+ public static ZonedDateTime parseDate(String date) {
+ if (date != null && date.length() > 0) {
+ return formatters.stream()
+ .map(
+ f -> {
+ try {
+ return ZonedDateTime.parse(date, f);
+ } catch (DateTimeParseException dateTimeParseException) {
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .findFirst()
+ .orElseGet(
+ () -> {
+ try {
+ return LocalDateTime.parse(
+ date, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
+ .atZone(ZoneOffset.UTC);
+ } catch (DateTimeParseException dateTimeParseException) {
+ return null;
+ }
+ });
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/constraints/NumberConstraintOperator.java b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/NumberConstraintOperator.java
new file mode 100644
index 0000000..168e777
--- /dev/null
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/NumberConstraintOperator.java
@@ -0,0 +1,65 @@
+package com.navi.medici.strategy.constraints;
+
+import com.navi.medici.constraint.Constraint;
+import com.navi.medici.context.LitmusContext;
+import com.navi.medici.enums.Operator;
+import java.util.Objects;
+
+public class NumberConstraintOperator implements ConstraintOperator {
+
+ @Override
+ public boolean evaluate(Constraint constraint, LitmusContext context) {
+ return context.getByName(constraint.getContextName())
+ .map(
+ cVal -> {
+ try {
+ return Double.parseDouble(cVal);
+ } catch (NumberFormatException nfe) {
+ return null;
+ }
+ })
+ .map(
+ cVal -> {
+ try {
+ if (constraint.getValues().size() > 0) {
+ return constraint.getValues().stream()
+ .map(
+ v -> {
+ try {
+ return Double.parseDouble(v);
+ } catch (NumberFormatException nfe) {
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .anyMatch(v -> eval(constraint.getOperator(), v, cVal));
+ } else if (constraint.getValue() != null
+ && constraint.getValue().length() > 0) {
+ Double value = Double.parseDouble(constraint.getValue());
+ return eval(constraint.getOperator(), value, cVal);
+ } else {
+ return null;
+ }
+ } catch (NumberFormatException nfe) {
+ return null;
+ }
+ })
+ .orElse(false);
+ }
+ private boolean eval(Operator operator, Double value, Double contextValue) {
+ switch (operator) {
+ case NUM_LT:
+ return contextValue < value;
+ case NUM_LTE:
+ return contextValue <= value;
+ case NUM_EQ:
+ return contextValue.equals(value);
+ case NUM_GTE:
+ return contextValue >= value;
+ case NUM_GT:
+ return contextValue > value;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/litmus-client/src/main/java/com/navi/medici/strategy/constraints/StringConstraintOperator.java b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/StringConstraintOperator.java
new file mode 100644
index 0000000..aed3e9f
--- /dev/null
+++ b/litmus-client/src/main/java/com/navi/medici/strategy/constraints/StringConstraintOperator.java
@@ -0,0 +1,71 @@
+package com.navi.medici.strategy.constraints;
+
+import com.navi.medici.constraint.Constraint;
+import com.navi.medici.context.LitmusContext;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+
+public class StringConstraintOperator implements ConstraintOperator {
+
+ private Locale comparisonLocale;
+
+ public StringConstraintOperator(Locale comparisonLocale) {
+ this.comparisonLocale = comparisonLocale;
+ }
+
+ @Override
+ public boolean evaluate(Constraint constraint, LitmusContext context) {
+ List values = constraint.getValues();
+ Optional contextValue = context.getByName(constraint.getContextName());
+ boolean caseInsensitive = constraint.isCaseInsensitive();
+ switch (constraint.getOperator()) {
+ case IN:
+ return isIn(values, contextValue, caseInsensitive);
+ case NOT_IN:
+ return !isIn(values, contextValue, caseInsensitive);
+ case STR_CONTAINS:
+ return contains(values, contextValue, caseInsensitive);
+ case STR_STARTS_WITH:
+ return startsWith(values, contextValue, caseInsensitive);
+ case STR_ENDS_WITH:
+ return endsWith(values, contextValue, caseInsensitive);
+ default:
+ return false;
+ }
+ }
+
+ private boolean endsWith(
+ List values, Optional contextValue, boolean caseInsensitive) {
+ return contextValue
+ .map(c -> values.stream()
+ .anyMatch(v -> caseInsensitive
+ ? c.toLowerCase(comparisonLocale).endsWith(v.toLowerCase(comparisonLocale)) : c.endsWith(v)))
+ .orElse(false);
+ }
+
+ private boolean startsWith(
+ List values, Optional contextValue, boolean caseInsensitive) {
+ return contextValue
+ .map(
+ c -> values.stream()
+ .anyMatch(v -> caseInsensitive
+ ? v.toLowerCase(comparisonLocale).startsWith(c.toLowerCase(comparisonLocale)) : c.startsWith(v)))
+ .orElse(false);
+ }
+
+ private boolean contains(
+ List values, Optional contextValue, boolean caseInsensitive) {
+ return contextValue
+ .map(c -> values.stream()
+ .anyMatch(v -> caseInsensitive
+ ? c.toLowerCase(comparisonLocale).contains(v.toLowerCase(comparisonLocale)) : c.contains(v)))
+ .orElse(false);
+ }
+
+ private boolean isIn(List values, Optional value, boolean caseInsensitive) {
+ return value.map(v -> values.stream()
+ .anyMatch(c -> caseInsensitive ? c.equalsIgnoreCase(v) : c.equals(v)))
+ .orElse(false);
+ }
+}
diff --git a/litmus-client/src/main/java/com/navi/medici/util/VariantUtil.java b/litmus-client/src/main/java/com/navi/medici/util/VariantUtil.java
index d37085a..2c05012 100644
--- a/litmus-client/src/main/java/com/navi/medici/util/VariantUtil.java
+++ b/litmus-client/src/main/java/com/navi/medici/util/VariantUtil.java
@@ -80,16 +80,16 @@ public final class VariantUtil {
}
public static Variant selectVariant(LitmusExperiment litmusExperiment, LitmusContext context, Variant defaultVariant) {
- if (litmusExperiment == null || StringUtils.isBlank(litmusExperiment.getVariants())) {
+ if (litmusExperiment.getVariants().isEmpty()) {
return defaultVariant;
}
- List variants = JacksonUtils.stringToListObject(litmusExperiment.getVariants(), VariantDefinition.class);
- int totalWeight = variants.stream().mapToInt(VariantDefinition::getWeight).sum();
+// List variants = JacksonUtils.stringToListObject(litmusExperiment.getVariants(), VariantDefinition.class);
+ int totalWeight = litmusExperiment.getVariants().stream().mapToInt(VariantDefinition::getWeight).sum();
if (totalWeight == 0) {
return defaultVariant;
}
- Optional variantOverride = getOverride(variants, context);
+ Optional variantOverride = getOverride(litmusExperiment.getVariants(), context);
if (variantOverride.isPresent()) {
var variantDefinition = variantOverride.get();
return Variant.builder()
@@ -100,7 +100,7 @@ public final class VariantUtil {
.build();
}
Optional customStickiness =
- variants.stream()
+ litmusExperiment.getVariants().stream()
.filter(f -> f.getStickiness() != null)
.map(VariantDefinition::getStickiness)
.findFirst();
@@ -109,7 +109,7 @@ public final class VariantUtil {
getSeed(context, customStickiness), litmusExperiment.getName(), totalWeight);
int counter = 0;
- for (final VariantDefinition definition : variants) {
+ for (final VariantDefinition definition : litmusExperiment.getVariants()) {
if (definition.getWeight() != 0) {
counter += definition.getWeight();
if (counter >= target) {
diff --git a/litmus-client/src/test/java/com/navi/medici/bootstrap/LitmusExperimentBootstrapFileProviderTest.java b/litmus-client/src/test/java/com/navi/medici/bootstrap/LitmusExperimentBootstrapFileProviderTest.java
new file mode 100644
index 0000000..f350985
--- /dev/null
+++ b/litmus-client/src/test/java/com/navi/medici/bootstrap/LitmusExperimentBootstrapFileProviderTest.java
@@ -0,0 +1,10 @@
+package com.navi.medici.bootstrap;
+
+import junit.framework.TestCase;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LitmusExperimentBootstrapFileProviderTest {
+
+}
\ No newline at end of file
diff --git a/litmus-client/src/test/java/com/navi/medici/client/ClickStreamClientTest.java b/litmus-client/src/test/java/com/navi/medici/client/ClickStreamClientTest.java
new file mode 100644
index 0000000..049c4fd
--- /dev/null
+++ b/litmus-client/src/test/java/com/navi/medici/client/ClickStreamClientTest.java
@@ -0,0 +1,71 @@
+package com.navi.medici.client;
+
+import static com.navi.medici.utils.TestUtils.buildLitmusConfig;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.navi.medici.clickstream.ClickStreamPayload;
+import com.navi.medici.clickstream.ClickStreamPayload.App;
+import com.navi.medici.config.LitmusConfig;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import java.util.List;
+import okhttp3.Call;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClickStreamClientTest {
+
+ @Mock
+ LitmusConfig litmusConfig;
+
+ @Mock
+ OkHttpClient okHttpClient;
+
+ @Mock
+ MeterRegistry meterRegistry;
+
+ ClickStreamClient client;
+
+
+ @Test
+ public void testEventIngestion() throws Exception {
+ var litmusConfig = buildLitmusConfig();
+
+ var clickStreamPayload = ClickStreamPayload.builder()
+ .clickStreamEvent(List.of())
+ .app(App.builder().build())
+ .build();
+ client = new ClickStreamClient(litmusConfig.getClickStreamAPI(), litmusConfig.getAppName(),
+ litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
+
+ final Response response = new Response.Builder()
+ .request(new Request.Builder().url("https://example.com").build())
+ .protocol(Protocol.HTTP_1_1)
+ .code(200).message("").body(
+ ResponseBody.create(
+ MediaType.parse("application/json"),
+ "serializedBody"
+ ))
+ .build();
+
+ final Call remoteCall = mock(Call.class);
+
+ when(remoteCall.execute()).thenReturn(response);
+
+ client.publish("test-experiment", clickStreamPayload);
+ }
+
+
+}
\ No newline at end of file
diff --git a/litmus-client/src/test/java/com/navi/medici/client/HttpExperimentFetcherTest.java b/litmus-client/src/test/java/com/navi/medici/client/HttpExperimentFetcherTest.java
new file mode 100644
index 0000000..3cb8101
--- /dev/null
+++ b/litmus-client/src/test/java/com/navi/medici/client/HttpExperimentFetcherTest.java
@@ -0,0 +1,62 @@
+package com.navi.medici.client;
+
+import static com.navi.medici.utils.TestUtils.buildLitmusConfig;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.navi.medici.config.LitmusConfig;
+import io.micrometer.core.instrument.MeterRegistry;
+import okhttp3.Call;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Protocol;
+import okhttp3.Request;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class HttpExperimentFetcherTest {
+ @Mock
+ LitmusConfig litmusConfig;
+
+ @Mock
+ OkHttpClient okHttpClient;
+
+ @Mock
+ MeterRegistry meterRegistry;
+
+ HttpExperimentFetcher httpExperimentFetcher;
+
+
+ @Test
+ public void fetchExperiments() throws Exception {
+ var litmusConfig = buildLitmusConfig();
+ httpExperimentFetcher = new HttpExperimentFetcher(litmusConfig.getLitmusURLs(), litmusConfig.getAppName(), litmusConfig.getNamePrefix(),
+ litmusConfig.getProjectName(), litmusConfig.getVertical(), litmusConfig.getMeterRegistry());
+ final Response response = new Response.Builder()
+ .request(new Request.Builder().url("https://example.com/litmus-core/v1/experiments?vertical=test-experiment&polling_time=20").build())
+ .protocol(Protocol.HTTP_1_1)
+ .code(200).message("").body(
+ ResponseBody.create(
+ MediaType.parse("application/json"),
+ "serializedBody"
+ ))
+ .build();
+
+ final Call remoteCall = mock(Call.class);
+ when(okHttpClient.newCall(any())).thenReturn(remoteCall);
+
+ when(okHttpClient.newCall(any()).execute()).thenReturn(response);
+
+ var litmusResponse = httpExperimentFetcher.fetchExperiments("test-experiment", 20L );
+ }
+
+}
\ No newline at end of file
diff --git a/litmus-client/src/test/java/com/navi/medici/event/EventDispatcherTest.java b/litmus-client/src/test/java/com/navi/medici/event/EventDispatcherTest.java
new file mode 100644
index 0000000..b961199
--- /dev/null
+++ b/litmus-client/src/test/java/com/navi/medici/event/EventDispatcherTest.java
@@ -0,0 +1,75 @@
+package com.navi.medici.event;
+
+import static com.navi.medici.utils.TestUtils.buildLitmusContext;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.navi.medici.clickstream.ClickStreamPayload;
+import com.navi.medici.client.ClickStreamClient;
+import com.navi.medici.config.LitmusConfig;
+import com.navi.medici.context.LitmusContext;
+import com.navi.medici.enums.ExperimentType;
+import com.navi.medici.request.v1.LitmusExperiment;
+import java.util.concurrent.CompletableFuture;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class EventDispatcherTest {
+
+ @Mock
+ ClickStreamClient client;
+
+ @Mock
+ LitmusConfig litmusConfig;
+
+ @InjectMocks
+ EventDispatcher eventDispatcher;
+
+ @Test
+ public void testEventDispatchWithoutClickStreamPayload() {
+ LitmusContext litmusContext = LitmusContext.builder()
+ .appName("test-app")
+ .userId("test-user-id")
+ .appVersionCode("200")
+ .osType("android")
+ .clickStreamPayload(null)
+ .build();
+
+ LitmusExperiment litmusExperiment = LitmusExperiment.builder()
+ .experimentId("test-experiment-id")
+ .vertical("PL")
+ .enabled(true)
+ .name("test-experiment-name")
+ .type(ExperimentType.EXPERIMENT)
+ .build();
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(ClickStreamPayload.class);
+ eventDispatcher.publish(litmusContext, litmusExperiment, false, null);
+ }
+
+ @Test
+ public void testEventDispatchWithClickStreamPayload() {
+
+ LitmusContext litmusContext = buildLitmusContext();
+
+ LitmusExperiment litmusExperiment = LitmusExperiment.builder()
+ .experimentId("test-experiment-id")
+ .vertical("PL")
+ .enabled(true)
+ .name("test-experiment-name")
+ .type(ExperimentType.EXPERIMENT)
+ .build();
+
+ ArgumentCaptor captor = ArgumentCaptor.forClass(ClickStreamPayload.class);
+ eventDispatcher.publish(litmusContext, litmusExperiment, false, null);
+ }
+}
\ No newline at end of file
diff --git a/litmus-client/src/test/java/com/navi/medici/utils/TestUtils.java b/litmus-client/src/test/java/com/navi/medici/utils/TestUtils.java
new file mode 100644
index 0000000..1fbebe9
--- /dev/null
+++ b/litmus-client/src/test/java/com/navi/medici/utils/TestUtils.java
@@ -0,0 +1,28 @@
+package com.navi.medici.utils;
+
+import com.navi.medici.config.LitmusConfig;
+import com.navi.medici.context.LitmusContext;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+
+public class TestUtils {
+ public static LitmusConfig buildLitmusConfig() {
+ return LitmusConfig.builder()
+ .meterRegistry(new SimpleMeterRegistry())
+ .clickStreamAPI("https://example.com")
+ .vertical("PL")
+ .appName("test-app")
+ .litmusAPI("https://example.com/litmus-core/v1")
+ .build();
+ }
+
+ public static LitmusContext buildLitmusContext() {
+ var metadata = "{\"app\":{\"name\":\"test-name\",\"version\":\"test-version\"},\"device\":null,\"os\":{\"name\":\"test-os\",\"version\":null,\"api_version\":\"test-api-version\"},\"location\":null,\"network\":null,\"session\":null,\"user\":null}";
+ return LitmusContext.builder()
+ .appName("test-app")
+ .userId("test-user-id")
+ .appVersionCode("200")
+ .osType("android")
+ .clickStreamPayload(metadata)
+ .build();
+ }
+}
diff --git a/litmus-core/pom.xml b/litmus-core/pom.xml
index c256607..1e8209a 100644
--- a/litmus-core/pom.xml
+++ b/litmus-core/pom.xml
@@ -5,11 +5,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-core
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
jar
litmus-core
@@ -31,25 +31,25 @@
com.navi.medici
litmus-model
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
com.navi.medici
litmus-db
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
com.navi.medici
litmus-cache
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
com.navi.medici
litmus-util
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
@@ -86,6 +86,13 @@
5.5.2
+
+ org.springdoc
+ springdoc-openapi-ui
+ 1.6.8
+
+
+
diff --git a/litmus-core/src/main/java/com/navi/medici/config/SwaggerConfig.java b/litmus-core/src/main/java/com/navi/medici/config/SwaggerConfig.java
new file mode 100644
index 0000000..05181d8
--- /dev/null
+++ b/litmus-core/src/main/java/com/navi/medici/config/SwaggerConfig.java
@@ -0,0 +1,18 @@
+package com.navi.medici.config;
+
+
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SwaggerConfig {
+
+// @Bean
+// public Docket api() {
+// return new Docket(DocumentationType.SWAGGER_2)
+// .select()
+// .apis(RequestHandlerSelectors.any())
+// .paths(regex("/litmus-core/.*"))
+// .build();
+// }
+
+}
diff --git a/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java b/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java
index 222177e..4cf602a 100644
--- a/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java
+++ b/litmus-core/src/main/java/com/navi/medici/controller/v1/ExperimentController.java
@@ -40,6 +40,12 @@ public class ExperimentController {
return experimentService.fetchAllExperiments();
}
+ @GetMapping("/fetch")
+ @Timed(value = "litmus.core.fetch.experiment.by.name", percentiles = {0.95, 0.99})
+ public LitmusExperiment fetchExperimentsByName(@RequestParam("experiment_name") String experimentName) {
+ return experimentService.fetchExperimentByName(experimentName);
+ }
+
@GetMapping(value = "/vertical")
@Timed(value = "litmus.fetch.vertical.experiment", percentiles = {0.95, 0.99})
public ResponseEntity fetchExperimentsForAVertical(@RequestParam("vertical") String vertical,
diff --git a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java
index 2d35089..d21a72d 100644
--- a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java
+++ b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentService.java
@@ -11,6 +11,8 @@ public interface ExperimentService {
LitmusExperimentCollection fetchAllExperiments();
+ LitmusExperiment fetchExperimentByName(String experimentName);
+
void attachVariants(String experimentId, List variantDefinitions);
LitmusExperimentCollection fetchAllExperimentsForVerticals(String vertical, Long pollingTime);
diff --git a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java
index f0f9fb4..3033b33 100644
--- a/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java
+++ b/litmus-core/src/main/java/com/navi/medici/service/experiment/ExperimentServiceImpl.java
@@ -12,6 +12,7 @@ import com.navi.medici.strategy.ActivationStrategy;
import com.navi.medici.util.JacksonUtils;
import com.navi.medici.variants.VariantDefinition;
import java.util.List;
+import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
import lombok.extern.log4j.Log4j2;
@@ -59,6 +60,12 @@ public record ExperimentServiceImpl(IExperimentQuery experimentQuery,
.build();
}
+ @Override
+ public LitmusExperiment fetchExperimentByName(String experimentName) {
+ Optional litmusExperiment = experimentQuery.findByName(experimentName);
+ return litmusExperiment.map(this::build).orElse(null);
+ }
+
@Override
public void attachVariants(String experimentId, List variantDefinitions) {
var experimentOptional = experimentQuery.findByExperimentId(experimentId);
@@ -75,7 +82,7 @@ public record ExperimentServiceImpl(IExperimentQuery experimentQuery,
@Override
public LitmusExperimentCollection fetchAllExperimentsForVerticals(String vertical, Long pollingTime) {
- var experimentEntities = experimentQuery.findByVertical(vertical, pollingTime);
+ var experimentEntities = experimentQuery.findByVertical(vertical, pollingTime);
List litmusExperiments = experimentEntities.stream()
.map(this::build)
@@ -94,7 +101,7 @@ public record ExperimentServiceImpl(IExperimentQuery experimentQuery,
.enabled(experimentEntity.getEnabled())
.archived(experimentEntity.getArchived())
.strategies(jacksonUtils.stringToListObject(experimentEntity.getStrategies(), ActivationStrategy.class))
- .variants(experimentEntity.getVariants())
+ .variants(jacksonUtils.stringToListObject(experimentEntity.getVariants(), VariantDefinition.class))
.type(experimentEntity.getType())
.startTime(experimentEntity.getStartTime())
.endTime(experimentEntity.getEndTime())
diff --git a/litmus-core/src/main/resources/application.properties b/litmus-core/src/main/resources/application.properties
index c722155..a5d2d0e 100644
--- a/litmus-core/src/main/resources/application.properties
+++ b/litmus-core/src/main/resources/application.properties
@@ -35,4 +35,6 @@ redis.false.probability=0.001
segment.s3.bucket=${SEGMENT_S3_BUCKET:navi-test}
-s3.enabled=${S3_ENABLED:false}
\ No newline at end of file
+s3.enabled=${S3_ENABLED:false}
+
+springdoc.swagger-ui.path=/swagger-ui.html
\ No newline at end of file
diff --git a/litmus-db/pom.xml b/litmus-db/pom.xml
index 79f0777..06a042f 100644
--- a/litmus-db/pom.xml
+++ b/litmus-db/pom.xml
@@ -4,11 +4,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-db
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
jar
litmus-db
@@ -27,7 +27,7 @@
com.navi.medici
litmus-model
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
diff --git a/litmus-liquibase/pom.xml b/litmus-liquibase/pom.xml
index 818e531..2d919a6 100644
--- a/litmus-liquibase/pom.xml
+++ b/litmus-liquibase/pom.xml
@@ -4,11 +4,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-liquibase
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
jar
litmus-liquibase
diff --git a/litmus-mock/pom.xml b/litmus-mock/pom.xml
index 252c326..50a1864 100644
--- a/litmus-mock/pom.xml
+++ b/litmus-mock/pom.xml
@@ -4,11 +4,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-mock
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
jar
litmus-mock
@@ -16,13 +16,13 @@
com.navi.medici
litmus-model
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
com.navi.medici
litmus-client
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
diff --git a/litmus-model/pom.xml b/litmus-model/pom.xml
index 7e74623..c05a2cd 100644
--- a/litmus-model/pom.xml
+++ b/litmus-model/pom.xml
@@ -5,11 +5,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-model
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
jar
litmus-model
diff --git a/litmus-model/src/main/java/com/navi/medici/constraint/Constraint.java b/litmus-model/src/main/java/com/navi/medici/constraint/Constraint.java
index 6de107f..1856c61 100644
--- a/litmus-model/src/main/java/com/navi/medici/constraint/Constraint.java
+++ b/litmus-model/src/main/java/com/navi/medici/constraint/Constraint.java
@@ -23,4 +23,8 @@ public class Constraint {
String contextName;
Operator operator;
List values;
+
+ String value;
+ boolean inverted;
+ boolean caseInsensitive;
}
diff --git a/litmus-model/src/main/java/com/navi/medici/enums/Operator.java b/litmus-model/src/main/java/com/navi/medici/enums/Operator.java
index 9197db4..212ad4a 100644
--- a/litmus-model/src/main/java/com/navi/medici/enums/Operator.java
+++ b/litmus-model/src/main/java/com/navi/medici/enums/Operator.java
@@ -2,5 +2,15 @@ package com.navi.medici.enums;
public enum Operator {
IN,
- NOT_IN
+ NOT_IN,
+ STR_ENDS_WITH,
+ STR_STARTS_WITH,
+ STR_CONTAINS,
+ NUM_EQ,
+ NUM_GT,
+ NUM_GTE,
+ NUM_LT,
+ NUM_LTE,
+ DATE_AFTER,
+ DATE_BEFORE,
}
diff --git a/litmus-model/src/main/java/com/navi/medici/request/v1/LitmusExperiment.java b/litmus-model/src/main/java/com/navi/medici/request/v1/LitmusExperiment.java
index 8cf090a..a067879 100644
--- a/litmus-model/src/main/java/com/navi/medici/request/v1/LitmusExperiment.java
+++ b/litmus-model/src/main/java/com/navi/medici/request/v1/LitmusExperiment.java
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.navi.medici.enums.ExperimentType;
import com.navi.medici.strategy.ActivationStrategy;
+import com.navi.medici.variants.VariantDefinition;
import java.time.LocalDateTime;
import java.util.List;
import lombok.AccessLevel;
@@ -41,7 +42,7 @@ public class LitmusExperiment {
List strategies;
@JsonProperty("variants")
- String variants;
+ List variants;
@JsonProperty("type")
ExperimentType type;
diff --git a/litmus-proxy/pom.xml b/litmus-proxy/pom.xml
index 2a4b3b4..4de3d3b 100644
--- a/litmus-proxy/pom.xml
+++ b/litmus-proxy/pom.xml
@@ -4,11 +4,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-proxy
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
jar
litmus-proxy
@@ -17,13 +17,13 @@
com.navi.medici
litmus-model
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
com.navi.medici
litmus-client
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
diff --git a/litmus-proxy/src/main/java/com/navi/medici/controller/LitmusProxyController.java b/litmus-proxy/src/main/java/com/navi/medici/controller/LitmusProxyController.java
index 8cd42f9..cfe048c 100644
--- a/litmus-proxy/src/main/java/com/navi/medici/controller/LitmusProxyController.java
+++ b/litmus-proxy/src/main/java/com/navi/medici/controller/LitmusProxyController.java
@@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RestController;
public record LitmusProxyController(Litmus litmus) {
@GetMapping(value = "/experiment", produces = MediaType.APPLICATION_JSON_VALUE)
- @Timed(value = "litmus.proxy.create.experiment", percentiles = {0.95, 0.99})
+ @Timed(value = "litmus.proxy.fetch.experiment", percentiles = {0.95, 0.99}, extraTags = {"mt", "fetch"} )
public ResponseEntity> fetch(@RequestParam("name") String experimentName) {
var result = litmus.isEnabled(experimentName);
@@ -25,7 +25,7 @@ public record LitmusProxyController(Litmus litmus) {
}
@GetMapping(value = "/variant", produces = MediaType.APPLICATION_JSON_VALUE)
- @Timed(value = "litmus.proxy.fetch.experiment", percentiles = {0.95, 0.99})
+ @Timed(value = "litmus.proxy.fetch.experiment", percentiles = {0.95, 0.99}, extraTags = {"mt", "fetchVariants"})
public ResponseEntity> fetchVariants(@RequestParam("name") String variantName) {
var variant = litmus.getVariant(variantName);
diff --git a/litmus-util/pom.xml b/litmus-util/pom.xml
index e3eb9c8..7903351 100644
--- a/litmus-util/pom.xml
+++ b/litmus-util/pom.xml
@@ -4,11 +4,11 @@
litmus
com.navi.medici
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-util
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
litmus-util
diff --git a/pom.xml b/pom.xml
index a830b2d..ee16b76 100644
--- a/pom.xml
+++ b/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.navi.medici
litmus
- 2.0.0-SNAPSHOT
+ 2.0.1-RELEASE
pom
litmus
@@ -81,6 +81,11 @@
4.13.2
test
+
+ org.mockito
+ mockito-core
+ test
+
org.apache.logging.log4j