Akshat | TP-19381 : add sample size api and added Update flow (#38)
* TP-19381 | add sample size api * TP-19381 | refactor sample size api and fixed bug for all filter in teams * TP-19381 | add tests * Akshat | TP-23049 | Add Update Apis for Experiment Flow (#48) * TP-23049 | add metric and segment dropdown * TP-23049 | change ExperimentResponse to DashboardExperimentResponse * TP-23049 | add fetch experiment by name on portal in edit flow * TP-23049 | attach metric to an experiment * TP-23049 | add release, pause and rollback api for experiment * TP-23049 | add required email in release, pause and rollback apiss * TP-23049 | add createdAt, updatedAt and createdBy in experiment response * TP-23049 | add experiment audit trails * TP-23049 | change experimentInfoEntity * TP-23049 | add controller tests * TP-23049 | add variant sum validations * TP-23049 | add mapper test * TP-19381 | change sample size api from POST to GET * TP-19381 | set updated by * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105137 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105138 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105139 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105140 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105143 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105146 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105147 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105153 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105153 * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105159 * TP-19381 | convert Dto to DTO * TP-19381 | Handle null pointer for old experiments * TP-19860 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r105145 * TP-19381 | add groupId while creating experiment * TP-19381 | logged old and new configurations when updating an experiment * TP-19381 | resolve https://github.cmd.navi-tech.in/medici/litmus/pull/38#discussion_r106617 * TP-19381 | add lazy loading for experiment entity for less memory usage on client side * TP-19381 | add tests
This commit is contained in:
committed by
GitHub Enterprise
parent
53bad05c79
commit
c7b148d856
@@ -1,134 +1,134 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<artifactId>litmus</artifactId>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>litmus-core</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-core</name>
|
||||
|
||||
<artifactId>litmus-core</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
<packaging>jar</packaging>
|
||||
<name>litmus-core</name>
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<nexus.host>https://nexus.cmd.navi-tech.in</nexus.host>
|
||||
</properties>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<nexus.host>https://nexus.cmd.navi-tech.in</nexus.host>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>nexus</id>
|
||||
<name>Snapshot</name>
|
||||
<url>${nexus.host}/repository/maven-snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>nexus</id>
|
||||
<name>Snapshot</name>
|
||||
<url>${nexus.host}/repository/maven-snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-model</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</dependency>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-model</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-db</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-db</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-cache</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-cache</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-util</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.navi.medici</groupId>
|
||||
<artifactId>litmus-util</artifactId>
|
||||
<version>2.0.8-RELEASE</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<version>2.17.256</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>software.amazon.awssdk</groupId>
|
||||
<artifactId>s3</artifactId>
|
||||
<version>2.17.256</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.opencsv</groupId>
|
||||
<artifactId>opencsv</artifactId>
|
||||
<version>5.5.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.opencsv</groupId>
|
||||
<artifactId>opencsv</artifactId>
|
||||
<version>5.5.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>1.6.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.persistence</groupId>
|
||||
<artifactId>javax.persistence-api</artifactId>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>1.6.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.persistence</groupId>
|
||||
<artifactId>javax.persistence-api</artifactId>
|
||||
<version>2.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>7.0.4.Final</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.hibernate</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>7.0.4.Final</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-inline</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -3,4 +3,13 @@ package com.navi.medici;
|
||||
public class Constants {
|
||||
public static final String ALL = "ALL";
|
||||
public static final String HEADER_EMAIL_ID = "X-Email-Id";
|
||||
|
||||
//Update Messages
|
||||
public static final String EXPERIMENT_CREATED = "Experiment created";
|
||||
public static final String STRATEGIES_UPDATED = "Strategies updated";
|
||||
public static final String VARIANTS_UPDATED = "Variants updated";
|
||||
public static final String EXPERIMENT_RELEASED = "Experiment has been released";
|
||||
public static final String EXPERIMENT_ROLL_BACKED = "Experiment has been roll-backed";
|
||||
public static final String PAUSE_EXPERIMENT = "Experiment has been paused";
|
||||
public static final String METRIC_ATTACHED = "Metric %s attached to experiment";
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.navi.medici.controller.v1;
|
||||
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.service.experiment.ExperimentService;
|
||||
import com.navi.medici.service.metric.MetricService;
|
||||
import com.navi.medici.service.segment.SegmentService;
|
||||
import com.navi.medici.service.team.TeamService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
@@ -19,6 +21,8 @@ import java.util.List;
|
||||
public class DropdownController {
|
||||
private final ExperimentService experimentService;
|
||||
private final TeamService teamService;
|
||||
private final MetricService metricService;
|
||||
private final SegmentService segmentService;
|
||||
|
||||
@GetMapping("/owners")
|
||||
public ResponseEntity<List<Dropdown>> getOwnersDropdown() {
|
||||
@@ -29,4 +33,14 @@ public class DropdownController {
|
||||
public ResponseEntity<List<Dropdown>> getTeamsDropdown() {
|
||||
return ResponseEntity.ok(teamService.getTeamsDropdown());
|
||||
}
|
||||
|
||||
@GetMapping("/metrics")
|
||||
public ResponseEntity<List<Dropdown>> getMetricsDropdown() {
|
||||
return ResponseEntity.ok(metricService.getMetricsDropdown());
|
||||
}
|
||||
|
||||
@GetMapping("/segments")
|
||||
public ResponseEntity<List<Dropdown>> getSegmentsDropdown() {
|
||||
return ResponseEntity.ok(segmentService.getSegmentsDropdown());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.navi.medici.controller.v1;
|
||||
|
||||
import com.navi.medici.Constants;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import com.navi.medici.request.v1.LitmusExperimentStrategyUpdate;
|
||||
import com.navi.medici.request.v1.VerticalUpdateRequest;
|
||||
@@ -21,6 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -56,7 +58,7 @@ public class ExperimentController {
|
||||
@GetMapping(value = "/vertical")
|
||||
@Timed(value = "litmus.fetch.vertical.experiment", percentiles = {0.95, 0.99})
|
||||
public ResponseEntity<LitmusExperimentCollection> fetchExperimentsForVertical(@RequestParam("vertical") String vertical,
|
||||
@RequestParam("polling_time") Long pollingTime) {
|
||||
@RequestParam("polling_time") Long pollingTime) {
|
||||
log.info("fetch experiment with polling time received. vertical: {}, polling_time: {}", vertical, pollingTime);
|
||||
var collection = experimentService.fetchAllExperimentsForVertical(vertical);
|
||||
|
||||
@@ -72,16 +74,20 @@ public class ExperimentController {
|
||||
@PutMapping(value = "/attach/variants/{experiment_id}", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed(value = "litmus.attach.variants", percentiles = {0.95, 0.99})
|
||||
public void attachVariants(@PathVariable("experiment_id") String experimentId,
|
||||
@RequestBody List<VariantDefinition> variantDefinitions) {
|
||||
experimentService.attachVariants(experimentId, variantDefinitions);
|
||||
@RequestBody List<VariantDefinition> variantDefinitions,
|
||||
HttpServletRequest httpServletRequest) {
|
||||
String emailId = httpServletRequest.getHeader(Constants.HEADER_EMAIL_ID);
|
||||
experimentService.attachVariants(experimentId, variantDefinitions, emailId);
|
||||
}
|
||||
|
||||
|
||||
@PutMapping(value = "/update/strategies", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Timed(value = "litmus.update.strategies", percentiles = {0.95, 0.99})
|
||||
public void updateStrategies(@RequestBody LitmusExperimentStrategyUpdate litmusExperimentStrategyUpdate) {
|
||||
public void updateStrategies(@RequestBody LitmusExperimentStrategyUpdate litmusExperimentStrategyUpdate,
|
||||
HttpServletRequest httpServletRequest) {
|
||||
String emailId = httpServletRequest.getHeader(Constants.HEADER_EMAIL_ID);
|
||||
log.info("strategy update request received. experiment_id: {}", litmusExperimentStrategyUpdate.getExperimentId());
|
||||
experimentService.updateStrategies(litmusExperimentStrategyUpdate);
|
||||
experimentService.updateStrategies(litmusExperimentStrategyUpdate, emailId);
|
||||
}
|
||||
|
||||
@PostMapping(value = "/update/vertical", consumes = MediaType.APPLICATION_JSON_VALUE)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
package com.navi.medici.controller.v2;
|
||||
|
||||
import com.navi.medici.Constants;
|
||||
import com.navi.medici.dto.ExperimentAuditTrailDTO;
|
||||
import com.navi.medici.enums.ExperimentStatus;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.request.v1.CreateExperimentRequest;
|
||||
import com.navi.medici.request.v1.ExperimentSearchRequest;
|
||||
import com.navi.medici.request.v1.SampleSizeRequest;
|
||||
import com.navi.medici.response.DashboardExperimentResponse;
|
||||
import com.navi.medici.response.ExperimentResponse;
|
||||
import com.navi.medici.response.PaginatedSearchResponse;
|
||||
import com.navi.medici.service.experiment.ExperimentService;
|
||||
@@ -14,13 +18,18 @@ import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/v2/experiments")
|
||||
@Log4j2
|
||||
@@ -31,11 +40,11 @@ public class ExperimentControllerV2 {
|
||||
|
||||
@GetMapping
|
||||
@Timed(value = "litmus.get.experiments", percentiles = {0.95, 0.99})
|
||||
public ResponseEntity<PaginatedSearchResponse<ExperimentResponse>> getExperiments(@RequestParam(value = "query", required = false) String query,
|
||||
@RequestParam(value = "page", required = false, defaultValue = "1") int page,
|
||||
@RequestParam(value = "size", required = false, defaultValue = "10") int size,
|
||||
@RequestParam(value = "status", required = false) String experimentStatus,
|
||||
@RequestParam(value = "owner", required = false) String owner) {
|
||||
public ResponseEntity<PaginatedSearchResponse<DashboardExperimentResponse>> getExperiments(@RequestParam(value = "query", required = false) String query,
|
||||
@RequestParam(value = "page", required = false, defaultValue = "1") int page,
|
||||
@RequestParam(value = "size", required = false, defaultValue = "10") int size,
|
||||
@RequestParam(value = "status", required = false) String experimentStatus,
|
||||
@RequestParam(value = "owner", required = false) String owner) {
|
||||
ExperimentSearchRequest request = ExperimentSearchRequest.builder()
|
||||
.query(query)
|
||||
.experimentStatus(ControllerUtils.checkForAllFilter(experimentStatus) ? ExperimentStatus.valueOf(experimentStatus) : null)
|
||||
@@ -44,7 +53,7 @@ public class ExperimentControllerV2 {
|
||||
.size(size)
|
||||
.build();
|
||||
log.info("fetching experiments with query:{}, experimentStatus:{}, owner:{}, page:{} and size:{}", query, experimentStatus, owner, page, size);
|
||||
PaginatedSearchResponse<ExperimentResponse> response = experimentService.getExperiments(request);
|
||||
PaginatedSearchResponse<DashboardExperimentResponse> response = experimentService.getExperiments(request);
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
@@ -54,4 +63,59 @@ public class ExperimentControllerV2 {
|
||||
@RequestHeader(Constants.HEADER_EMAIL_ID) String emailId) {
|
||||
return ResponseEntity.ok(experimentService.createExperiment(request, emailId));
|
||||
}
|
||||
|
||||
@GetMapping("/sample-size")
|
||||
public long getSampleSizeOfExperiment(@RequestParam("baselineConversion") double baselineConversion,
|
||||
@RequestParam("minimumDetectableEffect") double minimumDetectableEffect,
|
||||
@RequestParam("confidenceInterval") double confidenceInterval) {
|
||||
SampleSizeRequest request = SampleSizeRequest.builder()
|
||||
.baselineConversion(baselineConversion)
|
||||
.minimumDetectableEffect(minimumDetectableEffect)
|
||||
.confidenceInterval(confidenceInterval)
|
||||
.build();
|
||||
return experimentService.getSampleSizeForExperiment(request);
|
||||
}
|
||||
|
||||
@GetMapping("/fetch")
|
||||
@Timed(value = "litmus.fetch.experiment.api", percentiles = {0.95, 0.99})
|
||||
public ResponseEntity<ExperimentResponse> fetchExperiment(@RequestParam("name") String name) {
|
||||
log.info("fetching details of experiment: {}", name);
|
||||
return ResponseEntity.ok(experimentService.fetchExperiment(name));
|
||||
}
|
||||
|
||||
@PutMapping("/attach/metric/{experimentId}")
|
||||
@Timed(value = "litmus.attach.metric.api", percentiles = {0.95, 0.99})
|
||||
public void attachMetric(@PathVariable("experimentId") String experimentId,
|
||||
@RequestBody AttachMetricToExperimentRequest request,
|
||||
HttpServletRequest httpServletRequest) {
|
||||
log.info("Attach metric request received for experimentId: {}, request: {}", experimentId, request);
|
||||
String emailId = httpServletRequest.getHeader(Constants.HEADER_EMAIL_ID);
|
||||
experimentService.attachMetric(experimentId, request, emailId);
|
||||
}
|
||||
|
||||
@PutMapping("/release/{experimentId}")
|
||||
public void releaseExperiment(@PathVariable("experimentId") String experimentId,
|
||||
@RequestHeader(Constants.HEADER_EMAIL_ID) String emailId) {
|
||||
log.info("Release request received for experimentId: {}", experimentId);
|
||||
experimentService.releaseExperiment(experimentId, emailId);
|
||||
}
|
||||
|
||||
@PutMapping("/pause/{experimentId}")
|
||||
public void pauseExperiment(@PathVariable("experimentId") String experimentId,
|
||||
@RequestHeader(Constants.HEADER_EMAIL_ID) String emailId) {
|
||||
log.info("Pause request received for experimentId: {}", experimentId);
|
||||
experimentService.pauseExperiment(experimentId, emailId);
|
||||
}
|
||||
|
||||
@PutMapping("/rollback/{experimentId}")
|
||||
public void rollbackExperiment(@PathVariable("experimentId") String experimentId,
|
||||
@RequestHeader(Constants.HEADER_EMAIL_ID) String emailId) {
|
||||
log.info("Release request received for experimentId: {}", experimentId);
|
||||
experimentService.rollbackExperiment(experimentId, emailId);
|
||||
}
|
||||
|
||||
@GetMapping("/audit-trail/{experimentId}")
|
||||
public ResponseEntity<List<ExperimentAuditTrailDTO>> getExperimentAuditTrail(@PathVariable("experimentId") String experimentId) {
|
||||
return ResponseEntity.ok(experimentService.getExperimentAuditTrail(experimentId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.navi.medici.request.v1.SegmentSearchRequest;
|
||||
import com.navi.medici.response.PaginatedSearchResponse;
|
||||
import com.navi.medici.response.SegmentResponse;
|
||||
import com.navi.medici.service.segment.SegmentService;
|
||||
import com.navi.medici.util.ControllerUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@@ -41,7 +42,7 @@ public class SegmentControllerV2 {
|
||||
@RequestParam(value = "team", required = false) String teamName) {
|
||||
SegmentSearchRequest request = SegmentSearchRequest.builder()
|
||||
.query(query)
|
||||
.teamName(teamName)
|
||||
.teamName(ControllerUtils.checkForAllFilter(teamName) ? teamName : null)
|
||||
.page(page)
|
||||
.size(size)
|
||||
.build();
|
||||
|
||||
@@ -1,39 +1,97 @@
|
||||
package com.navi.medici.mapper;
|
||||
|
||||
import com.navi.medici.dto.ExperimentAuditTrailDTO;
|
||||
import com.navi.medici.dto.ExperimentImpact;
|
||||
import com.navi.medici.dto.ExperimentInfoDTO;
|
||||
import com.navi.medici.entity.ExperimentAuditTrailEntity;
|
||||
import com.navi.medici.entity.ExperimentEntity;
|
||||
import com.navi.medici.entity.ExperimentInfoEntity;
|
||||
import com.navi.medici.entity.ExperimentMetricMappingEntity;
|
||||
import com.navi.medici.enums.ExperimentMetricType;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.response.DashboardExperimentResponse;
|
||||
import com.navi.medici.response.ExperimentResponse;
|
||||
import com.navi.medici.strategy.ActivationStrategy;
|
||||
import com.navi.medici.util.JacksonUtils;
|
||||
import com.navi.medici.variants.VariantDefinition;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ExperimentMapper {
|
||||
public ExperimentResponse mapExperimentEntityToExperimentResponse(ExperimentEntity experimentEntity) {
|
||||
Optional<ExperimentMetricMappingEntity> primaryMetric = Objects.nonNull(experimentEntity.getExperimentMetricMappingEntities())
|
||||
? experimentEntity.getExperimentMetricMappingEntities().stream()
|
||||
private final JacksonUtils jacksonUtils;
|
||||
|
||||
public DashboardExperimentResponse mapExperimentEntityToDashBoardExperimentResponse(ExperimentEntity experimentEntity) {
|
||||
Optional<ExperimentMetricMappingEntity> primaryMetric = Objects.nonNull(experimentEntity.getExperimentMetricMappings())
|
||||
? experimentEntity.getExperimentMetricMappings().stream()
|
||||
.filter(mapping -> ExperimentMetricType.PRIMARY.equals(mapping.getExperimentMetricType()))
|
||||
.findFirst()
|
||||
: Optional.empty();
|
||||
return ExperimentResponse.builder()
|
||||
return DashboardExperimentResponse.builder()
|
||||
.experimentId(experimentEntity.getExperimentId())
|
||||
.experimentName(experimentEntity.getName())
|
||||
.experimentStatus(Objects.nonNull(experimentEntity.getExperimentInfoEntity())
|
||||
? experimentEntity.getExperimentInfoEntity().getExperimentStatus()
|
||||
.experimentStatus(Objects.nonNull(experimentEntity.getExperimentInfo())
|
||||
? experimentEntity.getExperimentInfo().getExperimentStatus()
|
||||
: null)
|
||||
.impact(ExperimentImpact.builder().build())
|
||||
.createdBy(experimentEntity.getCreatedBy())
|
||||
.testUsers(Objects.nonNull(experimentEntity.getExperimentInfoEntity())
|
||||
? experimentEntity.getExperimentInfoEntity().getTestUsers()
|
||||
.testUsers(Objects.nonNull(experimentEntity.getExperimentInfo())
|
||||
? experimentEntity.getExperimentInfo().getTestUsers()
|
||||
: 0)
|
||||
.primaryMetric(primaryMetric.isPresent()
|
||||
? primaryMetric.get().getMetric().getMetricName()
|
||||
: null)
|
||||
.build();
|
||||
}
|
||||
|
||||
private List<AttachMetricToExperimentRequest> getAttachMetricToExperimentRequestsFromExperimentMappings(Set<ExperimentMetricMappingEntity> experimentMetricMapping) {
|
||||
return experimentMetricMapping.stream()
|
||||
.map(entity -> AttachMetricToExperimentRequest.builder()
|
||||
.metricName(entity.getMetric().getMetricName())
|
||||
.experimentMetricType(entity.getExperimentMetricType())
|
||||
.build())
|
||||
.toList();
|
||||
}
|
||||
|
||||
public ExperimentResponse mapExperimentEntityToExperimentResponse(ExperimentEntity experimentEntity) {
|
||||
return ExperimentResponse.builder()
|
||||
.experimentId(experimentEntity.getExperimentId())
|
||||
.experimentName(experimentEntity.getName())
|
||||
.description(experimentEntity.getDescription())
|
||||
.strategies(jacksonUtils.stringToListObject(experimentEntity.getStrategies(), ActivationStrategy.class))
|
||||
.variants(jacksonUtils.stringToListObject(experimentEntity.getVariants(), VariantDefinition.class))
|
||||
.type(experimentEntity.getType())
|
||||
.experimentInfo(mapExperimentInfoEntityToExperimentInfoDTO(experimentEntity.getExperimentInfo()))
|
||||
.vertical(experimentEntity.getVertical())
|
||||
.metrics(getAttachMetricToExperimentRequestsFromExperimentMappings(experimentEntity.getExperimentMetricMappings()))
|
||||
.createdAt(experimentEntity.getCreatedAt())
|
||||
.updatedAt(experimentEntity.getUpdatedAt())
|
||||
.createdBy(experimentEntity.getCreatedBy())
|
||||
.build();
|
||||
}
|
||||
|
||||
public ExperimentInfoDTO mapExperimentInfoEntityToExperimentInfoDTO(ExperimentInfoEntity experimentInfo) {
|
||||
if (Objects.isNull(experimentInfo)) {
|
||||
return null;
|
||||
}
|
||||
return ExperimentInfoDTO.builder()
|
||||
.experimentMetadata(experimentInfo.getExperimentMetadata())
|
||||
.testUsers(experimentInfo.getTestUsers())
|
||||
.experimentStatus(experimentInfo.getExperimentStatus())
|
||||
.build();
|
||||
}
|
||||
|
||||
public ExperimentAuditTrailDTO mapExperimentAuditTrailEntityToExperimentAuditTrailDTO(ExperimentAuditTrailEntity experimentAuditTrail) {
|
||||
return ExperimentAuditTrailDTO.builder()
|
||||
.log(experimentAuditTrail.getLog())
|
||||
.createdAt(experimentAuditTrail.getCreatedAt())
|
||||
.createdBy(experimentAuditTrail.getCreatedBy())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.navi.medici.service.experiment;
|
||||
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.dto.ExperimentAuditTrailDTO;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.request.v1.CreateExperimentRequest;
|
||||
import com.navi.medici.request.v1.ExperimentSearchRequest;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import com.navi.medici.request.v1.LitmusExperimentStrategyUpdate;
|
||||
import com.navi.medici.request.v1.SampleSizeRequest;
|
||||
import com.navi.medici.request.v1.VerticalUpdateRequest;
|
||||
import com.navi.medici.response.DashboardExperimentResponse;
|
||||
import com.navi.medici.response.ExperimentResponse;
|
||||
import com.navi.medici.response.LitmusExperimentCollection;
|
||||
import com.navi.medici.response.PaginatedSearchResponse;
|
||||
@@ -21,20 +25,33 @@ public interface ExperimentService {
|
||||
|
||||
LitmusExperiment fetchExperimentByName(String experimentName);
|
||||
|
||||
void attachVariants(String experimentId, List<VariantDefinition> variantDefinitions);
|
||||
void attachVariants(String experimentId, List<VariantDefinition> variantDefinitions, String emailId);
|
||||
|
||||
LitmusExperimentCollection fetchAllExperimentsForVerticalInPollingTime(String vertical, Long pollingTime);
|
||||
|
||||
void updateStrategies(LitmusExperimentStrategyUpdate litmusExperimentStrategyUpdate);
|
||||
void updateStrategies(LitmusExperimentStrategyUpdate litmusExperimentStrategyUpdate, String emailId);
|
||||
|
||||
void updateVertical(VerticalUpdateRequest verticalUpdateRequest);
|
||||
|
||||
LitmusExperimentCollection fetchAllExperimentsForVertical(String vertical);
|
||||
|
||||
PaginatedSearchResponse<ExperimentResponse> getExperiments(ExperimentSearchRequest request);
|
||||
PaginatedSearchResponse<DashboardExperimentResponse> getExperiments(ExperimentSearchRequest request);
|
||||
|
||||
ExperimentResponse createExperiment(CreateExperimentRequest request, String emailId);
|
||||
DashboardExperimentResponse createExperiment(CreateExperimentRequest request, String emailId);
|
||||
|
||||
long getSampleSizeForExperiment(SampleSizeRequest request);
|
||||
|
||||
List<Dropdown> getOwnersDropdown();
|
||||
|
||||
ExperimentResponse fetchExperiment(String name);
|
||||
|
||||
void attachMetric(String experimentId, AttachMetricToExperimentRequest request, String emailId);
|
||||
|
||||
void releaseExperiment(String experimentId, String emailId);
|
||||
|
||||
void pauseExperiment(String experimentId, String emailId);
|
||||
|
||||
void rollbackExperiment(String experimentId, String emailId);
|
||||
|
||||
List<ExperimentAuditTrailDTO> getExperimentAuditTrail(String experimentId);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.navi.medici.service.experiment;
|
||||
|
||||
import com.navi.medici.Constants;
|
||||
import com.navi.medici.config.LitmusCoreConfig;
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.dto.ExperimentAuditTrailDTO;
|
||||
import com.navi.medici.dto.ExperimentMetadata;
|
||||
import com.navi.medici.entity.ExperimentAuditTrailEntity;
|
||||
import com.navi.medici.entity.ExperimentEntity;
|
||||
import com.navi.medici.entity.ExperimentInfoEntity;
|
||||
import com.navi.medici.entity.ExperimentMetricMappingEntity;
|
||||
@@ -14,23 +17,28 @@ import com.navi.medici.enums.ExperimentType;
|
||||
import com.navi.medici.exceptions.BadRequestException;
|
||||
import com.navi.medici.exceptions.ExperimentAlreadyExistException;
|
||||
import com.navi.medici.exceptions.LitmusExperimentNotFoundException;
|
||||
import com.navi.medici.exceptions.NoExperimentsFoundForVerticalException;
|
||||
import com.navi.medici.exceptions.UnableToChangeExperimentStatusException;
|
||||
import com.navi.medici.mapper.ExperimentMapper;
|
||||
import com.navi.medici.query.experiment.IExperimentQuery;
|
||||
import com.navi.medici.query.experimentaudittrail.IExperimentAuditTrailQuery;
|
||||
import com.navi.medici.query.experimentinfo.IExperimentInfoQuery;
|
||||
import com.navi.medici.query.experimentmetricmapping.IExperimentMetricMappingQuery;
|
||||
import com.navi.medici.query.experimentmetricresult.IExperimentMetricResultQuery;
|
||||
import com.navi.medici.query.metric.IMetricQuery;
|
||||
import com.navi.medici.query.team.ITeamQuery;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.request.v1.CreateExperimentRequest;
|
||||
import com.navi.medici.request.v1.ExperimentSearchRequest;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import com.navi.medici.request.v1.LitmusExperimentStrategyUpdate;
|
||||
import com.navi.medici.request.v1.SampleSizeRequest;
|
||||
import com.navi.medici.request.v1.VerticalUpdateRequest;
|
||||
import com.navi.medici.response.DashboardExperimentResponse;
|
||||
import com.navi.medici.response.ExperimentResponse;
|
||||
import com.navi.medici.response.LitmusExperimentCollection;
|
||||
import com.navi.medici.response.PaginatedSearchResponse;
|
||||
import com.navi.medici.specification.ExperimentSpecification;
|
||||
import com.navi.medici.stats.SampleSizeCalculator;
|
||||
import com.navi.medici.strategy.ActivationStrategy;
|
||||
import com.navi.medici.util.JacksonUtils;
|
||||
import com.navi.medici.validator.ExperimentValidator;
|
||||
@@ -46,8 +54,12 @@ import org.springframework.stereotype.Service;
|
||||
import javax.transaction.Transactional;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -65,6 +77,7 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
private final ExperimentValidator experimentValidator;
|
||||
private final IMetricQuery metricQuery;
|
||||
private final ITeamQuery teamQuery;
|
||||
private final IExperimentAuditTrailQuery experimentAuditTrailQuery;
|
||||
|
||||
private final IExperimentInfoQuery experimentInfoQuery;
|
||||
private final IExperimentMetricResultQuery experimentMetricResultQuery;
|
||||
@@ -72,18 +85,29 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public ExperimentResponse createExperiment(CreateExperimentRequest request, String emailId) {
|
||||
public DashboardExperimentResponse createExperiment(CreateExperimentRequest request, String emailId) {
|
||||
log.info("Received create experiment request : {}", request.toString());
|
||||
experimentValidator.validateCreateExperimentRequest(request);
|
||||
Optional<TeamEntity> team = teamQuery.findByTeamName(request.getVertical());
|
||||
List<ActivationStrategy> strategies = request.getStrategies();
|
||||
List<VariantDefinition> variants = request.getVariants();
|
||||
strategies.forEach(strategy -> {
|
||||
Map<String, String> parameters = new HashMap<>(strategy.getParameters());
|
||||
if (strategy.getName().equals("flexibleRollout")) {
|
||||
parameters.put("groupId", request.getExperimentName() + "-group-id");
|
||||
} else {
|
||||
parameters.put("groupId", strategy.getParameters().get("segment") + "-group-id");
|
||||
}
|
||||
strategy.setParameters(parameters);
|
||||
});
|
||||
ExperimentEntity experiment = ExperimentEntity.builder()
|
||||
.name(request.getExperimentName())
|
||||
.experimentId(UUID.randomUUID().toString())
|
||||
.description(request.getDescription())
|
||||
.enabled(true)
|
||||
.archived(false)
|
||||
.strategies(jacksonUtils.objectToString(request.getStrategies()))
|
||||
.variants(jacksonUtils.objectToString(request.getVariants()))
|
||||
.strategies(jacksonUtils.objectToString(strategies))
|
||||
.variants(jacksonUtils.objectToString(variants))
|
||||
.type(ExperimentType.EXPERIMENT)
|
||||
.vertical(request.getVertical())
|
||||
.createdBy(emailId)
|
||||
@@ -119,7 +143,8 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
experimentMetricMappingQuery.save(secondaryMetricMapping);
|
||||
}
|
||||
ExperimentEntity savedExperiment = experimentQuery.save(experiment);
|
||||
return experimentMapper.mapExperimentEntityToExperimentResponse(savedExperiment);
|
||||
saveAuditTrail(experiment.getExperimentId(), Constants.EXPERIMENT_CREATED, emailId);
|
||||
return experimentMapper.mapExperimentEntityToDashBoardExperimentResponse(savedExperiment);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,12 +178,12 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public PaginatedSearchResponse<ExperimentResponse> getExperiments(ExperimentSearchRequest request) {
|
||||
public PaginatedSearchResponse<DashboardExperimentResponse> getExperiments(ExperimentSearchRequest request) {
|
||||
Pageable paging = PageRequest.of(request.getPage() - 1, request.getSize());
|
||||
Page<ExperimentEntity> experimentEntities = experimentQuery.findAll(ExperimentSpecification.getExperiments(request), paging);
|
||||
return PaginatedSearchResponse.<ExperimentResponse>builder()
|
||||
return PaginatedSearchResponse.<DashboardExperimentResponse>builder()
|
||||
.data(experimentEntities.stream()
|
||||
.map(experimentMapper::mapExperimentEntityToExperimentResponse)
|
||||
.map(experimentMapper::mapExperimentEntityToDashBoardExperimentResponse)
|
||||
.collect(Collectors.toList()))
|
||||
.page(experimentEntities.getNumber() + 1)
|
||||
.size(experimentEntities.getSize())
|
||||
@@ -191,7 +216,8 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachVariants(String experimentId, List<VariantDefinition> variantDefinitions) {
|
||||
public void attachVariants(String experimentId, List<VariantDefinition> variantDefinitions, String emailId) {
|
||||
experimentValidator.validateAttachVariantsRequest(variantDefinitions);
|
||||
var experimentOptional = experimentQuery.findByExperimentId(experimentId);
|
||||
if (experimentOptional.isEmpty()) {
|
||||
log.error("experiment_id is not found. experiment_id: {}", experimentId);
|
||||
@@ -200,6 +226,11 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
|
||||
var experiment = experimentOptional.get();
|
||||
experiment.setVariants(jacksonUtils.objectToString(variantDefinitions));
|
||||
log.info("updating variants of experiment: {}, new_variants: {}, old_variants: {}",
|
||||
experiment.getName(),
|
||||
jacksonUtils.objectToString(variantDefinitions),
|
||||
experiment.getVariants());
|
||||
saveAuditTrail(experimentId, Constants.VARIANTS_UPDATED, emailId);
|
||||
|
||||
experimentQuery.save(experiment);
|
||||
}
|
||||
@@ -222,19 +253,21 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStrategies(LitmusExperimentStrategyUpdate litmusExperimentStrategyUpdate) {
|
||||
public void updateStrategies(LitmusExperimentStrategyUpdate litmusExperimentStrategyUpdate, String emailId) {
|
||||
var experimentExist = experimentQuery.findByExperimentId(litmusExperimentStrategyUpdate.getExperimentId());
|
||||
if (experimentExist.isEmpty()) {
|
||||
throw new LitmusExperimentNotFoundException(String.format("experiment %s not found in system",
|
||||
litmusExperimentStrategyUpdate.getExperimentId()));
|
||||
}
|
||||
|
||||
log.info("updating strategies. experiment_id: {}, strategies: {}",
|
||||
litmusExperimentStrategyUpdate.getExperimentId(),
|
||||
jacksonUtils.objectToString(litmusExperimentStrategyUpdate.getStrategies()));
|
||||
log.info("updating strategies. experiment: {}, new_strategies: {}, old_strategies:{}",
|
||||
experimentExist.get().getName(),
|
||||
jacksonUtils.objectToString(litmusExperimentStrategyUpdate.getStrategies()),
|
||||
experimentExist.get().getStrategies());
|
||||
var litmusExperiment = experimentExist.get();
|
||||
litmusExperiment.setStrategies(jacksonUtils.objectToString(litmusExperimentStrategyUpdate.getStrategies()));
|
||||
|
||||
saveAuditTrail(litmusExperiment.getExperimentId(), Constants.STRATEGIES_UPDATED, emailId);
|
||||
litmusExperiment.setUpdatedBy(emailId);
|
||||
experimentQuery.save(litmusExperiment);
|
||||
}
|
||||
|
||||
@@ -256,7 +289,7 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
public LitmusExperimentCollection fetchAllExperimentsForVertical(String vertical) {
|
||||
var experimentEntities = experimentQuery.findByVertical(vertical);
|
||||
if (experimentEntities.isEmpty()) {
|
||||
throw new NoExperimentsFoundForVerticalException(String.format("no experiments found for this vertical %s",
|
||||
throw new LitmusExperimentNotFoundException(String.format("no experiments found for this vertical %s",
|
||||
vertical));
|
||||
}
|
||||
|
||||
@@ -307,4 +340,120 @@ public class ExperimentServiceImpl implements ExperimentService {
|
||||
.vertical(experimentEntity.getVertical())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSampleSizeForExperiment(SampleSizeRequest request) {
|
||||
double baselineConversion = request.getBaselineConversion();
|
||||
double minimumDetectableEffect = request.getMinimumDetectableEffect();
|
||||
double confidenceInterval = request.getConfidenceInterval();
|
||||
log.info("Calculating sample size for baselineConversion = {}, minimumDetectableEffect = {}, confidenceInterval = {}",
|
||||
baselineConversion,
|
||||
minimumDetectableEffect,
|
||||
confidenceInterval);
|
||||
double alpha = (100.0 - confidenceInterval) / 100;
|
||||
double power = 0.8;
|
||||
return SampleSizeCalculator.sampleCalculator(baselineConversion, minimumDetectableEffect, alpha, power);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExperimentResponse fetchExperiment(String name) {
|
||||
Optional<ExperimentEntity> experiment = experimentQuery.findByName(name);
|
||||
if (experiment.isEmpty()) {
|
||||
throw new LitmusExperimentNotFoundException("Experiment with name : " + name + "is not present");
|
||||
}
|
||||
return experimentMapper.mapExperimentEntityToExperimentResponse(experiment.get());
|
||||
}
|
||||
|
||||
private ExperimentEntity getExperimentEntityFromId(String experimentId) {
|
||||
Optional<ExperimentEntity> experimentEntity = experimentQuery.findByExperimentId(experimentId);
|
||||
if (experimentEntity.isEmpty()) {
|
||||
throw new LitmusExperimentNotFoundException("Experiment with id : " + experimentId + " is not present");
|
||||
}
|
||||
return experimentEntity.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void attachMetric(String experimentId, AttachMetricToExperimentRequest request, String emailId) {
|
||||
experimentValidator.validateAttachMetricRequest(request);
|
||||
Optional<MetricEntity> metric = metricQuery.findByMetricName(request.getMetricName());
|
||||
ExperimentEntity experiment = getExperimentEntityFromId(experimentId);
|
||||
ExperimentMetricMappingEntity experimentMetricMapping = ExperimentMetricMappingEntity.builder()
|
||||
.experiment(experiment)
|
||||
.metric(metric.get())
|
||||
.experimentMetricType(request.getExperimentMetricType())
|
||||
.build();
|
||||
Set<ExperimentMetricMappingEntity> experimentMetricMappings = new HashSet<>(experiment.getExperimentMetricMappings());
|
||||
experimentMetricMappings.add(experimentMetricMapping);
|
||||
experiment.setExperimentMetricMappings(experimentMetricMappings);
|
||||
experiment.setUpdatedBy(emailId);
|
||||
saveAuditTrail(experimentId, String.format(Constants.METRIC_ATTACHED, request.getMetricName()), emailId);
|
||||
|
||||
experimentQuery.save(experiment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void releaseExperiment(String experimentId, String emailId) {
|
||||
ExperimentEntity experiment = getExperimentEntityFromId(experimentId);
|
||||
log.info("Releasing experiment with experimentId: {}", experimentId);
|
||||
ExperimentStatus experimentStatus = experiment.getExperimentInfo().getExperimentStatus();
|
||||
if (!experimentStatus.equals(ExperimentStatus.RUNNING)) {
|
||||
throw new UnableToChangeExperimentStatusException("Unable to change release experiment as experiment is in " + experimentStatus.toString() + " state");
|
||||
}
|
||||
experiment.getExperimentInfo().setExperimentStatus(ExperimentStatus.DONE);
|
||||
experiment.setType(RELEASE);
|
||||
experiment.setUpdatedBy(emailId);
|
||||
saveAuditTrail(experimentId, Constants.EXPERIMENT_RELEASED, emailId);
|
||||
experiment.setUpdatedBy(emailId);
|
||||
experimentQuery.save(experiment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pauseExperiment(String experimentId, String emailId) {
|
||||
ExperimentEntity experiment = getExperimentEntityFromId(experimentId);
|
||||
log.info("Pausing experiment with experimentId: {}", experimentId);
|
||||
ExperimentStatus experimentStatus = experiment.getExperimentInfo().getExperimentStatus();
|
||||
if (!experimentStatus.equals(ExperimentStatus.RUNNING)) {
|
||||
throw new UnableToChangeExperimentStatusException("Unable to change pause experiment as experiment is in " + experimentStatus.toString() + " state");
|
||||
}
|
||||
experiment.getExperimentInfo().setExperimentStatus(ExperimentStatus.HOLD);
|
||||
experiment.setType(KILL_SWITCH);
|
||||
experiment.setUpdatedBy(emailId);
|
||||
saveAuditTrail(experimentId, Constants.PAUSE_EXPERIMENT, emailId);
|
||||
experiment.setUpdatedBy(emailId);
|
||||
experimentQuery.save(experiment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollbackExperiment(String experimentId, String emailId) {
|
||||
ExperimentEntity experiment = getExperimentEntityFromId(experimentId);
|
||||
log.info("Rollbacking experiment with experimentId: {}", experimentId);
|
||||
ExperimentStatus experimentStatus = experiment.getExperimentInfo().getExperimentStatus();
|
||||
if (!experimentStatus.equals(ExperimentStatus.RUNNING)) {
|
||||
throw new UnableToChangeExperimentStatusException("Unable to change rollback experiment as experiment is in " + experimentStatus.toString() + " state");
|
||||
}
|
||||
experiment.getExperimentInfo().setExperimentStatus(ExperimentStatus.DONE);
|
||||
experiment.setType(KILL_SWITCH);
|
||||
experiment.setUpdatedBy(emailId);
|
||||
saveAuditTrail(experimentId, Constants.EXPERIMENT_ROLL_BACKED, emailId);
|
||||
experiment.setUpdatedBy(emailId);
|
||||
experimentQuery.save(experiment);
|
||||
}
|
||||
|
||||
private void saveAuditTrail(String experimentId, String log, String emailId) {
|
||||
ExperimentEntity experiment = getExperimentEntityFromId(experimentId);
|
||||
ExperimentAuditTrailEntity experimentAuditTrail = ExperimentAuditTrailEntity.builder()
|
||||
.experiment(experiment)
|
||||
.log(log)
|
||||
.createdBy(emailId)
|
||||
.build();
|
||||
experimentAuditTrailQuery.save(experimentAuditTrail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExperimentAuditTrailDTO> getExperimentAuditTrail(String experimentId) {
|
||||
ExperimentEntity experiment = getExperimentEntityFromId(experimentId);
|
||||
return experiment.getExperimentAuditTrails().stream()
|
||||
.map(experimentMapper::mapExperimentAuditTrailEntityToExperimentAuditTrailDTO)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
package com.navi.medici.service.metric;
|
||||
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.request.v1.CreateMetricRequest;
|
||||
import com.navi.medici.request.v1.MetricSearchRequest;
|
||||
import com.navi.medici.response.MetricResponse;
|
||||
import com.navi.medici.response.PaginatedSearchResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface MetricService {
|
||||
MetricResponse createMetric(CreateMetricRequest createMetricRequest, String emailId);
|
||||
|
||||
PaginatedSearchResponse<MetricResponse> getMetrics(MetricSearchRequest request);
|
||||
|
||||
List<Dropdown> getMetricsDropdown();
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.navi.medici.service.metric;
|
||||
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.entity.MetricEntity;
|
||||
import com.navi.medici.mapper.MetricMapper;
|
||||
import com.navi.medici.query.experimentmetricmapping.IExperimentMetricMappingQuery;
|
||||
@@ -69,4 +70,15 @@ public class MetricServiceImpl implements MetricService {
|
||||
.totalSize(metricEntities.getTotalElements())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Dropdown> getMetricsDropdown() {
|
||||
List<String> metricNames = metricQuery.getAllMetricNames();
|
||||
return metricNames.stream()
|
||||
.map(metricName -> Dropdown.builder()
|
||||
.label(metricName)
|
||||
.value(metricName)
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.navi.medici.service.segment;
|
||||
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.request.v1.AddIdToSegmentRequest;
|
||||
import com.navi.medici.request.v1.CreateSegmentRequest;
|
||||
import com.navi.medici.request.v1.DeleteIdsFromSegmentRequest;
|
||||
@@ -9,6 +10,8 @@ import com.navi.medici.response.PaginatedSearchResponse;
|
||||
import com.navi.medici.response.SegmentResponse;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface SegmentService {
|
||||
|
||||
void create(SegmentRequest segmentRequest);
|
||||
@@ -26,4 +29,6 @@ public interface SegmentService {
|
||||
SegmentResponse createSegment(CreateSegmentRequest request, String emailId);
|
||||
|
||||
PaginatedSearchResponse<SegmentResponse> getSegments(SegmentSearchRequest request);
|
||||
|
||||
List<Dropdown> getSegmentsDropdown();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.navi.medici.service.segment;
|
||||
|
||||
import com.navi.medici.command.CacheCommands;
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.entity.SegmentEntity;
|
||||
import com.navi.medici.entity.TeamEntity;
|
||||
import com.navi.medici.exceptions.SegmentAlreadyExistException;
|
||||
@@ -176,4 +177,15 @@ public class SegmentServiceImpl implements SegmentService {
|
||||
.totalSize(segmentEntities.getTotalElements())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Dropdown> getSegmentsDropdown() {
|
||||
List<String> semgentNames = segmentQuery.getAllSegmentNames();
|
||||
return semgentNames.stream()
|
||||
.map(semgentName -> Dropdown.builder()
|
||||
.label(semgentName)
|
||||
.value(semgentName)
|
||||
.build())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ExperimentSpecification {
|
||||
});
|
||||
|
||||
Optional.ofNullable(status).ifPresent(value -> {
|
||||
Join<ExperimentEntity, ExperimentInfoEntity> experimentInfo = root.join("experimentInfoEntity");
|
||||
Join<ExperimentEntity, ExperimentInfoEntity> experimentInfo = root.join("experimentInfo");
|
||||
predicateList.add(
|
||||
cb.equal(
|
||||
experimentInfo.get("experimentStatus"),
|
||||
|
||||
@@ -2,17 +2,20 @@ package com.navi.medici.validator;
|
||||
|
||||
import com.navi.medici.entity.MetricEntity;
|
||||
import com.navi.medici.exceptions.BadRequestException;
|
||||
import com.navi.medici.exceptions.VariantWeightSumNotHundredException;
|
||||
import com.navi.medici.query.experiment.IExperimentQuery;
|
||||
import com.navi.medici.query.metric.IMetricQuery;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.request.v1.CreateExperimentRequest;
|
||||
import com.navi.medici.variants.VariantDefinition;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@Data
|
||||
@@ -23,7 +26,6 @@ public class ExperimentValidator {
|
||||
private final IExperimentQuery experimentQuery;
|
||||
private final IMetricQuery metricQuery;
|
||||
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
public void validateCreateExperimentRequest(CreateExperimentRequest request) {
|
||||
if (StringUtils.isBlank(request.getExperimentName()) || request.getExperimentName().contains(" ")) {
|
||||
throw new BadRequestException("Experiment Name should be of the format '<vertical>-<service>-<name>-<env>'");
|
||||
@@ -35,5 +37,37 @@ public class ExperimentValidator {
|
||||
if (primaryMetric.isEmpty()) {
|
||||
throw new BadRequestException("Primary metric required for creation of experiment");
|
||||
}
|
||||
if (Objects.nonNull(request.getVariants())) {
|
||||
int sum = 0;
|
||||
for (VariantDefinition variant : request.getVariants()) {
|
||||
sum += variant.getWeight();
|
||||
}
|
||||
if (sum != 100) {
|
||||
throw new VariantWeightSumNotHundredException("Sum of weights of variants should be 100");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void validateAttachMetricRequest(AttachMetricToExperimentRequest request) {
|
||||
if (StringUtils.isBlank(request.getMetricName())) {
|
||||
throw new BadRequestException("Metric Name should not be empty or null");
|
||||
}
|
||||
if (Objects.isNull(request.getExperimentMetricType())) {
|
||||
throw new BadRequestException("experiment-metric Type can't be null");
|
||||
}
|
||||
Optional<MetricEntity> metric = metricQuery.findByMetricName(request.getMetricName());
|
||||
if (metric.isEmpty()) {
|
||||
throw new BadRequestException("Metric With name: " + request.getMetricName() + " does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
public void validateAttachVariantsRequest(List<VariantDefinition> variantDefinitions) {
|
||||
int sum = 0;
|
||||
for (VariantDefinition variant : variantDefinitions) {
|
||||
sum += variant.getWeight();
|
||||
}
|
||||
if (sum != 100) {
|
||||
throw new VariantWeightSumNotHundredException("Sum of weights of variants should be 100");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,13 @@ package com.navi.medici;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.dto.ExperimentAuditTrailDTO;
|
||||
import com.navi.medici.dto.ExperimentImpact;
|
||||
import com.navi.medici.dto.ExperimentInfoDTO;
|
||||
import com.navi.medici.dto.ExperimentMetadata;
|
||||
import com.navi.medici.dto.VariantStat;
|
||||
import com.navi.medici.entity.ExperimentAuditTrailEntity;
|
||||
import com.navi.medici.entity.ExperimentEntity;
|
||||
import com.navi.medici.entity.ExperimentInfoEntity;
|
||||
import com.navi.medici.entity.ExperimentMetricMappingEntity;
|
||||
import com.navi.medici.entity.MetricEntity;
|
||||
import com.navi.medici.entity.SegmentEntity;
|
||||
@@ -16,6 +19,7 @@ import com.navi.medici.enums.ExperimentType;
|
||||
import com.navi.medici.enums.MetricType;
|
||||
import com.navi.medici.enums.SegmentType;
|
||||
import com.navi.medici.request.v1.AddIdToSegmentRequest;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.request.v1.CreateExperimentRequest;
|
||||
import com.navi.medici.request.v1.CreateMetricRequest;
|
||||
import com.navi.medici.request.v1.CreateSegmentRequest;
|
||||
@@ -25,9 +29,11 @@ import com.navi.medici.request.v1.ExperimentSearchRequest;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import com.navi.medici.request.v1.LitmusExperimentStrategyUpdate;
|
||||
import com.navi.medici.request.v1.MetricSearchRequest;
|
||||
import com.navi.medici.request.v1.SampleSizeRequest;
|
||||
import com.navi.medici.request.v1.SegmentRequest;
|
||||
import com.navi.medici.request.v1.SegmentSearchRequest;
|
||||
import com.navi.medici.request.v1.VerticalUpdateRequest;
|
||||
import com.navi.medici.response.DashboardExperimentResponse;
|
||||
import com.navi.medici.response.ExperimentResponse;
|
||||
import com.navi.medici.response.MetricResponse;
|
||||
import com.navi.medici.response.SegmentResponse;
|
||||
@@ -47,6 +53,7 @@ import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@@ -92,19 +99,6 @@ public class TestUtils {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static List<VariantStat> getVariantStats(int count) {
|
||||
List<VariantStat> variantStats = new ArrayList<>();
|
||||
for (int i = 1; i <= count; i++) {
|
||||
variantStats.add(
|
||||
VariantStat.builder()
|
||||
.variantName(String.format("test variant %d", i))
|
||||
.variantMetricValue(1.0)
|
||||
.build()
|
||||
);
|
||||
}
|
||||
return variantStats;
|
||||
}
|
||||
|
||||
public static MetricEntity getMetricEntity() {
|
||||
return MetricEntity.builder()
|
||||
.id(1L)
|
||||
@@ -129,14 +123,35 @@ public class TestUtils {
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public static ExperimentMetricMappingEntity getExperimentMetricMappingEntity() {
|
||||
return ExperimentMetricMappingEntity.builder()
|
||||
.experiment(getExperimentEntity())
|
||||
.metric(getMetricEntity())
|
||||
.experiment(ExperimentEntity.builder()
|
||||
.experimentId("test-uuid-experiment")
|
||||
.name("test experiment")
|
||||
.build())
|
||||
.experimentMetricType(ExperimentMetricType.PRIMARY)
|
||||
.metric(MetricEntity.builder()
|
||||
.metricId("test-uuid-metric")
|
||||
.metricName("test metric")
|
||||
.build())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public static ExperimentInfoEntity getExperimentInfoEntity() {
|
||||
return ExperimentInfoEntity.builder()
|
||||
.experiment(ExperimentEntity.builder()
|
||||
.experimentId("test-uuid-experiment")
|
||||
.name("test experiment")
|
||||
.build())
|
||||
.experimentMetadata(getExperimentMetadata())
|
||||
.experimentStatus(ExperimentStatus.CREATED)
|
||||
.team(getTeamEntity())
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public static ExperimentEntity getExperimentEntity() {
|
||||
return ExperimentEntity.builder()
|
||||
.id(1L)
|
||||
@@ -153,8 +168,11 @@ public class TestUtils {
|
||||
.vertical(getTeamEntity().getTeamName())
|
||||
.createdBy(TEST_USER)
|
||||
.updatedBy(TEST_USER)
|
||||
.experimentInfo(getExperimentInfoEntity())
|
||||
.experimentMetricMappings(Set.of(getExperimentMetricMappingEntity()))
|
||||
.createdAt(getLocalDateTime())
|
||||
.updatedAt(getLocalDateTime())
|
||||
.experimentAuditTrails(Set.of(getExperimentAuditTrailEntity()))
|
||||
.build();
|
||||
}
|
||||
|
||||
@@ -165,7 +183,7 @@ public class TestUtils {
|
||||
.archived(false)
|
||||
.primaryMetric("test metric")
|
||||
.secondaryMetric("test metric")
|
||||
.strategies(new ArrayList<>())
|
||||
.strategies(List.of(getActivationStrategy()))
|
||||
.variants(new ArrayList<>())
|
||||
.type("EXPERIMENT")
|
||||
.vertical("PL")
|
||||
@@ -185,8 +203,8 @@ public class TestUtils {
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ExperimentResponse getExperimentResponse() {
|
||||
return ExperimentResponse.builder()
|
||||
public static DashboardExperimentResponse getDashboardExperimentResponse() {
|
||||
return DashboardExperimentResponse.builder()
|
||||
.experimentId("test-uuid-experiment")
|
||||
.experimentName("test experiment")
|
||||
.createdBy(TEST_USER)
|
||||
@@ -366,4 +384,60 @@ public class TestUtils {
|
||||
.size(15)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static SampleSizeRequest getSampleSizeRequest() {
|
||||
return SampleSizeRequest.builder()
|
||||
.baselineConversion(9.0)
|
||||
.confidenceInterval(95)
|
||||
.minimumDetectableEffect(0.3)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ExperimentAuditTrailEntity getExperimentAuditTrailEntity() {
|
||||
return ExperimentAuditTrailEntity.builder()
|
||||
.id(1L)
|
||||
.log("test log")
|
||||
.createdBy(TEST_USER)
|
||||
.createdAt(getLocalDateTime())
|
||||
.updatedAt(getLocalDateTime())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ExperimentAuditTrailDTO getExperimentAuditTrailDto() {
|
||||
return ExperimentAuditTrailDTO.builder()
|
||||
.log("test log")
|
||||
.createdBy(TEST_USER)
|
||||
.createdAt(getLocalDateTime())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static AttachMetricToExperimentRequest getAttachMetricToExperimentRequest() {
|
||||
return AttachMetricToExperimentRequest.builder()
|
||||
.metricName(getMetricEntity().getMetricName())
|
||||
.experimentMetricType(ExperimentMetricType.SECONDARY)
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ExperimentInfoDTO getExperimentInfoDTO() {
|
||||
return ExperimentInfoDTO.builder()
|
||||
.experimentStatus(getExperimentInfoEntity().getExperimentStatus())
|
||||
.testUsers(0L)
|
||||
.experimentMetadata(getExperimentMetadata())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static ExperimentResponse getExperimentResponse() {
|
||||
return ExperimentResponse.builder()
|
||||
.experimentId(getExperimentEntity().getExperimentId())
|
||||
.experimentName(getExperimentEntity().getName())
|
||||
.description(getExperimentEntity().getDescription())
|
||||
.strategies(List.of(getActivationStrategy()))
|
||||
.variants(getVariants(2))
|
||||
.type(getExperimentEntity().getType())
|
||||
.experimentInfo(getExperimentInfoDTO())
|
||||
.createdBy(TEST_USER)
|
||||
.createdAt(getLocalDateTime())
|
||||
.updatedAt(getLocalDateTime())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.navi.medici.controller.v1;
|
||||
|
||||
import com.navi.medici.service.experiment.ExperimentService;
|
||||
import com.navi.medici.service.metric.MetricService;
|
||||
import com.navi.medici.service.segment.SegmentService;
|
||||
import com.navi.medici.service.team.TeamService;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -27,6 +29,12 @@ class DropdownControllerTest {
|
||||
|
||||
@Mock
|
||||
private TeamService teamService;
|
||||
|
||||
@Mock
|
||||
private SegmentService segmentService;
|
||||
|
||||
@Mock
|
||||
private MetricService metricService;
|
||||
private MockMvc mockMvc;
|
||||
|
||||
@BeforeEach
|
||||
@@ -47,4 +55,18 @@ class DropdownControllerTest {
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetMetricsDropdown() throws Exception {
|
||||
MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/v1/dropdown/metrics");
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetSegmentsDropdown() throws Exception {
|
||||
MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/v1/dropdown/segments");
|
||||
mockMvc.perform(request)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,13 @@ package com.navi.medici.controller.v2;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.navi.medici.Constants;
|
||||
import com.navi.medici.TestUtils;
|
||||
import com.navi.medici.enums.ExperimentStatus;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.request.v1.CreateExperimentRequest;
|
||||
import com.navi.medici.request.v1.ExperimentSearchRequest;
|
||||
import com.navi.medici.response.ExperimentResponse;
|
||||
import com.navi.medici.request.v1.SampleSizeRequest;
|
||||
import com.navi.medici.response.DashboardExperimentResponse;
|
||||
import com.navi.medici.service.experiment.ExperimentService;
|
||||
import com.navi.medici.util.JacksonUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
@@ -20,8 +23,11 @@ import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||
@@ -76,7 +82,7 @@ public class ExperimentControllerV2Test {
|
||||
.minimumDetectableEffect(0.3)
|
||||
.confidenceInterval(95)
|
||||
.build();
|
||||
ExperimentResponse response = ExperimentResponse.builder()
|
||||
DashboardExperimentResponse response = DashboardExperimentResponse.builder()
|
||||
.experimentName("test-experiment")
|
||||
.primaryMetric("primary-metric")
|
||||
.build();
|
||||
@@ -91,4 +97,81 @@ public class ExperimentControllerV2Test {
|
||||
mockMvc.perform(httpRequest)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetSampleSize() throws Exception {
|
||||
SampleSizeRequest request = TestUtils.getSampleSizeRequest();
|
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap();
|
||||
map.put("baselineConversion", Collections.singletonList("9.0"));
|
||||
map.put("confidenceInterval", Collections.singletonList("95.0"));
|
||||
map.put("minimumDetectableEffect", Collections.singletonList("0.3"));
|
||||
MockHttpServletRequestBuilder httpRequest = MockMvcRequestBuilders.get("/v2/experiments/sample-size")
|
||||
.params(map);
|
||||
|
||||
mockMvc.perform(httpRequest)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFetchExperiment() throws Exception {
|
||||
String name = TestUtils.getExperimentEntity().getName();
|
||||
MockHttpServletRequestBuilder httpRequest = MockMvcRequestBuilders.get("/v2/experiments/fetch")
|
||||
.param("name", name);
|
||||
|
||||
mockMvc.perform(httpRequest)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAttachMetric() throws Exception {
|
||||
AttachMetricToExperimentRequest request = TestUtils.getAttachMetricToExperimentRequest();
|
||||
MockHttpServletRequestBuilder httpRequest = MockMvcRequestBuilders.put("/v2/experiments/attach/metric/experimentId")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(jacksonUtils.objectToString(request));
|
||||
|
||||
mockMvc.perform(httpRequest)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReleaseExperiment() throws Exception {
|
||||
String experimentId = TestUtils.getExperimentEntity().getExperimentId();
|
||||
String emailId = TestUtils.TEST_USER;
|
||||
MockHttpServletRequestBuilder httpRequest = MockMvcRequestBuilders.put("/v2/experiments/release/" + experimentId)
|
||||
.header(Constants.HEADER_EMAIL_ID, TestUtils.TEST_USER);
|
||||
|
||||
mockMvc.perform(httpRequest)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPauseExperiment() throws Exception {
|
||||
String experimentId = TestUtils.getExperimentEntity().getExperimentId();
|
||||
String emailId = TestUtils.TEST_USER;
|
||||
MockHttpServletRequestBuilder httpRequest = MockMvcRequestBuilders.put("/v2/experiments/pause/" + experimentId)
|
||||
.header(Constants.HEADER_EMAIL_ID, TestUtils.TEST_USER);
|
||||
|
||||
mockMvc.perform(httpRequest)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRollbackExperiment() throws Exception {
|
||||
String experimentId = TestUtils.getExperimentEntity().getExperimentId();
|
||||
String emailId = TestUtils.TEST_USER;
|
||||
MockHttpServletRequestBuilder httpRequest = MockMvcRequestBuilders.put("/v2/experiments/rollback/" + experimentId)
|
||||
.header(Constants.HEADER_EMAIL_ID, TestUtils.TEST_USER);
|
||||
|
||||
mockMvc.perform(httpRequest)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetExperimentAuditTrail() throws Exception {
|
||||
String experimentId = TestUtils.getExperimentEntity().getExperimentId();
|
||||
MockHttpServletRequestBuilder httpRequest = MockMvcRequestBuilders.get("/v2/experiments/audit-trail/" + experimentId);
|
||||
|
||||
mockMvc.perform(httpRequest)
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,21 @@
|
||||
package com.navi.medici.mapper;
|
||||
|
||||
import com.navi.medici.TestUtils;
|
||||
import com.navi.medici.dto.ExperimentAuditTrailDTO;
|
||||
import com.navi.medici.dto.ExperimentImpact;
|
||||
import com.navi.medici.dto.ExperimentInfoDTO;
|
||||
import com.navi.medici.entity.ExperimentAuditTrailEntity;
|
||||
import com.navi.medici.entity.ExperimentEntity;
|
||||
import com.navi.medici.entity.ExperimentInfoEntity;
|
||||
import com.navi.medici.enums.ExperimentStatus;
|
||||
import com.navi.medici.response.DashboardExperimentResponse;
|
||||
import com.navi.medici.response.ExperimentResponse;
|
||||
import com.navi.medici.util.JacksonUtils;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Set;
|
||||
@@ -17,23 +25,47 @@ class ExperimentMapperTest {
|
||||
@InjectMocks
|
||||
private ExperimentMapper experimentMapper;
|
||||
|
||||
@Mock
|
||||
private JacksonUtils jacksonUtils;
|
||||
|
||||
@Test
|
||||
void shouldMapExperimentEntityToExperimentResponse() {
|
||||
void shouldMapExperimentEntityToDashboardExperimentResponse() {
|
||||
ExperimentEntity experimentEntity = ExperimentEntity.builder()
|
||||
.name("experiment-name")
|
||||
.experimentId("uuid")
|
||||
.createdBy("sample-user")
|
||||
.experimentMetricMappingEntities(Set.of(TestUtils.getExperimentMetricMappingEntity()))
|
||||
.experimentMetricMappings(Set.of(TestUtils.getExperimentMetricMappingEntity()))
|
||||
.build();
|
||||
ExperimentResponse experimentResponse = experimentMapper.mapExperimentEntityToExperimentResponse(experimentEntity);
|
||||
Assertions.assertEquals("test metric", experimentResponse.getPrimaryMetric());
|
||||
DashboardExperimentResponse dashboardExperimentResponse = experimentMapper.mapExperimentEntityToDashBoardExperimentResponse(experimentEntity);
|
||||
Assertions.assertEquals("test metric", dashboardExperimentResponse.getPrimaryMetric());
|
||||
Assertions.assertEquals(ExperimentImpact.builder()
|
||||
.control(0.0)
|
||||
.treatment(0.0)
|
||||
.flag(null)
|
||||
.build().toString(), experimentResponse.getImpact().toString());
|
||||
Assertions.assertEquals("uuid", experimentResponse.getExperimentId());
|
||||
Assertions.assertEquals("experiment-name", experimentResponse.getExperimentName());
|
||||
Assertions.assertEquals(0, experimentResponse.getTestUsers());
|
||||
.build().toString(), dashboardExperimentResponse.getImpact().toString());
|
||||
Assertions.assertEquals("uuid", dashboardExperimentResponse.getExperimentId());
|
||||
Assertions.assertEquals("experiment-name", dashboardExperimentResponse.getExperimentName());
|
||||
Assertions.assertEquals(0, dashboardExperimentResponse.getTestUsers());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMapExperimentEntityToExperimentResponse() {
|
||||
ExperimentEntity experiment = TestUtils.getExperimentEntity();
|
||||
ExperimentResponse experimentResponse = experimentMapper.mapExperimentEntityToExperimentResponse(experiment);
|
||||
Assertions.assertEquals("test experiment", experimentResponse.getExperimentName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMapExperimentInfoEntityToExperimentInfoDto() {
|
||||
ExperimentInfoEntity experimentInfo = TestUtils.getExperimentInfoEntity();
|
||||
ExperimentInfoDTO experimentInfoDto = experimentMapper.mapExperimentInfoEntityToExperimentInfoDTO(experimentInfo);
|
||||
Assertions.assertEquals(ExperimentStatus.CREATED, experimentInfoDto.getExperimentStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMapExperimentAuditTrailEntityToExperimentAuditTrailDto() {
|
||||
ExperimentAuditTrailEntity experimentAuditTrail = TestUtils.getExperimentAuditTrailEntity();
|
||||
ExperimentAuditTrailDTO experimentAuditTrailDto = experimentMapper.mapExperimentAuditTrailEntityToExperimentAuditTrailDTO(experimentAuditTrail);
|
||||
Assertions.assertEquals("test log", experimentAuditTrailDto.getLog());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.navi.medici.mapper;
|
||||
|
||||
import com.navi.medici.TestUtils;
|
||||
import com.navi.medici.entity.SegmentEntity;
|
||||
import com.navi.medici.response.SegmentResponse;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class SegmentMapperTest {
|
||||
|
||||
@InjectMocks
|
||||
private SegmentMapper segmentMapper;
|
||||
|
||||
@Test
|
||||
void shouldMapSegmentEntityToSegmentResponse() {
|
||||
SegmentEntity segment = TestUtils.getSegmentEntity();
|
||||
SegmentResponse response = segmentMapper.mapSegmentEntityToSegmentResponse(segment, 2);
|
||||
assertEquals("test-uuid-segment", response.getSegmentId());
|
||||
}
|
||||
}
|
||||
@@ -4,25 +4,31 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.navi.medici.TestUtils;
|
||||
import com.navi.medici.config.LitmusCoreConfig;
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.dto.ExperimentAuditTrailDTO;
|
||||
import com.navi.medici.entity.ExperimentAuditTrailEntity;
|
||||
import com.navi.medici.entity.ExperimentEntity;
|
||||
import com.navi.medici.entity.MetricEntity;
|
||||
import com.navi.medici.entity.TeamEntity;
|
||||
import com.navi.medici.enums.ExperimentStatus;
|
||||
import com.navi.medici.enums.ExperimentType;
|
||||
import com.navi.medici.exceptions.BadRequestException;
|
||||
import com.navi.medici.exceptions.ExperimentAlreadyExistException;
|
||||
import com.navi.medici.exceptions.LitmusExperimentNotFoundException;
|
||||
import com.navi.medici.exceptions.NoExperimentsFoundForVerticalException;
|
||||
import com.navi.medici.mapper.ExperimentMapper;
|
||||
import com.navi.medici.query.experiment.IExperimentQuery;
|
||||
import com.navi.medici.query.experimentaudittrail.IExperimentAuditTrailQuery;
|
||||
import com.navi.medici.query.experimentinfo.IExperimentInfoQuery;
|
||||
import com.navi.medici.query.experimentmetricmapping.IExperimentMetricMappingQuery;
|
||||
import com.navi.medici.query.metric.IMetricQuery;
|
||||
import com.navi.medici.query.team.ITeamQuery;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.request.v1.CreateExperimentRequest;
|
||||
import com.navi.medici.request.v1.ExperimentSearchRequest;
|
||||
import com.navi.medici.request.v1.LitmusExperiment;
|
||||
import com.navi.medici.request.v1.LitmusExperimentStrategyUpdate;
|
||||
import com.navi.medici.request.v1.SampleSizeRequest;
|
||||
import com.navi.medici.request.v1.VerticalUpdateRequest;
|
||||
import com.navi.medici.response.DashboardExperimentResponse;
|
||||
import com.navi.medici.response.ExperimentResponse;
|
||||
import com.navi.medici.response.LitmusExperimentCollection;
|
||||
import com.navi.medici.response.PaginatedSearchResponse;
|
||||
@@ -48,6 +54,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
|
||||
@@ -82,22 +89,25 @@ class ExperimentServiceImplTest {
|
||||
|
||||
@Mock
|
||||
private JacksonUtils jacksonUtils;
|
||||
@Mock
|
||||
private IExperimentAuditTrailQuery experimentAuditTrailQuery;
|
||||
|
||||
|
||||
@Test
|
||||
void shouldCreateExperiment() {
|
||||
CreateExperimentRequest request = TestUtils.getCreateExperimentRequest();
|
||||
String emailId = TestUtils.TEST_USER;
|
||||
ExperimentResponse response = TestUtils.getExperimentResponse();
|
||||
DashboardExperimentResponse response = TestUtils.getDashboardExperimentResponse();
|
||||
MetricEntity metricEntity = TestUtils.getMetricEntity();
|
||||
TeamEntity team = TestUtils.getTeamEntity();
|
||||
Mockito.when(metricQuery.findByMetricName(any())).thenReturn(Optional.ofNullable(metricEntity));
|
||||
Mockito.when(experimentMapper.mapExperimentEntityToExperimentResponse(any())).thenReturn(response);
|
||||
Mockito.when(experimentMapper.mapExperimentEntityToDashBoardExperimentResponse(any())).thenReturn(response);
|
||||
Mockito.when(jacksonUtils.objectToString(any())).thenReturn(" ");
|
||||
Mockito.when(teamQuery.findByTeamName(any())).thenReturn(Optional.ofNullable(team));
|
||||
ExperimentResponse experimentResponse = experimentService.createExperiment(request, emailId);
|
||||
assertEquals("test experiment", experimentResponse.getExperimentName());
|
||||
assertEquals("test metric", experimentResponse.getPrimaryMetric());
|
||||
Mockito.when(experimentQuery.findByExperimentId(any())).thenReturn(Optional.ofNullable(TestUtils.getExperimentEntity()));
|
||||
DashboardExperimentResponse dashboardExperimentResponse = experimentService.createExperiment(request, emailId);
|
||||
assertEquals("test experiment", dashboardExperimentResponse.getExperimentName());
|
||||
assertEquals("test metric", dashboardExperimentResponse.getPrimaryMetric());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -125,10 +135,10 @@ class ExperimentServiceImplTest {
|
||||
ExperimentSearchRequest request = TestUtils.getExperimentSearchRequest();
|
||||
Pageable paging = PageRequest.of(request.getPage() - 1, request.getSize());
|
||||
Page<ExperimentEntity> experimentEntities = new PageImpl<>(List.of(TestUtils.getExperimentEntity()), paging, 1);
|
||||
ExperimentResponse experimentResponse = TestUtils.getExperimentResponse();
|
||||
DashboardExperimentResponse dashboardExperimentResponse = TestUtils.getDashboardExperimentResponse();
|
||||
Mockito.when(experimentQuery.findAll(any(), any())).thenReturn(experimentEntities);
|
||||
Mockito.when(experimentMapper.mapExperimentEntityToExperimentResponse(any())).thenReturn(experimentResponse);
|
||||
PaginatedSearchResponse<ExperimentResponse> response = experimentService.getExperiments(request);
|
||||
Mockito.when(experimentMapper.mapExperimentEntityToDashBoardExperimentResponse(any())).thenReturn(dashboardExperimentResponse);
|
||||
PaginatedSearchResponse<DashboardExperimentResponse> response = experimentService.getExperiments(request);
|
||||
assertEquals(1, response.getTotalSize());
|
||||
assertEquals("test experiment", response.getData().get(0).getExperimentName());
|
||||
}
|
||||
@@ -156,7 +166,7 @@ class ExperimentServiceImplTest {
|
||||
Optional<ExperimentEntity> experiment = Optional.ofNullable(TestUtils.getExperimentEntity());
|
||||
List<VariantDefinition> variants = TestUtils.getVariants(2);
|
||||
Mockito.when(experimentQuery.findByExperimentId(any(String.class))).thenReturn(experiment);
|
||||
assertDoesNotThrow(() -> experimentService.attachVariants(experiment.get().getExperimentId(), variants));
|
||||
assertDoesNotThrow(() -> experimentService.attachVariants(experiment.get().getExperimentId(), variants, TestUtils.TEST_USER));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -164,7 +174,7 @@ class ExperimentServiceImplTest {
|
||||
Optional<ExperimentEntity> experiment = Optional.ofNullable(TestUtils.getExperimentEntity());
|
||||
List<VariantDefinition> variants = TestUtils.getVariants(2);
|
||||
Mockito.when(experimentQuery.findByExperimentId(any(String.class))).thenReturn(Optional.empty());
|
||||
assertThrows(BadRequestException.class, () -> experimentService.attachVariants(experiment.get().getExperimentId(), variants));
|
||||
assertThrows(BadRequestException.class, () -> experimentService.attachVariants(experiment.get().getExperimentId(), variants, TestUtils.TEST_USER));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -181,14 +191,14 @@ class ExperimentServiceImplTest {
|
||||
LitmusExperimentStrategyUpdate litmusExperimentStrategyUpdate = TestUtils.getLitmusExperimentStrategyUpdate();
|
||||
Optional<ExperimentEntity> experiment = Optional.ofNullable(TestUtils.getExperimentEntity());
|
||||
Mockito.when(experimentQuery.findByExperimentId(any(String.class))).thenReturn(experiment);
|
||||
assertDoesNotThrow(() -> experimentService.updateStrategies(litmusExperimentStrategyUpdate));
|
||||
assertDoesNotThrow(() -> experimentService.updateStrategies(litmusExperimentStrategyUpdate, TestUtils.TEST_USER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldThrowWhenUpdateStrategies() {
|
||||
LitmusExperimentStrategyUpdate litmusExperimentStrategyUpdate = TestUtils.getLitmusExperimentStrategyUpdate();
|
||||
Mockito.when(experimentQuery.findByExperimentId(any(String.class))).thenReturn(Optional.empty());
|
||||
assertThrows(LitmusExperimentNotFoundException.class, () -> experimentService.updateStrategies(litmusExperimentStrategyUpdate));
|
||||
assertThrows(LitmusExperimentNotFoundException.class, () -> experimentService.updateStrategies(litmusExperimentStrategyUpdate, TestUtils.TEST_USER));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -221,7 +231,7 @@ class ExperimentServiceImplTest {
|
||||
void shouldThrowWhenFetchAllExperimentsForVertical() {
|
||||
String vertical = TestUtils.getTeamEntity().getTeamName();
|
||||
Mockito.when(experimentQuery.findByVertical(any(String.class))).thenReturn(List.of());
|
||||
assertThrows(NoExperimentsFoundForVerticalException.class, () -> experimentService.fetchAllExperimentsForVertical(vertical));
|
||||
assertThrows(LitmusExperimentNotFoundException.class, () -> experimentService.fetchAllExperimentsForVertical(vertical));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -232,4 +242,67 @@ class ExperimentServiceImplTest {
|
||||
assertEquals("test user", ownerDropdown.get(1).getLabel());
|
||||
assertEquals("test user", ownerDropdown.get(1).getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetSampleSizeForExperiment() {
|
||||
SampleSizeRequest request = TestUtils.getSampleSizeRequest();
|
||||
assertEquals(143491, experimentService.getSampleSizeForExperiment(request));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetExperimentAuditTrail() {
|
||||
ExperimentEntity experiment = TestUtils.getExperimentEntity();
|
||||
Mockito.when(experimentQuery.findByExperimentId(anyString())).thenReturn(Optional.ofNullable(experiment));
|
||||
Mockito.when(experimentMapper.mapExperimentAuditTrailEntityToExperimentAuditTrailDTO(any(ExperimentAuditTrailEntity.class)))
|
||||
.thenReturn(TestUtils.getExperimentAuditTrailDto());
|
||||
List<ExperimentAuditTrailDTO> experimentAuditTrailDTOs = experimentService.getExperimentAuditTrail(experiment.getExperimentId());
|
||||
assertEquals("test log", experimentAuditTrailDTOs.get(0).getLog());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldFetchExperiment() {
|
||||
Optional<ExperimentEntity> experiment = Optional.ofNullable(TestUtils.getExperimentEntity());
|
||||
Mockito.when(experimentQuery.findByName(anyString())).thenReturn(experiment);
|
||||
Mockito.when(experimentMapper.mapExperimentEntityToExperimentResponse(experiment.get())).thenReturn(TestUtils.getExperimentResponse());
|
||||
ExperimentResponse response = experimentService.fetchExperiment(experiment.get().getName());
|
||||
assertEquals("test experiment", response.getExperimentName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAttachMetric() {
|
||||
ExperimentEntity experiment = TestUtils.getExperimentEntity();
|
||||
AttachMetricToExperimentRequest request = TestUtils.getAttachMetricToExperimentRequest();
|
||||
String emailId = TestUtils.TEST_USER;
|
||||
MetricEntity metric = TestUtils.getMetricEntity();
|
||||
Mockito.when(metricQuery.findByMetricName(metric.getMetricName())).thenReturn(Optional.of(metric));
|
||||
Mockito.when(experimentQuery.findByExperimentId(experiment.getExperimentId())).thenReturn(Optional.of(experiment));
|
||||
assertDoesNotThrow(() -> experimentService.attachMetric(experiment.getExperimentId(), request, emailId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReleaseExperiment() {
|
||||
ExperimentEntity experiment = TestUtils.getExperimentEntity();
|
||||
experiment.getExperimentInfo().setExperimentStatus(ExperimentStatus.RUNNING);
|
||||
Mockito.when(experimentQuery.findByExperimentId(experiment.getExperimentId()))
|
||||
.thenReturn(Optional.of(experiment));
|
||||
assertDoesNotThrow(() -> experimentService.releaseExperiment(experiment.getExperimentId(), TestUtils.TEST_USER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldPauseExperiment() {
|
||||
ExperimentEntity experiment = TestUtils.getExperimentEntity();
|
||||
experiment.getExperimentInfo().setExperimentStatus(ExperimentStatus.RUNNING);
|
||||
Mockito.when(experimentQuery.findByExperimentId(experiment.getExperimentId()))
|
||||
.thenReturn(Optional.of(experiment));
|
||||
assertDoesNotThrow(() -> experimentService.pauseExperiment(experiment.getExperimentId(), TestUtils.TEST_USER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRollBackExperiment() {
|
||||
ExperimentEntity experiment = TestUtils.getExperimentEntity();
|
||||
experiment.getExperimentInfo().setExperimentStatus(ExperimentStatus.RUNNING);
|
||||
Mockito.when(experimentQuery.findByExperimentId(experiment.getExperimentId()))
|
||||
.thenReturn(Optional.of(experiment));
|
||||
assertDoesNotThrow(() -> experimentService.rollbackExperiment(experiment.getExperimentId(), TestUtils.TEST_USER));
|
||||
}
|
||||
}
|
||||
@@ -72,4 +72,11 @@ class MetricServiceImplTest {
|
||||
PaginatedSearchResponse<MetricResponse> response = metricService.getMetrics(request);
|
||||
assertEquals("test metric", response.getData().get(0).getMetricName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetMetricsDropdown() {
|
||||
List<String> metricNames = List.of(TestUtils.getMetricEntity().getMetricName());
|
||||
Mockito.when(metricQuery.getAllMetricNames()).thenReturn(metricNames);
|
||||
assertEquals("test metric", metricService.getMetricsDropdown().get(0).getLabel());
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.navi.medici.service.segment;
|
||||
|
||||
import com.navi.medici.TestUtils;
|
||||
import com.navi.medici.command.CacheCommands;
|
||||
import com.navi.medici.dto.Dropdown;
|
||||
import com.navi.medici.entity.SegmentEntity;
|
||||
import com.navi.medici.entity.TeamEntity;
|
||||
import com.navi.medici.exceptions.SegmentAlreadyExistException;
|
||||
@@ -180,4 +181,12 @@ class SegmentServiceImplTest {
|
||||
assertEquals(1, response.getData().size());
|
||||
assertEquals("test segment", response.getData().get(0).getSegmentName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldGetSegmentsDropdown() {
|
||||
List<String> segmentNames = List.of(TestUtils.getSegmentEntity().getSegmentName());
|
||||
Mockito.when(segmentQuery.getAllSegmentNames()).thenReturn(segmentNames);
|
||||
List<Dropdown> dropdowns = segmentService.getSegmentsDropdown();
|
||||
assertEquals("test segment", dropdowns.get(0).getLabel());
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package com.navi.medici.dto;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class VariantStat {
|
||||
String variantName;
|
||||
double variantMetricValue;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.navi.medici.entity;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.JoinColumn;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
|
||||
@Entity
|
||||
@Table(name = "experiment_audit_trail")
|
||||
@Getter
|
||||
@Setter
|
||||
@SuperBuilder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class ExperimentAuditTrailEntity extends BaseEntity {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "experiment_id")
|
||||
ExperimentEntity experiment;
|
||||
|
||||
String log;
|
||||
String createdBy;
|
||||
}
|
||||
@@ -8,11 +8,14 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.hibernate.annotations.Cascade;
|
||||
import org.hibernate.annotations.CascadeType;
|
||||
|
||||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
import javax.persistence.Enumerated;
|
||||
import javax.persistence.FetchType;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.OneToOne;
|
||||
import javax.persistence.Table;
|
||||
@@ -68,12 +71,19 @@ public class ExperimentEntity extends BaseEntity implements Serializable {
|
||||
|
||||
String updatedBy;
|
||||
|
||||
@OneToMany(mappedBy = "experiment")
|
||||
Set<ExperimentMetricMappingEntity> experimentMetricMappingEntities;
|
||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "experiment")
|
||||
@Cascade(CascadeType.ALL)
|
||||
Set<ExperimentMetricMappingEntity> experimentMetricMappings;
|
||||
|
||||
@OneToOne(mappedBy = "experiment")
|
||||
ExperimentInfoEntity experimentInfoEntity;
|
||||
@Cascade(CascadeType.ALL)
|
||||
ExperimentInfoEntity experimentInfo;
|
||||
|
||||
@OneToMany(mappedBy = "experiment")
|
||||
Set<ExperimentMetricResultEntity> experimentMetricResultEntities;
|
||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "experiment")
|
||||
@Cascade(CascadeType.ALL)
|
||||
Set<ExperimentMetricResultEntity> experimentMetricResults;
|
||||
|
||||
@OneToMany(fetch = FetchType.LAZY, mappedBy = "experiment")
|
||||
@Cascade(CascadeType.ALL)
|
||||
Set<ExperimentAuditTrailEntity> experimentAuditTrails;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.hibernate.annotations.Cascade;
|
||||
import org.hibernate.annotations.CascadeType;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.EnumType;
|
||||
@@ -28,10 +30,12 @@ public class ExperimentMetricMappingEntity extends BaseEntity {
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "experiment_id")
|
||||
@Cascade(CascadeType.ALL)
|
||||
ExperimentEntity experiment;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "metric_id")
|
||||
@Cascade(CascadeType.ALL)
|
||||
MetricEntity metric;
|
||||
|
||||
@Enumerated(EnumType.STRING)
|
||||
|
||||
@@ -9,6 +9,8 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.hibernate.annotations.Cascade;
|
||||
import org.hibernate.annotations.CascadeType;
|
||||
import org.hibernate.annotations.Type;
|
||||
import org.hibernate.annotations.TypeDef;
|
||||
|
||||
@@ -30,10 +32,12 @@ import java.util.List;
|
||||
public class ExperimentMetricResultEntity extends BaseEntity {
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "experiment_id")
|
||||
@Cascade(CascadeType.ALL)
|
||||
ExperimentEntity experiment;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "metric_id")
|
||||
@Cascade(CascadeType.ALL)
|
||||
MetricEntity metric;
|
||||
|
||||
@Type(type = "jsonb")
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.navi.medici.query.experimentaudittrail;
|
||||
|
||||
import com.navi.medici.entity.ExperimentAuditTrailEntity;
|
||||
import com.navi.medici.repository.ExperimentAuditTrailRepository;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ExperimentAuditTrailQueryImpl implements IExperimentAuditTrailQuery {
|
||||
|
||||
private final ExperimentAuditTrailRepository experimentAuditTrailRepository;
|
||||
|
||||
@Override
|
||||
public ExperimentAuditTrailEntity save(ExperimentAuditTrailEntity experimentAuditTrail) {
|
||||
return experimentAuditTrailRepository.save(experimentAuditTrail);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.navi.medici.query.experimentaudittrail;
|
||||
|
||||
import com.navi.medici.entity.ExperimentAuditTrailEntity;
|
||||
|
||||
public interface IExperimentAuditTrailQuery {
|
||||
ExperimentAuditTrailEntity save(ExperimentAuditTrailEntity experimentAuditTrail);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface IMetricQuery {
|
||||
@@ -13,4 +14,6 @@ public interface IMetricQuery {
|
||||
Page<MetricEntity> findAll(Specification<MetricEntity> specification, Pageable pageable);
|
||||
|
||||
MetricEntity save(MetricEntity metric);
|
||||
|
||||
List<String> getAllMetricNames();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
@@ -29,4 +30,9 @@ public class MetricQueryImpl implements IMetricQuery {
|
||||
public MetricEntity save(MetricEntity metric) {
|
||||
return metricRepository.save(metric);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllMetricNames() {
|
||||
return metricRepository.getAllMetricNames();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface ISegmentQuery {
|
||||
@@ -15,4 +16,6 @@ public interface ISegmentQuery {
|
||||
Optional<SegmentEntity> findBySegmentName(String segmentName);
|
||||
|
||||
Page<SegmentEntity> findAll(Specification<SegmentEntity> specification, Pageable pageable);
|
||||
|
||||
List<String> getAllSegmentNames();
|
||||
}
|
||||
|
||||
@@ -2,18 +2,19 @@ package com.navi.medici.query.segment;
|
||||
|
||||
import com.navi.medici.entity.SegmentEntity;
|
||||
import com.navi.medici.repository.SegmentRepository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class SegmentQueryImpl implements ISegmentQuery{
|
||||
public class
|
||||
SegmentQueryImpl implements ISegmentQuery {
|
||||
private final SegmentRepository segmentRepository;
|
||||
|
||||
@Override
|
||||
@@ -32,7 +33,12 @@ public class SegmentQueryImpl implements ISegmentQuery{
|
||||
}
|
||||
|
||||
@Override
|
||||
public Page<SegmentEntity> findAll(Specification<SegmentEntity> specification, Pageable pageable){
|
||||
public Page<SegmentEntity> findAll(Specification<SegmentEntity> specification, Pageable pageable) {
|
||||
return segmentRepository.findAll(specification, pageable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getAllSegmentNames() {
|
||||
return segmentRepository.getAllSegmentNames();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.navi.medici.repository;
|
||||
|
||||
import com.navi.medici.entity.ExperimentAuditTrailEntity;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface ExperimentAuditTrailRepository extends CrudRepository<ExperimentAuditTrailEntity, Long> {
|
||||
}
|
||||
@@ -4,13 +4,19 @@ import com.navi.medici.entity.MetricEntity;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface MetricRepository extends CrudRepository<MetricEntity, Long> {
|
||||
Optional<MetricEntity> findByMetricName(String name);
|
||||
|
||||
Page<MetricEntity> findAll(Specification<MetricEntity> specification, Pageable pageable);
|
||||
|
||||
@Query(value = "select m.metric_name from metrics m", nativeQuery = true)
|
||||
List<String> getAllMetricNames();
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package com.navi.medici.repository;
|
||||
|
||||
import com.navi.medici.entity.SegmentEntity;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.domain.Specification;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Repository
|
||||
public interface SegmentRepository extends CrudRepository<SegmentEntity, Long> {
|
||||
Optional<SegmentEntity> findBySegmentId(String segmentId);
|
||||
@@ -18,4 +18,7 @@ public interface SegmentRepository extends CrudRepository<SegmentEntity, Long> {
|
||||
Optional<SegmentEntity> findBySegmentName(String segmentName);
|
||||
|
||||
Page<SegmentEntity> findAll(Specification<SegmentEntity> specification, Pageable pageable);
|
||||
|
||||
@Query(value = "select s.name from segments s", nativeQuery = true)
|
||||
List<String> getAllSegmentNames();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
--changeset author:akshatsoni id:202303311332
|
||||
CREATE TABLE experiment_audit_trail
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
experiment_id int references experiments (id),
|
||||
log text,
|
||||
created_by varchar(100),
|
||||
created_at timestamp,
|
||||
updated_at timestamp,
|
||||
version int
|
||||
)
|
||||
@@ -65,6 +65,10 @@
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.persistence</groupId>
|
||||
<artifactId>javax.persistence-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.navi.medici.dto;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class ExperimentAuditTrailDTO {
|
||||
String log;
|
||||
String createdBy;
|
||||
LocalDateTime createdAt;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.navi.medici.dto;
|
||||
|
||||
import com.navi.medici.enums.ExperimentStatus;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class ExperimentInfoDTO {
|
||||
ExperimentStatus experimentStatus;
|
||||
long testUsers;
|
||||
ExperimentMetadata experimentMetadata;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.navi.medici.request.v1;
|
||||
|
||||
import com.navi.medici.enums.ExperimentMetricType;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class AttachMetricToExperimentRequest {
|
||||
String metricName;
|
||||
ExperimentMetricType experimentMetricType;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.navi.medici.request.v1;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class SampleSizeRequest {
|
||||
double baselineConversion;
|
||||
double minimumDetectableEffect;
|
||||
double confidenceInterval;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.navi.medici.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.navi.medici.dto.ExperimentImpact;
|
||||
import com.navi.medici.enums.ExperimentStatus;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
//TODO: make a generic experiment response
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class DashboardExperimentResponse {
|
||||
String experimentId;
|
||||
String experimentName;
|
||||
String createdBy;
|
||||
String primaryMetric;
|
||||
ExperimentImpact impact;
|
||||
long testUsers;
|
||||
ExperimentStatus experimentStatus;
|
||||
}
|
||||
@@ -1,29 +1,36 @@
|
||||
package com.navi.medici.response;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.navi.medici.dto.ExperimentImpact;
|
||||
import com.navi.medici.enums.ExperimentStatus;
|
||||
import com.navi.medici.dto.ExperimentInfoDTO;
|
||||
import com.navi.medici.enums.ExperimentType;
|
||||
import com.navi.medici.request.v1.AttachMetricToExperimentRequest;
|
||||
import com.navi.medici.strategy.ActivationStrategy;
|
||||
import com.navi.medici.variants.VariantDefinition;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldDefaults;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@Builder
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
@FieldDefaults(level = AccessLevel.PRIVATE)
|
||||
public class ExperimentResponse {
|
||||
String experimentId;
|
||||
String experimentName;
|
||||
String description;
|
||||
List<ActivationStrategy> strategies;
|
||||
List<VariantDefinition> variants;
|
||||
ExperimentType type;
|
||||
ExperimentInfoDTO experimentInfo;
|
||||
String vertical;
|
||||
List<AttachMetricToExperimentRequest> metrics;
|
||||
LocalDateTime createdAt;
|
||||
LocalDateTime updatedAt;
|
||||
String createdBy;
|
||||
String primaryMetric;
|
||||
ExperimentImpact impact;
|
||||
long testUsers;
|
||||
ExperimentStatus experimentStatus;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,12 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-math3</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.navi.medici.exceptions;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
@Log4j2
|
||||
public class UnableToChangeExperimentStatusException extends RuntimeException {
|
||||
public UnableToChangeExperimentStatusException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.navi.medici.exceptions;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
|
||||
@Log4j2
|
||||
public class VariantWeightSumNotHundredException extends RuntimeException {
|
||||
public VariantWeightSumNotHundredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.navi.medici.stats;
|
||||
|
||||
import org.apache.commons.math3.distribution.NormalDistribution;
|
||||
|
||||
public class SampleSizeCalculator {
|
||||
|
||||
private static NormalDistribution normalDistribution;
|
||||
|
||||
public static long sampleCalculator(double baselineConversion, double minimumDetectableEffect, double alpha, double power) {
|
||||
normalDistribution = new NormalDistribution(0, 1);
|
||||
double zPower = normalDistribution.inverseCumulativeProbability(power);
|
||||
double zAlpha = normalDistribution.inverseCumulativeProbability(1.0 - alpha / 2);
|
||||
double bcr = baselineConversion / 100;
|
||||
double mde = minimumDetectableEffect / 100;
|
||||
double dcr = bcr + mde;
|
||||
double bcrConfidence = -zAlpha * Math.sqrt(2 * bcr * (1.0 - bcr));
|
||||
double dcrConfidence = -zPower * Math.sqrt((bcr * (1.0 - bcr)) + (dcr * (1.0 - dcr)));
|
||||
long numberOfSamples = (long) Math.ceil(Math.pow(bcrConfidence + dcrConfidence, 2) / Math.pow(dcr - bcr, 2));
|
||||
return numberOfSamples;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user