Skip to content
This repository has been archived by the owner on Jul 10, 2024. It is now read-only.

Commit

Permalink
Merge pull request #170 from zacharygoodwin/PROC-1523
Browse files Browse the repository at this point in the history
PROC-1523: Add namespaces filter
  • Loading branch information
zacharygoodwin authored Jun 6, 2024
2 parents 180e32c + 125d633 commit 4b060ef
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 30 deletions.
66 changes: 36 additions & 30 deletions proctor-common/src/main/java/com/indeed/proctor/common/Proctor.java
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,11 @@ private boolean isIncognitoEnabled(@Nonnull final Map<String, Object> inputConte
.orElse(false);
}

/**
* Aggregates properties of Payload Experiments for look up in Proctor Result. Checks Priority
* of Property to handle conflicting overrides of properties within namespaces. Properties are
* the Key:Value pairs of fields stored in JsonNode Payload type.
*/
private void populateProperties(
final String testName,
final TestBucket testBucket,
Expand All @@ -533,36 +538,37 @@ private void populateProperties(
payload.getJson()
.fields()
.forEachRemaining(
field -> {
final PayloadProperty curr = testProperties.get(field.getKey());
if (curr == null) {
testProperties.put(
field.getKey(),
PayloadProperty.builder()
.value(field.getValue())
.testName(testName)
.build());
} else {
final PayloadExperimentConfig currPayloadConfig =
testChoosers
.get(curr.getTestName())
.getTestDefinition()
.getPayloadExperimentConfig();
final PayloadExperimentConfig newPayloadConfig =
testChoosers
.get(testName)
.getTestDefinition()
.getPayloadExperimentConfig();
if (isHigherPriority(currPayloadConfig, newPayloadConfig)) {
testProperties.put(
field.getKey(),
PayloadProperty.builder()
.value(field.getValue())
.testName(testName)
.build());
}
}
});
field -> attemptStoringProperty(field, testName, testProperties));
}
}

private void attemptStoringProperty(
final Map.Entry<String, com.fasterxml.jackson.databind.JsonNode> field,
final String testName,
final Map<String, PayloadProperty> testProperties) {
final PayloadProperty curr = testProperties.get(field.getKey());
// store property if it does not exist in map
if (curr == null) {
testProperties.put(
field.getKey(),
PayloadProperty.builder().value(field.getValue()).testName(testName).build());
} else {
final PayloadExperimentConfig currPayloadConfig =
testChoosers
.get(curr.getTestName())
.getTestDefinition()
.getPayloadExperimentConfig();
final PayloadExperimentConfig newPayloadConfig =
testChoosers.get(testName).getTestDefinition().getPayloadExperimentConfig();
// store property if it has higher priority than the currently stored property
if (isHigherPriority(currPayloadConfig, newPayloadConfig)) {
testProperties.put(
field.getKey(),
PayloadProperty.builder()
.value(field.getValue())
.testName(testName)
.build());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.indeed.proctor.common.dynamic;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonTypeName;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.indeed.proctor.common.model.ConsumableTestDefinition;
import com.indeed.proctor.common.model.PayloadExperimentConfig;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;

@JsonTypeName("namespaces_filter")
public class NamespacesFilter implements DynamicFilter {
private final Set<String> namespaces;

public NamespacesFilter(@JsonProperty("namespaces") final Set<String> namespaces) {
Preconditions.checkArgument(
!CollectionUtils.isEmpty(namespaces),
"namespaces should be non-empty string list.");
this.namespaces = ImmutableSet.copyOf(namespaces);
}

@JsonProperty("namespaces")
public Set<String> getNamespaces() {
return this.namespaces;
}

@Override
public boolean matches(final String testName, final ConsumableTestDefinition testDefinition) {
final boolean isMatched =
Optional.ofNullable(testDefinition.getPayloadExperimentConfig())
.map(PayloadExperimentConfig::getNamespaces).orElse(Collections.emptyList())
.stream()
.anyMatch(this.namespaces::contains);
testDefinition.setDynamic(isMatched);
return isMatched;
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final NamespacesFilter that = (NamespacesFilter) o;
return namespaces.equals(that.namespaces);
}

@Override
public int hashCode() {
return Objects.hash(namespaces);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.indeed.proctor.common.dynamic;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.indeed.proctor.common.Serializers;
import com.indeed.proctor.common.model.ConsumableTestDefinition;
import com.indeed.proctor.common.model.PayloadExperimentConfig;
import com.indeed.proctor.common.model.TestType;
import org.junit.Test;

import java.util.List;

import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.assertEquals;

public class TestNamespacesFilter {
private static final ObjectMapper OBJECT_MAPPER = Serializers.lenient();

@Test
public void testMatchEmptyNamespaces() {
assertThatThrownBy(() -> new NamespacesFilter(emptySet()))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("namespaces should be non-empty string list.");
}

@Test
public void testMatchSingleNamespaces() {
final NamespacesFilter filter = new NamespacesFilter(ImmutableSet.of("test"));

assertFilterMatchesEquals(true, filter, ImmutableList.of("test"));
assertFilterMatchesEquals(true, filter, ImmutableList.of("foo", "test"));

assertFilterMatchesEquals(false, filter, emptyList());
assertFilterMatchesEquals(false, filter, ImmutableList.of("foo_test"));
}

@Test
public void testMatchNamespaces() {
final NamespacesFilter filter = new NamespacesFilter(ImmutableSet.of("test1", "test2"));

assertFilterMatchesEquals(true, filter, ImmutableList.of("test1"));
assertFilterMatchesEquals(true, filter, ImmutableList.of("test2"));
assertFilterMatchesEquals(true, filter, ImmutableList.of("test1", "test2"));
assertFilterMatchesEquals(true, filter, ImmutableList.of("test2", "test1"));
assertFilterMatchesEquals(true, filter, ImmutableList.of("foo", "test1", "test2"));

assertFilterMatchesEquals(false, filter, emptyList());
}

private void assertFilterMatchesEquals(
final boolean expected,
final NamespacesFilter filter,
final List<String> testNamespaces) {
final ConsumableTestDefinition definition =
new ConsumableTestDefinition(
"",
null,
TestType.EMAIL_ADDRESS,
null,
emptyList(),
emptyList(),
false,
emptyMap(),
null,
EMPTY_LIST);
definition.setPayloadExperimentConfig(
PayloadExperimentConfig.builder().namespaces(testNamespaces).build());

assertEquals(expected, filter.matches("", definition));
assertEquals(expected, definition.getDynamic());
}

@Test
public void testSerializeMetaTagsFilter() throws JsonProcessingException {
final NamespacesFilter filter = new NamespacesFilter(ImmutableSet.of("test1", "test2"));

final String serializedFilter = OBJECT_MAPPER.writeValueAsString(filter);

assertEquals(
"{\"type\":\"namespaces_filter\",\"namespaces\":[\"test1\",\"test2\"]}",
serializedFilter);
}
}

0 comments on commit 4b060ef

Please sign in to comment.