tags) {
+ instance.setTags(tags);
+ return this;
+ }
+
public PrioritizedExperiment build() {
PrioritizedExperiment result = instance;
instance = null;
diff --git a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ContextTest.java b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ContextTest.java
index f31ee91be..bfa59b31e 100644
--- a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ContextTest.java
+++ b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ContextTest.java
@@ -28,8 +28,6 @@
/**
* This class tests the functionality of the Context object.
- *
- * Created by asuckro on 8/12/15.
*/
public class ContextTest {
diff --git a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentBatchTest.java b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentBatchTest.java
index c0451c577..8afd10f3c 100644
--- a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentBatchTest.java
+++ b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentBatchTest.java
@@ -28,8 +28,6 @@
/**
* This class is testing the functionality of the {@link ExperimentBatch}
- *
- * Created by asuckro on 8/19/15.
*/
public class ExperimentBatchTest {
diff --git a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentIDListTest.java b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentIDListTest.java
index 0b5529eeb..876d3d095 100644
--- a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentIDListTest.java
+++ b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentIDListTest.java
@@ -26,8 +26,6 @@
/**
* This class tests the functionality of the {@link ExperimentIDList}
- *
- * Created by asuckro on 8/19/15.
*/
public class ExperimentIDListTest {
diff --git a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentListTest.java b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentListTest.java
index 7e56b7e63..c50c8356a 100644
--- a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentListTest.java
+++ b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/ExperimentListTest.java
@@ -24,8 +24,6 @@
/**
* This tests the functionality of the {@link ExperimentList}
- *
- * Created by asuckro on 8/20/15.
*/
public class ExperimentListTest {
diff --git a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/NewExperimentTest.java b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/NewExperimentTest.java
index 901cb20c1..e7faec88f 100644
--- a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/NewExperimentTest.java
+++ b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/NewExperimentTest.java
@@ -17,14 +17,17 @@
import org.junit.Test;
+import java.util.Arrays;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
- * Created on 3/10/16.
+ * Test for the {@link NewExperiment} class.
*/
public class NewExperimentTest {
@@ -32,6 +35,8 @@ public class NewExperimentTest {
public void testBuilderCreation() {
Experiment.ID id = Experiment.ID.newInstance();
Date date = new Date();
+ String[] tagsArray = {"ui", "mobile", "12&"};
+ Set tags = new HashSet<>(Arrays.asList(tagsArray));
NewExperiment.Builder builder = NewExperiment.withID(id);
NewExperiment newExperiment = builder.withDescription("desc")
.withIsPersonalizationEnabled(true)
@@ -46,6 +51,7 @@ public void testBuilderCreation() {
.withIsRapidExperiment(true)
.withUserCap(1000)
.withCreatorID("c1")
+ .withTags(tags)
.build();
assertThat(newExperiment.getApplicationName(), is(Application.Name.valueOf("app")));
@@ -57,6 +63,10 @@ public void testBuilderCreation() {
assertThat(newExperiment.getID(), is(id));
assertThat(newExperiment.getDescription(), is("desc"));
assertThat(newExperiment.getLabel(), is(Experiment.Label.valueOf("label")));
+ assertThat(newExperiment.getTags().size(), is(3));
+ Arrays.sort(tagsArray); // the tags should be sorted
+ assertThat(newExperiment.getTags().toArray(), is(tagsArray));
+
newExperiment.setApplicationName(Application.Name.valueOf("NewApp"));
newExperiment.setCreatorID("c2");
@@ -72,7 +82,7 @@ public void testBuildBadSamplePercentage() {
.withModelName("m1")
.withModelVersion("v1")
.withRule("r1")
- .withSamplingPercent(null) //<-- this is what causes the execption
+ .withSamplingPercent(null) //<-- this is what causes the exception
.withLabel(Experiment.Label.valueOf("label"))
.withAppName(Application.Name.valueOf("app"))
.withIsRapidExperiment(true)
diff --git a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/PrioritizedExperimentTest.java b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/PrioritizedExperimentTest.java
index 93cf272e5..b20479566 100644
--- a/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/PrioritizedExperimentTest.java
+++ b/modules/experiment-objects/src/test/java/com/intuit/wasabi/experimentobjects/PrioritizedExperimentTest.java
@@ -17,18 +17,23 @@
import org.junit.Test;
+import java.util.Arrays;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
* Test class for the {@link PrioritizedExperiment}
- *
- *
- * Created by asuckro on 8/12/15.
*/
public class PrioritizedExperimentTest {
+ private String[] tagsArray = {"ui", "mobile", "12&"};
+ private Set tags = new HashSet<>(Arrays.asList(tagsArray));
private PrioritizedExperiment prioExp = PrioritizedExperiment.withID(Experiment.ID.newInstance())
.withApplicationName(Application.Name.valueOf("appName"))
@@ -39,6 +44,7 @@ public class PrioritizedExperimentTest {
.withPriority(3)
.withUserCap(42)
.withIsRapidExperiment(false)
+ .withTags(tags)
.build();
private Experiment exp = Experiment.withID(Experiment.ID.newInstance())
@@ -49,6 +55,7 @@ public class PrioritizedExperimentTest {
.withState(Experiment.State.RUNNING)
.withIsRapidExperiment(true)
.withUserCap(300)
+ .withTags(tags)
.build();
@Test
@@ -85,6 +92,7 @@ public void testBuilderFromOtherExperiment() {
assertEquals(fromExp.getLabel(), exp.getLabel());
assertEquals(fromExp.getApplicationName(), exp.getApplicationName());
assertEquals(fromExp.getIsRapidExperiment(), exp.getIsRapidExperiment());
+ assertEquals(fromExp.getTags(), exp.getTags());
}
@Test
@@ -96,14 +104,17 @@ public void testBuilderMethods() {
.withModelVersion("1")
.withCreatorID("c1")
.withStartTime(startTime)
- .withCreationTime(startTime).build();
+ .withCreationTime(startTime)
+ .withTags(tags)
+ .build();
assertEquals(startTime, exp.getCreationTime());
assertEquals(startTime, exp.getStartTime());
assertEquals("m1", exp.getModelName());
assertEquals("1", exp.getModelVersion());
assertEquals("c1", exp.getCreatorID());
-
+ Arrays.sort(tagsArray);
+ assertArrayEquals(tagsArray, exp.getTags().toArray());
}
@Test
@@ -111,5 +122,19 @@ public void testClone() throws Exception {
assertEquals(prioExp, prioExp.clone());
}
+ @Test
+ public void testIsDeleted() throws Exception {
+ assertFalse(prioExp.isDeleted());
+ PrioritizedExperiment prioExp2 = prioExp.clone();
+ prioExp2.setState(Experiment.State.DELETED);
+ assertTrue(prioExp2.isDeleted());
+ }
+
+ @Test
+ public void addNullTags() {
+ prioExp.setTags(null);
+ assertEquals(prioExp.getTags(), null);
+ }
+
}
diff --git a/modules/experiment/src/main/java/com/intuit/wasabi/experiment/Experiments.java b/modules/experiment/src/main/java/com/intuit/wasabi/experiment/Experiments.java
index a1193f5c2..12d69886c 100644
--- a/modules/experiment/src/main/java/com/intuit/wasabi/experiment/Experiments.java
+++ b/modules/experiment/src/main/java/com/intuit/wasabi/experiment/Experiments.java
@@ -22,7 +22,10 @@
import com.intuit.wasabi.experimentobjects.NewExperiment;
import com.intuit.wasabi.experimentobjects.exceptions.InvalidExperimentStateTransitionException;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Interface to perform CRUD operations on experiment. In addition, it also
@@ -155,4 +158,14 @@ boolean buildUpdatedExperiment(Experiment experiment, Experiment updates, Experi
* @return a list of Application.Name types
*/
List getApplications();
+
+ /**
+ * Returns all tags that belong to the Applications specified in the given
+ * {@link com.intuit.wasabi.experimentobjects.Application.Name}s.
+ *
+ * @param applicationNames the {@link com.intuit.wasabi.experimentobjects.Application.Name}s for which the
+ * tags should be retrieved
+ * @return a {@link Map} containing the Applications and their tags
+ */
+ Map> getTagsForApplications(Collection applicationNames);
}
diff --git a/modules/experiment/src/main/java/com/intuit/wasabi/experiment/impl/ExperimentsImpl.java b/modules/experiment/src/main/java/com/intuit/wasabi/experiment/impl/ExperimentsImpl.java
index b594af70f..a031ffea3 100644
--- a/modules/experiment/src/main/java/com/intuit/wasabi/experiment/impl/ExperimentsImpl.java
+++ b/modules/experiment/src/main/java/com/intuit/wasabi/experiment/impl/ExperimentsImpl.java
@@ -42,8 +42,12 @@
import javax.inject.Inject;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static com.intuit.wasabi.experimentobjects.Experiment.State.DELETED;
import static com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT;
@@ -105,6 +109,14 @@ public List getApplications() {
return cassandraRepository.getApplicationsList();
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Map> getTagsForApplications(Collection applicationNames) {
+ return cassandraRepository.getTagListForApplications(applicationNames != null ? applicationNames : Collections.emptySet());
+ }
+
/**
* {@inheritDoc}
*/
@@ -450,6 +462,16 @@ public boolean buildUpdatedExperiment(Experiment experiment, Experiment updates,
changeList.add(changeData);
}
+ if (updates.getTags() != null && !updates.getTags().equals(experiment.getTags())) {
+ builder.withTags(updates.getTags());
+ requiresUpdate = true;
+ Set oldTags = experiment.getTags() == null ? Collections.EMPTY_SET : experiment.getTags();
+ changeData = new ExperimentAuditInfo("tags",
+ oldTags.toString(),
+ updates.getTags().toString());
+ changeList.add(changeData);
+ }
+
/*
* Application name and label cannot be changed once the experiment is beyond the DRAFT state.
* Hence, we are not including them as a part of the audit log.
diff --git a/modules/experiment/src/test/java/com/intuit/wasabi/experiment/ExperimentsImplTest.java b/modules/experiment/src/test/java/com/intuit/wasabi/experiment/ExperimentsImplTest.java
index c12aaed3b..d3468b52b 100644
--- a/modules/experiment/src/test/java/com/intuit/wasabi/experiment/ExperimentsImplTest.java
+++ b/modules/experiment/src/test/java/com/intuit/wasabi/experiment/ExperimentsImplTest.java
@@ -41,6 +41,7 @@
import org.mockito.runners.MockitoJUnitRunner;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -831,4 +832,11 @@ public void testBuildUpdateExperimentIsRapid() {
then(changeList.size()).isEqualTo(1);
then(result).isEqualTo(true);
}
+
+ @Test
+ public void testGetTags() {
+ when(cassandraRepository.getTagListForApplications(null)).thenReturn(null);
+ expImpl.getTagsForApplications(null);
+ verify(cassandraRepository).getTagListForApplications(Collections.emptySet());
+ }
}
diff --git a/modules/functional-test/Vagrantfile b/modules/functional-test/Vagrantfile
index 0d982588f..1f7278de9 100644
--- a/modules/functional-test/Vagrantfile
+++ b/modules/functional-test/Vagrantfile
@@ -13,8 +13,8 @@ Vagrant.configure("2") do |config|
dev.vm.provider "virtualbox" do |v|
v.name = "wasabi.os.it.vbox"
- v.memory = 4096
- v.cpus = 4
+ v.memory = 8192
+ v.cpus = 8
v.customize ["modifyvm", :id, "--cableconnected1", "on"]
end
end
diff --git a/modules/functional-test/provision.sh b/modules/functional-test/provision.sh
index a54d05585..6645061aa 100755
--- a/modules/functional-test/provision.sh
+++ b/modules/functional-test/provision.sh
@@ -25,8 +25,9 @@ yum -y install httpd
sudo service httpd start
# install Oracle JDK
-wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u92-b14/jdk-8u92-linux-x64.rpm"
-sudo yum -y localinstall jdk-8u92-linux-x64.rpm
+wget --no-check-certificate -c --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u131-b11/d54c1d3a095b4ff2b6607d096fa80163/jdk-8u131-linux-x64.rpm
+
+sudo yum -y localinstall jdk-8u131-linux-x64.rpm
cat < tags;
+
/**
* The serialization strategy for comparisons and JSON serialization.
*/
@@ -172,7 +178,7 @@ public Experiment(String id) {
*/
public Experiment(String label, Application application, String startTime, String endTime,
double samplingPercent) {
- this(label, application, startTime, endTime, samplingPercent, null, null, false, "", "", false, 0, null);
+ this(label, application, startTime, endTime, samplingPercent, null, null, false, "", "", false, 0, null, null);
}
/**
@@ -191,10 +197,12 @@ public Experiment(String label, Application application, String startTime, Strin
* @param isRapidExperiment flag if rapid experimentation
* @param userCap max users for rapid experiments
* @param creatorID the creator
+ * @param tags the set of tags
*/
public Experiment(String label, Application application, String startTime, String endTime, double samplingPercent,
String description, String rule, Boolean isPersonalizationEnabled, String modelName,
- String modelVersion, Boolean isRapidExperiment, Integer userCap, String creatorID) {
+ String modelVersion, Boolean isRapidExperiment, Integer userCap, String creatorID,
+ Set tags) {
this.setLabel(label)
.setApplication(application)
.setStartTime(startTime)
@@ -207,7 +215,8 @@ public Experiment(String label, Application application, String startTime, Strin
.setIsPersonalizationEnabled(isPersonalizationEnabled)
.setIsRapidExperiment(isRapidExperiment)
.setUserCap(userCap)
- .setCreatorID(creatorID);
+ .setCreatorID(creatorID)
+ .setTags(tags);
}
/**
@@ -518,6 +527,17 @@ public Experiment setCreatorID(String creatorID) {
return this;
}
+ /**
+ * Sets the tags for an experiment
+ *
+ * @param tags the tags
+ * @return this
+ */
+ public Experiment setTags(Set tags) {
+ this.tags = tags;
+ return this;
+ }
+
@Override
public void setSerializationStrategy(SerializationStrategy serializationStrategy) {
Experiment.serializationStrategy = serializationStrategy;
diff --git a/modules/main/src/main/env/docker/wasabi/Dockerfile b/modules/main/src/main/env/docker/wasabi/Dockerfile
index 734cccbbd..ce1243b94 100644
--- a/modules/main/src/main/env/docker/wasabi/Dockerfile
+++ b/modules/main/src/main/env/docker/wasabi/Dockerfile
@@ -25,13 +25,13 @@ ENV WASABI_SRC_DIR ${application.name}
ENV WASABI_HOME /usr/local/${application.name}
ENV WASABI_JAVA_OPTIONS ""
-ENV JDK_MAJOR_VERSION 8u92
-ENV JDK_MINOR_VERSION b14
+ENV JDK_MAJOR_VERSION 8u131
+ENV JDK_MINOR_VERSION b11
ENV JDK_VERSION ${JDK_MAJOR_VERSION}-${JDK_MINOR_VERSION}
RUN yum -y update && yum install -y wget
-RUN wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/${JDK_VERSION}/jdk-${JDK_MAJOR_VERSION}-linux-x64.rpm \
+RUN wget --no-check-certificate --no-cookies --header "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/${JDK_VERSION}/d54c1d3a095b4ff2b6607d096fa80163/jdk-${JDK_MAJOR_VERSION}-linux-x64.rpm \
&& rpm -ivh jdk-${JDK_MAJOR_VERSION}-linux-x64.rpm && rm jdk-${JDK_MAJOR_VERSION}-linux-x64.rpm
COPY ./ ${WASABI_HOME}/
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/ExperimentRepository.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/ExperimentRepository.java
index 89091ddec..a0eb4a83d 100644
--- a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/ExperimentRepository.java
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/ExperimentRepository.java
@@ -31,6 +31,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* Mid-level interface for the experiments repository
@@ -265,5 +266,12 @@ void logBucketChanges(Experiment.ID experimentID, Bucket.Label bucketLabel,
*/
void createApplication(Application.Name applicationName);
+ /**
+ * Gets a list of tags associated with the given {@link Application.Name}.
+ *
+ * @param applicationNames the list of {@link Application.Name}s the tags should be retrieved for
+ * @return a Map of {@link Application.Name}s to their tags
+ */
+ Map> getTagListForApplications(Collection applicationNames);
}
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/CassandraRepositoryModule.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/CassandraRepositoryModule.java
index f1803b0c6..486a31628 100644
--- a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/CassandraRepositoryModule.java
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/CassandraRepositoryModule.java
@@ -35,6 +35,7 @@
import com.intuit.wasabi.repository.cassandra.accessor.ExclusionAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.ExperimentAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.ExperimentPageAccessor;
+import com.intuit.wasabi.repository.cassandra.accessor.ExperimentTagAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.PrioritiesAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.StagingAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.UserFeedbackAccessor;
@@ -65,6 +66,7 @@
import com.intuit.wasabi.repository.cassandra.provider.ExclusionAccessorProvider;
import com.intuit.wasabi.repository.cassandra.provider.ExperimentAccessorProvider;
import com.intuit.wasabi.repository.cassandra.provider.ExperimentPageAccessorProvider;
+import com.intuit.wasabi.repository.cassandra.provider.ExperimentTagAccessorProvider;
import com.intuit.wasabi.repository.cassandra.provider.MappingManagerProvider;
import com.intuit.wasabi.repository.cassandra.provider.PrioritiesAccessorProvider;
import com.intuit.wasabi.repository.cassandra.provider.StagingAccessorProvider;
@@ -90,7 +92,6 @@
import static com.google.inject.name.Names.named;
import static com.intuit.autumn.utils.PropertyFactory.create;
import static com.intuit.autumn.utils.PropertyFactory.getProperty;
-import static java.lang.Boolean.TRUE;
import static java.lang.Integer.parseInt;
import static org.slf4j.LoggerFactory.getLogger;
@@ -137,6 +138,7 @@ protected void configure() {
bind(UserFeedbackAccessor.class).toProvider(UserFeedbackAccessorProvider.class).in(Singleton.class);
bind(UserInfoAccessor.class).toProvider(UserInfoAccessorProvider.class).in(Singleton.class);
bind(UserRoleAccessor.class).toProvider(UserRoleAccessorProvider.class).in(Singleton.class);
+ bind(ExperimentTagAccessor.class).toProvider(ExperimentTagAccessorProvider.class).in(Singleton.class);
//Bind those indexes
bind(AppPageIndexAccessor.class).toProvider(AppPageIndexAccessorProvider.class).in(Singleton.class);
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/ClientConfiguration.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/ClientConfiguration.java
index d66f57e76..736fbcdea 100644
--- a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/ClientConfiguration.java
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/ClientConfiguration.java
@@ -16,6 +16,7 @@
package com.intuit.wasabi.repository.cassandra;
import com.datastax.driver.core.ConsistencyLevel;
+import com.datastax.driver.core.SocketOptions;
import com.intuit.wasabi.cassandra.datastax.CassandraDriver;
import java.util.Arrays;
@@ -135,6 +136,16 @@ public int getPoolTimeoutMillis() {
return parseInt(getProperty("poolTimeoutMillis", properties, "0"));
}
+ @Override
+ public int getConnectTimeoutMillis() {
+ return parseInt(getProperty("connectTimeoutMillis", properties, String.valueOf(SocketOptions.DEFAULT_CONNECT_TIMEOUT_MILLIS)));
+ }
+
+ @Override
+ public int getReadTimeoutMillis() {
+ return parseInt(getProperty("readTimeoutMillis", properties, String.valueOf(SocketOptions.DEFAULT_READ_TIMEOUT_MILLIS)));
+ }
+
@Override
public int getMaxConnectionsPerHostRemote() {
return parseInt(getProperty("maxConnectionsPerHostRemote", properties, "32"));
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentAccessor.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentAccessor.java
index 1c4e24e5c..1762435fc 100644
--- a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentAccessor.java
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentAccessor.java
@@ -21,9 +21,11 @@
import com.datastax.driver.mapping.annotations.Query;
import com.google.common.util.concurrent.ListenableFuture;
import com.intuit.wasabi.repository.cassandra.pojo.Experiment;
+import com.intuit.wasabi.repository.cassandra.pojo.index.ExperimentTagsByApplication;
import java.util.Date;
import java.util.List;
+import java.util.Set;
import java.util.UUID;
/**
@@ -53,13 +55,13 @@ public interface ExperimentAccessor {
"rule = ?, sample_percent = ?, " +
"start_time = ?, end_time = ?, " +
"state=?, label=?, app_name=?, modified=? , is_personalized=?, model_name=?, model_version=?," +
- " is_rapid_experiment=?, user_cap=?" +
+ " is_rapid_experiment=?, user_cap=?, tags=?" +
" where id = ?")
ResultSet updateExperiment(String description, String hypothesisIsCorrect, String results,
String rule, double sample_percent,
Date start_time, Date end_time, String state, String label, String app_name,
Date modified, boolean is_personalized, String model_name, String model_version,
- boolean is_rapid_experiment, int user_cap, UUID experimentId);
+ boolean is_rapid_experiment, int user_cap, Set tags, UUID experimentId);
@Query("select * from experiment where app_name = ?")
@@ -71,14 +73,19 @@ ResultSet updateExperiment(String description, String hypothesisIsCorrect, Strin
@Query("select * from experiment where id = ?")
Result selectBy(UUID experimentId);
+
@Query("insert into experiment " +
"(id, description, hypothesis_is_correct, results, rule, sample_percent, start_time, end_time, " +
" state, label, app_name, created, modified, is_personalized, model_name, model_version," +
- " is_rapid_experiment, user_cap, creatorid) " +
- "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
+ " is_rapid_experiment, user_cap, creatorid, tags) " +
+ "values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
void insertExperiment(UUID experimentId, String description, String hypothesisIsCorrect, String results,
String rule, double samplePercent,
Date startTime, Date endTime, String state, String label, String appName,
Date created, Date modified, boolean isPersonalized, String modelName,
- String modelVersion, boolean isRapidExperiment, int userCap, String creatorid);
+ String modelVersion, boolean isRapidExperiment, int userCap, String creatorid,
+ Set tags);
+
+ @Query("select tags from experiment where app_name = ?")
+ Result getAllTags(String appName);
}
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentTagAccessor.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentTagAccessor.java
new file mode 100644
index 000000000..a88707ec5
--- /dev/null
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentTagAccessor.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright 2017 Intuit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.intuit.wasabi.repository.cassandra.accessor;
+
+import com.datastax.driver.mapping.Result;
+import com.datastax.driver.mapping.annotations.Accessor;
+import com.datastax.driver.mapping.annotations.Query;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.intuit.wasabi.repository.cassandra.pojo.index.ExperimentTagsByApplication;
+
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Accessor for the tags associated with Experiments.
+ */
+@Accessor
+public interface ExperimentTagAccessor {
+
+ @Query("select * from experiment_tag where app_name = ?")
+ ListenableFuture> getExperimentTagsAsync(String appName);
+
+ @Query("insert into experiment_tag(app_name, exp_id, tags) values (?,?,?)")
+ void insert(String appName, UUID experimentId, Set tags);
+
+}
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepository.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepository.java
index 8081a8440..df2dfa876 100644
--- a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepository.java
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepository.java
@@ -45,22 +45,27 @@
import com.intuit.wasabi.repository.cassandra.accessor.ApplicationListAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.BucketAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.ExperimentAccessor;
+import com.intuit.wasabi.repository.cassandra.accessor.ExperimentTagAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.audit.BucketAuditLogAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.audit.ExperimentAuditLogAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.index.ExperimentLabelIndexAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.index.ExperimentState;
import com.intuit.wasabi.repository.cassandra.accessor.index.StateExperimentIndexAccessor;
import com.intuit.wasabi.repository.cassandra.pojo.index.ExperimentByAppNameLabel;
+import com.intuit.wasabi.repository.cassandra.pojo.index.ExperimentTagsByApplication;
import com.intuit.wasabi.repository.cassandra.pojo.index.StateExperimentIndex;
import org.slf4j.Logger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.stream.Collectors;
import static org.slf4j.LoggerFactory.getLogger;
@@ -90,6 +95,8 @@ public class CassandraExperimentRepository implements ExperimentRepository {
private ExperimentAuditLogAccessor experimentAuditLogAccessor;
+ private ExperimentTagAccessor experimentTagAccessor;
+
/**
* @return the experimentAccessor
*/
@@ -207,7 +214,8 @@ public CassandraExperimentRepository(CassandraDriver driver,
BucketAuditLogAccessor bucketAuditLogAccessor,
ExperimentAuditLogAccessor experimentAuditLogAccessor,
StateExperimentIndexAccessor stateExperimentIndexAccessor,
- ExperimentValidator validator) {
+ ExperimentValidator validator,
+ ExperimentTagAccessor experimentTagAccessor) {
this.driver = driver;
this.experimentAccessor = experimentAccessor;
this.experimentLabelIndexAccessor = experimentLabelIndexAccessor;
@@ -217,6 +225,7 @@ public CassandraExperimentRepository(CassandraDriver driver,
this.bucketAuditLogAccessor = bucketAuditLogAccessor;
this.experimentAuditLogAccessor = experimentAuditLogAccessor;
this.validator = validator;
+ this.experimentTagAccessor = experimentTagAccessor;
}
/**
@@ -389,10 +398,16 @@ public Experiment.ID createExperiment(NewExperiment newExperiment) {
newExperiment.getModelVersion(),
newExperiment.getIsRapidExperiment(),
newExperiment.getUserCap(),
- (newExperiment.getCreatorID() != null) ? newExperiment.getCreatorID() : "");
+ (newExperiment.getCreatorID() != null) ? newExperiment.getCreatorID() : "",
+ newExperiment.getTags());
+
// TODO - Do we need to create an application while creating a new experiment ?
createApplication(newExperiment.getApplicationName());
+
+ // update tags
+ updateExperimentTags(newExperiment.getApplicationName(), newExperiment.getId(), newExperiment.getTags());
+
} catch (Exception e) {
LOGGER.error("Error while creating experiment {}", newExperiment, e);
throw new RepositoryException("Exception while creating experiment " + newExperiment + " message " + e, e);
@@ -401,6 +416,24 @@ public Experiment.ID createExperiment(NewExperiment newExperiment) {
}
+ /**
+ * Updates the tags for a given Experiment. This also means that tags are deleted from
+ * the lookup table in Cassandra if they are removed from the experiment.
+ *
+ * @param applicationName the Application for which the tags should be added
+ * @param expId the Id of the experiment that has changed tags
+ * @param tags list of tags for the experiment
+ */
+ public void updateExperimentTags(Application.Name applicationName, Experiment.ID expId, Set tags) {
+
+ if (tags == null)
+ tags = Collections.EMPTY_SET; // need this to update this to delete tags
+
+ // update the tag table, just rewrite the complete entry
+ experimentTagAccessor.insert(applicationName.toString(), expId.getRawID(), tags);
+ }
+
+
/**
* {@inheritDoc}
*/
@@ -519,8 +552,11 @@ public Experiment updateExperiment(Experiment experiment) {
experiment.getModelVersion(),
experiment.getIsRapidExperiment(),
experiment.getUserCap(),
+ experiment.getTags(),
experiment.getID().getRawID());
+ updateExperimentTags(experiment.getApplicationName(), experiment.getID(), experiment.getTags());
+
// Point the experiment index to this experiment
updateExperimentLabelIndex(experiment.getID(), experiment.getApplicationName(), experiment.getLabel(),
experiment.getStartTime(), experiment.getEndTime(), experiment.getState());
@@ -1194,4 +1230,40 @@ public void createApplication(Application.Name applicationName) {
}
}
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Map> getTagListForApplications(Collection applicationNames) {
+
+ LOGGER.debug("Retrieving Experiment Tags for applications {}", applicationNames);
+
+ try {
+ List>> futures = new ArrayList<>();
+
+ for (Application.Name appName : applicationNames) {
+ ListenableFuture> resultSetFuture = experimentTagAccessor
+ .getExperimentTagsAsync(appName.toString());
+ futures.add(resultSetFuture);
+ }
+
+ Map> result = new HashMap<>();
+ for (ListenableFuture> future : futures) {
+ List expTagApplication = future.get().all();
+
+ Set allTagsForApplication = new TreeSet<>();
+ for (ExperimentTagsByApplication expTagsByApp : expTagApplication) {
+ allTagsForApplication.addAll(expTagsByApp.getTags());
+ }
+ result.put(Application.Name.valueOf(expTagApplication.get(0).getAppName()), allTagsForApplication);
+ }
+
+ return result;
+ } catch (Exception e) {
+ LOGGER.error("Error while retieving ExperimentTags for {}", applicationNames, e);
+ throw new RepositoryException("Unable to get ExperimentTags for applications: \""
+ + applicationNames.toString() + "\"" + e);
+ }
+ }
+
}
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/impl/ExperimentHelper.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/impl/ExperimentHelper.java
index fef9bed37..604ce621a 100644
--- a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/impl/ExperimentHelper.java
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/impl/ExperimentHelper.java
@@ -83,6 +83,7 @@ public static Experiment makeExperiment(
experimentObject.setRuleJson(convertRuleToJson(experimentPojo.getRule()));
experimentObject.setHypothesisIsCorrect(experimentPojo.getHypothesisIsCorrect());
experimentObject.setResults(experimentPojo.getResults());
+ experimentObject.setTags(experimentPojo.getTags());
return experimentObject;
}
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/pojo/Experiment.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/pojo/Experiment.java
index 7c53014c2..245a6244a 100644
--- a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/pojo/Experiment.java
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/pojo/Experiment.java
@@ -24,6 +24,7 @@
import lombok.NoArgsConstructor;
import java.util.Date;
+import java.util.Set;
import java.util.UUID;
/*****
@@ -85,6 +86,9 @@ public class Experiment {
@Column(name = "creatorid")
private String creatorId;
+ @Column(name = "tags")
+ private Set tags;
+
@Column(name = "hypothesis_is_correct")
private String hypothesisIsCorrect;
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/pojo/index/ExperimentTagsByApplication.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/pojo/index/ExperimentTagsByApplication.java
new file mode 100644
index 000000000..33972186d
--- /dev/null
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/pojo/index/ExperimentTagsByApplication.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright 2017 Intuit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.intuit.wasabi.repository.cassandra.pojo.index;
+
+import com.datastax.driver.mapping.annotations.ClusteringColumn;
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.Collections;
+import java.util.Set;
+import java.util.UUID;
+
+@Table(name = "experiment_tag")
+@Data
+@Builder(toBuilder = true)
+@NoArgsConstructor
+@AllArgsConstructor
+public class ExperimentTagsByApplication {
+
+ @PartitionKey(0)
+ @Column(name = "app_name")
+ String appName;
+
+ @ClusteringColumn(0)
+ @Column(name = "exp_id")
+ UUID expId;
+
+ @Column(name = "tags")
+ Set tags;
+
+ public Set getTags() {
+ return tags != null ? tags : Collections.EMPTY_SET;
+ }
+}
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/provider/ExperimentTagAccessorProvider.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/provider/ExperimentTagAccessorProvider.java
new file mode 100644
index 000000000..b7ab01d86
--- /dev/null
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/cassandra/provider/ExperimentTagAccessorProvider.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * Copyright 2017 Intuit
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *******************************************************************************/
+package com.intuit.wasabi.repository.cassandra.provider;
+
+import com.datastax.driver.core.Session;
+import com.datastax.driver.mapping.MappingManager;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.intuit.wasabi.cassandra.datastax.CassandraDriver;
+import com.intuit.wasabi.repository.cassandra.accessor.ExperimentTagAccessor;
+
+
+public class ExperimentTagAccessorProvider implements Provider {
+ private final Session session;
+ private final MappingManager manager;
+
+ @Inject
+ public ExperimentTagAccessorProvider(CassandraDriver driver) {
+ this.session = driver.getSession();
+ this.manager = new MappingManager(session);
+ }
+
+
+ @Override
+ public ExperimentTagAccessor get() {
+ return manager.createAccessor(ExperimentTagAccessor.class);
+ }
+}
\ No newline at end of file
diff --git a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/database/DatabaseExperimentRepository.java b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/database/DatabaseExperimentRepository.java
index bcb7ad6db..559f7bc19 100644
--- a/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/database/DatabaseExperimentRepository.java
+++ b/modules/repository-datastax/src/main/java/com/intuit/wasabi/repository/database/DatabaseExperimentRepository.java
@@ -41,6 +41,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import static com.intuit.wasabi.experimentobjects.Experiment.State.DELETED;
@@ -745,9 +746,7 @@ public BucketList getBuckets(Experiment.ID experimentID, boolean checkExperiment
}
/**
- * Creates an application at top level
- *
- * @param applicationName Application Name
+ * {@inheritDoc}
*/
@Override
public void createApplication(Application.Name applicationName) {
@@ -755,9 +754,13 @@ public void createApplication(Application.Name applicationName) {
}
@Override
- public void updateStateIndex(Experiment experiment) {
+ public Map> getTagListForApplications(Collection applicationNames) {
throw new UnsupportedOperationException("Not supported ");
}
+ @Override
+ public void updateStateIndex(Experiment experiment) {
+ throw new UnsupportedOperationException("Not supported ");
+ }
}
diff --git a/modules/repository-datastax/src/main/resources/cassandra_client_config.properties b/modules/repository-datastax/src/main/resources/cassandra_client_config.properties
index c1cfbc1bd..d79ff5873 100644
--- a/modules/repository-datastax/src/main/resources/cassandra_client_config.properties
+++ b/modules/repository-datastax/src/main/resources/cassandra_client_config.properties
@@ -25,6 +25,16 @@ keyspaceName:${cassandra.experiments.keyspaceName}
isSlowQueryLoggingEnabled:False
slowQueryLoggingThresholdMilli:100
+#The connection timeout in milliseconds.
+#As the name implies, the connection timeout defines how long the driver waits to establish a new connection to a Cassandra node before giving up.
+#Default value is 5000ms, increasing to 1 minutes to make functional tests run even in very low resource containers/environments.
+connectTimeoutMillis:60000
+
+#The per-host read timeout in milliseconds.
+#This defines how long the driver will wait for a given Cassandra node to answer a query.
+#Default value is 12000ms, increasing to 2 minutes to make functional tests run even in very low resource containers/environments.
+readTimeoutMillis:120000
+
#Both token aware balancing option is required. Otherwise will default to round robin policy
#if the LocalDC is specified but the UsedHostsPerRemoteDc < 0 then we will use round robin policy
tokenAwareLoadBalancingLocalDC:${cassandra.local.dc}
diff --git a/modules/repository-datastax/src/main/resources/com/intuit/wasabi/repository/impl/cassandra/migration/V035__Create_ExperimentTag_table.cql b/modules/repository-datastax/src/main/resources/com/intuit/wasabi/repository/impl/cassandra/migration/V035__Create_ExperimentTag_table.cql
new file mode 100644
index 000000000..59c8f082b
--- /dev/null
+++ b/modules/repository-datastax/src/main/resources/com/intuit/wasabi/repository/impl/cassandra/migration/V035__Create_ExperimentTag_table.cql
@@ -0,0 +1,6 @@
+create table experiment_tag (
+ app_name text,
+ exp_id uuid,
+ tags set,
+ PRIMARY KEY (app_name, exp_id)
+);
\ No newline at end of file
diff --git a/modules/repository-datastax/src/main/resources/com/intuit/wasabi/repository/impl/cassandra/migration/V036__Alter_Experiment_Add_Tags.cql b/modules/repository-datastax/src/main/resources/com/intuit/wasabi/repository/impl/cassandra/migration/V036__Alter_Experiment_Add_Tags.cql
new file mode 100644
index 000000000..8c85991f6
--- /dev/null
+++ b/modules/repository-datastax/src/main/resources/com/intuit/wasabi/repository/impl/cassandra/migration/V036__Alter_Experiment_Add_Tags.cql
@@ -0,0 +1 @@
+alter table experiment ADD tags set;
\ No newline at end of file
diff --git a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentAccessorITest.java b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentAccessorITest.java
index 5786ae991..6162e8933 100644
--- a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentAccessorITest.java
+++ b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/accessor/ExperimentAccessorITest.java
@@ -49,7 +49,7 @@ public void insertOneExperiments() {
"d1", "yes", "r1", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l1",
"app1", date1, date2, true,
- "m1", "v1", true, 5000, "c1");
+ "m1", "v1", true, 5000, "c1", null);
Result experiment1 = accessor.getExperimentById(experimentId1);
List experimentResult = experiment1.all();
@@ -80,12 +80,12 @@ public void insertTwoExperiments() {
"d1", "yes", "r1", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l1",
"app1", date1, date2, true,
- "m1", "v1", true, 5000, "c1");
+ "m1", "v1", true, 5000, "c1", null);
accessor.insertExperiment(experimentId2,
"d2", "no", "r2", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l2",
"app2", date1, date2, true,
- "m2", "v2", true, 5000, "c2");
+ "m2", "v2", true, 5000, "c2", null);
List experimentIds = new ArrayList<>();
diff --git a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepositoryITest.java b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepositoryITest.java
index f3596e1d5..e64442be3 100644
--- a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepositoryITest.java
+++ b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepositoryITest.java
@@ -40,11 +40,14 @@
import org.junit.Test;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
@@ -114,6 +117,7 @@ public void setUp() throws Exception {
newExperiment1.setStartTime(new Date());
newExperiment1.setEndTime(new Date());
newExperiment1.setSamplingPercent(0.2);
+ newExperiment1.setTags(new HashSet<>(Arrays.asList("tag1", "tag2")));
experimentID1 = repository.createExperiment(newExperiment1);
repository.createIndicesForNewExperiment(newExperiment1);
@@ -126,6 +130,7 @@ public void setUp() throws Exception {
newExperiment2.setStartTime(new Date());
newExperiment2.setEndTime(new Date());
newExperiment2.setSamplingPercent(0.2);
+ newExperiment2.setTags(new HashSet<>(Arrays.asList("tag3", "tag4")));
experimentID2 = repository.createExperiment(newExperiment2);
repository.createIndicesForNewExperiment(newExperiment2);
@@ -788,4 +793,17 @@ public void testCreateOneBucketsAndDeleteOneBucket() {
public void testCreateExperimentDuplicateThrowsException() {
ID experimentId = repository.createExperiment(newExperiment1);
}
+
+ @Test
+ public void testAddTagsToExperiment() {
+ Experiment experiment = repository.getExperiment(experimentID1);
+ experiment.setTags(new HashSet<>(Arrays.asList("tagNew", "tag2")));
+ repository.updateExperiment(experiment);
+
+ List allTags = Arrays.asList("tag2", "tag3", "tag4", "tagNew");
+
+ Map> result = repository.getTagListForApplications(Arrays.asList(appname));
+ assertTrue(result.size() == 1); // only one application
+ assertTrue(result.get(appname).containsAll(allTags));
+ }
}
diff --git a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepositoryTest.java b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepositoryTest.java
index 5066fa6e4..e84cae96b 100644
--- a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepositoryTest.java
+++ b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraExperimentRepositoryTest.java
@@ -36,29 +36,35 @@
import com.intuit.wasabi.repository.cassandra.accessor.ApplicationListAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.BucketAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.ExperimentAccessor;
+import com.intuit.wasabi.repository.cassandra.accessor.ExperimentTagAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.audit.BucketAuditLogAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.audit.ExperimentAuditLogAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.index.ExperimentLabelIndexAccessor;
import com.intuit.wasabi.repository.cassandra.accessor.index.StateExperimentIndexAccessor;
+import com.intuit.wasabi.repository.cassandra.pojo.index.ExperimentTagsByApplication;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class CassandraExperimentRepositoryTest {
@@ -86,6 +92,7 @@ public class CassandraExperimentRepositoryTest {
private ExperimentAccessor mockExperimentAccessor;
private ExperimentAuditLogAccessor mockExperimentAuditLogAccessor;
+ private ExperimentTagAccessor mockExperimentTagAccessor;
private StateExperimentIndexAccessor mockStateExperimentIndexAccessor;
@@ -102,6 +109,7 @@ public void setUp() throws Exception {
mockExperimentAuditLogAccessor = Mockito.mock(ExperimentAuditLogAccessor.class);
mockApplicationListAccessor = Mockito.mock(ApplicationListAccessor.class);
mockExperimentLabelIndexAccessor = Mockito.mock(ExperimentLabelIndexAccessor.class);
+ mockExperimentTagAccessor = Mockito.mock(ExperimentTagAccessor.class);
if (repository != null) return;
@@ -114,7 +122,8 @@ public void setUp() throws Exception {
mockDriver, mockExperimentAccessor, mockExperimentLabelIndexAccessor,
mockBucketAccessor, mockApplicationListAccessor,
mockBucketAuditLogAccessor, mockExperimentAuditLogAccessor,
- mockStateExperimentIndexAccessor, new ExperimentValidator());
+ mockStateExperimentIndexAccessor, new ExperimentValidator(),
+ mockExperimentTagAccessor);
bucket1 = Bucket.newInstance(experimentID1, Bucket.Label.valueOf("bl1")).withAllocationPercent(.23)
.withControl(true)
.withDescription("b1").withPayload("p1")
@@ -641,4 +650,74 @@ public void testGetBucketList() throws ExecutionException, InterruptedException
assertThat(actualResultMap.get(expId1).getBuckets().get(0).getLabel().toString(), is("Test-Bucket-1"));
}
+ @Test
+ public void testUpdateTags() {
+ //------ Input --------
+ Application.Name appName = Application.Name.valueOf("AppName");
+
+ Set exp1 = new TreeSet<>(Arrays.asList("tag1", "tag3"));
+ Set exp2 = new TreeSet<>(Arrays.asList("tag4"));
+ ExperimentTagsByApplication exp2Tags = ExperimentTagsByApplication.builder()
+ .tags(exp2).appName(appName.toString()).build();
+ ExperimentTagsByApplication exp1Tags = ExperimentTagsByApplication.builder()
+ .tags(exp1).appName(appName.toString()).build();
+
+ List dbResultList = Arrays.asList(exp1Tags, exp2Tags);
+
+ //------ Mocking interacting calls
+ Result dbResult = mock(Result.class);
+ when(mockExperimentAccessor.getAllTags(appName.toString())).thenReturn(dbResult);
+ when(dbResult.all()).thenReturn(dbResultList);
+
+ //------ Make call
+ repository.updateExperimentTags(appName, experimentID1, exp1);
+ repository.updateExperimentTags(appName, experimentID2, exp2);
+
+ //------ Verify result
+ verify(mockExperimentTagAccessor).insert(appName.toString(), experimentID1.getRawID(),
+ new TreeSet<>(Arrays.asList("tag1", "tag3")));
+ verify(mockExperimentTagAccessor).insert(appName.toString(), experimentID2.getRawID(),
+ new TreeSet<>(Arrays.asList("tag4")));
+ }
+
+ @Test
+ public void testGetTagList() throws ExecutionException, InterruptedException {
+ //------ Input --------
+ Application.Name app01 = Application.Name.valueOf("app01");
+ Application.Name app02 = Application.Name.valueOf("app02");
+ List appNames = Arrays.asList(app01, app02);
+
+ Set exp1 = new TreeSet<>(Arrays.asList("tag1", "tag3"));
+ Set exp2 = new TreeSet<>(Arrays.asList("tag4"));
+
+ List exp1Tags = Arrays.asList(ExperimentTagsByApplication.builder()
+ .tags(exp1).appName(app01.toString()).build());
+ List exp2Tags = Arrays.asList(ExperimentTagsByApplication.builder()
+ .tags(exp2).appName(app02.toString()).build());
+
+ //------ Mocking interacting calls
+ ListenableFuture> dbResultFuture1 = mock(ListenableFuture.class);
+ ListenableFuture> dbResultFuture2 = mock(ListenableFuture.class);
+
+ Result dbResult1 = mock(Result.class);
+ Result dbResult2 = mock(Result.class);
+
+ when(mockExperimentTagAccessor.getExperimentTagsAsync("app01")).thenReturn(dbResultFuture1);
+ when(mockExperimentTagAccessor.getExperimentTagsAsync("app02")).thenReturn(dbResultFuture2);
+
+ when(dbResultFuture1.get()).thenReturn(dbResult1);
+ when(dbResultFuture2.get()).thenReturn(dbResult2);
+
+ when(dbResult1.all()).thenReturn(exp1Tags);
+ when(dbResult2.all()).thenReturn(exp2Tags);
+
+ //------ Make call
+ Map> callResult = repository.getTagListForApplications(appNames);
+
+ //------ Verify result
+ assertEquals(2, callResult.size());
+ assertArrayEquals(exp1.toArray(), callResult.get(app01).toArray());
+ assertArrayEquals(exp2.toArray(), callResult.get(app02).toArray());
+ }
+
}
diff --git a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraMutexRepositoryITest.java b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraMutexRepositoryITest.java
index ce0ecda38..2ebd4c022 100644
--- a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraMutexRepositoryITest.java
+++ b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraMutexRepositoryITest.java
@@ -78,12 +78,12 @@ public void testGetExclusionsSuccess() {
"d1", "yes", "r1", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l1",
"app1", date1, date2, true,
- "m1", "v1", true, 5000, "c1");
+ "m1", "v1", true, 5000, "c1", null);
experimentAccessor.insertExperiment(pair.getRawID(),
"d2", "no", "r2", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l2",
"app2", date1, date2, true,
- "m2", "v2", true, 5000, "c2");
+ "m2", "v2", true, 5000, "c2", null);
repository.createExclusion(base, pair);
@@ -107,17 +107,17 @@ public void testGetExclusionsWithTwoExclusiosSuccess() {
"d1", "yes", "r1", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l1",
"app1", date1, date2, true,
- "m1", "v1", true, 5000, "c1");
+ "m1", "v1", true, 5000, "c1", null);
experimentAccessor.insertExperiment(pair1.getRawID(),
"d2", "yes", "r2", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l2",
"app2", date1, date2, true,
- "m2", "v2", true, 5000, "c2");
+ "m2", "v2", true, 5000, "c2", null);
experimentAccessor.insertExperiment(pair2.getRawID(),
"d2", "yes", "r2", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l2",
"app2", date1, date2, true,
- "m2", "v2", true, 5000, "c2");
+ "m2", "v2", true, 5000, "c2", null);
repository.createExclusion(base, pair1);
repository.createExclusion(base, pair2);
@@ -154,19 +154,19 @@ public void testGetNotExclusionsSuccess() {
"d1", "yes", "r1", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l1",
appName, date1, date2, true,
- "m1", "v1", true, 5000, "c1");
+ "m1", "v1", true, 5000, "c1", null);
experimentAccessor.insertExperiment(pair1.getRawID(),
"d2", "yes", "r2", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l2",
appName, date1, date2, true,
- "m2", "v2", true, 5000, "c2");
+ "m2", "v2", true, 5000, "c2", null);
experimentAccessor.insertExperiment(notExclusion.getRawID(),
"d2", "yes", "r2", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l2",
appName, date1, date2, true,
- "m2", "v2", true, 5000, "c2");
+ "m2", "v2", true, 5000, "c2", null);
repository.createExclusion(base, pair1);
diff --git a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraPrioritiesRepositoryITest.java b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraPrioritiesRepositoryITest.java
index a999674d6..55cd34bab 100644
--- a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraPrioritiesRepositoryITest.java
+++ b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/cassandra/impl/CassandraPrioritiesRepositoryITest.java
@@ -83,13 +83,13 @@ public void testTwoGetPrioritiesSuccess() {
"d1", "yes", "r1", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l1",
applicationName.toString(), date1, date2, true,
- "m1", "v1", true, 5000, "c1");
+ "m1", "v1", true, 5000, "c1", null);
experimentAccessor.insertExperiment(experimentId2,
"d2", "yes", "r2", "", 1.0, date1, date2,
com.intuit.wasabi.experimentobjects.Experiment.State.DRAFT.name(), "l2",
applicationName.toString(), date1, date2, true,
- "m2", "v2", true, 5000, "c2");
+ "m2", "v2", true, 5000, "c2", null);
List priorityIds = new ArrayList<>();
priorityIds.add(experimentId1);
diff --git a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/database/DatabaseExperimentRepositoryTest.java b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/database/DatabaseExperimentRepositoryTest.java
index d67453eab..00b4bc010 100644
--- a/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/database/DatabaseExperimentRepositoryTest.java
+++ b/modules/repository-datastax/src/test/java/com/intuit/wasabi/repository/database/DatabaseExperimentRepositoryTest.java
@@ -518,4 +518,14 @@ public void testGetBuckets() {
assertThat(result.getBuckets().size(), is(1));
assertThat(result.getBuckets().get(0).getExperimentID(), is(id));
}
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testUnsupportedTagMethod() {
+ repository.getTagListForApplications(Collections.EMPTY_LIST);
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void testUnsupportedApplicationMethod() {
+ repository.createApplication(Application.Name.valueOf("NotSupported"));
+ }
}
diff --git a/modules/ui/README.md b/modules/ui/README.md
index ef3c0cd62..ef736d552 100644
--- a/modules/ui/README.md
+++ b/modules/ui/README.md
@@ -57,3 +57,21 @@ Then:
This will build the UI into the dist folder and then start a web server, serving the UI from that folder on
http://localhost:9000 .
+
+### Troubleshooting
+
+If you happen to get some errors or problems with building a new version of the Wasabi UI after an update, you should
+follow these steps to refresh the build-time and run-time libraries:
+
+```
+% cd modules/ui
+% rm -rf node_modules
+% rm -rf app/bower_components
+% npm install
+% bower install
+```
+
+This will ensure that the versions of all the libraries are consistent with the current version of the Wasabi
+UI code.
+
+
diff --git a/modules/ui/app/images/icon_filter.png b/modules/ui/app/images/icon_filter.png
new file mode 100644
index 000000000..798899548
Binary files /dev/null and b/modules/ui/app/images/icon_filter.png differ
diff --git a/modules/ui/app/images/icon_filter_empty.png b/modules/ui/app/images/icon_filter_empty.png
new file mode 100644
index 000000000..170ddd35d
Binary files /dev/null and b/modules/ui/app/images/icon_filter_empty.png differ
diff --git a/modules/ui/app/index.html b/modules/ui/app/index.html
index cd426c0f6..ef00788e7 100644
--- a/modules/ui/app/index.html
+++ b/modules/ui/app/index.html
@@ -14,7 +14,9 @@
+
+
@@ -44,9 +46,9 @@
Applications
Plugins
-
- Tools
-