Skip to content

Commit fd91678

Browse files
author
gdgate
authored
Merge pull request #950 from xMort/pdo-bb-2494-ranking-filter
Introduce ranking filter Reviewed-by: Peter Plocháň https://github.com/peter-plochan
2 parents 478e48d + bcb8814 commit fd91678

File tree

14 files changed

+611
-4
lines changed

14 files changed

+611
-4
lines changed

gooddata-java-model/src/main/java/com/gooddata/sdk/model/executeafm/afm/filter/ExtendedFilter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@JsonSubTypes({
1313
@JsonSubTypes.Type(value = FilterItem.class),
1414
@JsonSubTypes.Type(value = MeasureValueFilter.class, name = MeasureValueFilter.NAME),
15+
@JsonSubTypes.Type(value = RankingFilter.class, name = RankingFilter.NAME),
1516
})
1617
public interface ExtendedFilter {
1718
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Copyright (C) 2007-2020, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
7+
package com.gooddata.sdk.model.executeafm.afm.filter;
8+
9+
import com.fasterxml.jackson.annotation.JsonCreator;
10+
import com.fasterxml.jackson.annotation.JsonIgnore;
11+
import com.fasterxml.jackson.annotation.JsonInclude;
12+
import com.fasterxml.jackson.annotation.JsonProperty;
13+
import com.fasterxml.jackson.annotation.JsonRootName;
14+
import com.gooddata.sdk.common.util.GoodDataToStringBuilder;
15+
import com.gooddata.sdk.common.util.Validate;
16+
import com.gooddata.sdk.model.executeafm.IdentifierObjQualifier;
17+
import com.gooddata.sdk.model.executeafm.ObjQualifier;
18+
import com.gooddata.sdk.model.executeafm.Qualifier;
19+
import com.gooddata.sdk.model.executeafm.afm.ObjQualifierConverter;
20+
21+
import java.io.Serializable;
22+
import java.util.Collection;
23+
import java.util.List;
24+
import java.util.Objects;
25+
import java.util.stream.Collectors;
26+
import java.util.stream.Stream;
27+
28+
import static com.gooddata.sdk.common.util.Validate.notNull;
29+
import static java.lang.String.format;
30+
31+
/**
32+
* Represents a ranking filter applied on an insight.
33+
*/
34+
@JsonRootName(RankingFilter.NAME)
35+
@JsonInclude(JsonInclude.Include.NON_NULL)
36+
public class RankingFilter implements ExtendedFilter, CompatibilityFilter, Serializable {
37+
38+
public static final String NAME = "rankingFilter";
39+
40+
private static final long serialVersionUID = 2642298346540031612L;
41+
42+
private final List<Qualifier> measures;
43+
private final List<Qualifier> attributes;
44+
private final String operator;
45+
private final Integer value;
46+
47+
/**
48+
* Creates a new {@link RankingFilter} instance.
49+
*
50+
* @param measures measures on which is the ranking applied. Must not be null.
51+
* @param attributes attributes that define ranking granularity. Optional, can be null.
52+
* @param operator operator that defines the type of ranking.
53+
* @param value number of requested ranked records.
54+
*
55+
* @throws NullPointerException thrown when required parameter is not provided.
56+
*/
57+
@JsonCreator
58+
public RankingFilter(
59+
@JsonProperty("measures") final List<Qualifier> measures,
60+
@JsonProperty("attributes") final List<Qualifier> attributes,
61+
@JsonProperty("operator") final String operator,
62+
@JsonProperty("value") final Integer value) {
63+
this.measures = notNull(measures, "measures must not be null!");
64+
this.attributes = attributes;
65+
this.operator = notNull(operator, "operator must not be null!");
66+
this.value = notNull(value, "value must not be null!");
67+
}
68+
69+
public RankingFilter(
70+
final List<Qualifier> measures,
71+
final List<Qualifier> attributes,
72+
final RankingFilterOperator operator,
73+
final Integer value) {
74+
this(measures, attributes, notNull(operator, "operator must not be null!").name(), value);
75+
}
76+
77+
/**
78+
* Returns all the qualifiers used by the ranking filter.
79+
* <p>
80+
* This information comes handy if it is necessary, for example, to convert the ranking filter to use just
81+
* the URI object qualifiers instead of the identifier object qualifiers. It can be used to gather these
82+
* for a conversion service.
83+
*
84+
* @return all the qualifiers the ranking filter uses
85+
*/
86+
@JsonIgnore
87+
public Collection<ObjQualifier> getObjQualifiers() {
88+
return Stream.concat(
89+
this.measures.stream(),
90+
this.attributes == null ? Stream.empty() : this.attributes.stream()
91+
)
92+
.filter(ObjQualifier.class::isInstance)
93+
.map(ObjQualifier.class::cast)
94+
.collect(Collectors.toSet());
95+
}
96+
97+
/**
98+
* Copy itself using the given object qualifier converter in case when {@link IdentifierObjQualifier} instances are used in the object otherwise the
99+
* original object is returned.
100+
* <p>
101+
* The provided converter must be able to handle the conversion for the qualifiers that are of the {@link IdentifierObjQualifier} type that are used by
102+
* this object or its encapsulated child objects.
103+
*
104+
* @param objQualifierConverter The function that converts identifier qualifiers to the matching URI qualifiers. In case when the object uses the
105+
* identifier qualifiers, it
106+
* will return a new copy of itself or its encapsulated objects that used URI qualifiers, otherwise the original object is returned.
107+
* The parameter must not be null.
108+
*
109+
* @return copy of itself with replaced qualifiers in case when some {@link IdentifierObjQualifier} were used, otherwise original object is returned.
110+
*
111+
* @throws IllegalArgumentException The exception is thrown when conversion for the identifier qualifier used by this ranking filter could not be
112+
* made by the provided
113+
* converter or when provided converter is null.
114+
*/
115+
public RankingFilter withObjUriQualifiers(final ObjQualifierConverter objQualifierConverter) {
116+
notNull(objQualifierConverter, "objQualifierConverter");
117+
118+
return new RankingFilter(
119+
translateIdentifierQualifiers(this.measures, objQualifierConverter),
120+
attributes == null ? null : translateIdentifierQualifiers(this.attributes, objQualifierConverter),
121+
operator,
122+
value
123+
);
124+
}
125+
126+
private List<Qualifier> translateIdentifierQualifiers(final List<Qualifier> qualifiers, final ObjQualifierConverter objQualifierConverter) {
127+
return qualifiers.stream()
128+
.map(qualifier -> translateIdentifierQualifier(qualifier, objQualifierConverter))
129+
.collect(Collectors.toList());
130+
}
131+
132+
private Qualifier translateIdentifierQualifier(final Qualifier qualifier, final ObjQualifierConverter objQualifierConverter) {
133+
if (qualifier instanceof IdentifierObjQualifier) {
134+
final IdentifierObjQualifier identifierQualifierToConvert = (IdentifierObjQualifier) qualifier;
135+
return objQualifierConverter.convertToUriQualifier(identifierQualifierToConvert)
136+
.orElseThrow(() -> buildExceptionForFailedConversion(identifierQualifierToConvert));
137+
}
138+
return qualifier;
139+
}
140+
141+
private static IllegalArgumentException buildExceptionForFailedConversion(final IdentifierObjQualifier qualifierFailedToConvert) {
142+
return new IllegalArgumentException(format("Supplied converter does not provide conversion for '%s'!", qualifierFailedToConvert));
143+
}
144+
145+
/**
146+
* @return measures on which is the ranking applied
147+
*/
148+
public List<Qualifier> getMeasures() {
149+
return measures;
150+
}
151+
152+
/**
153+
* @return granularity of the ranking
154+
*/
155+
public List<Qualifier> getAttributes() {
156+
return attributes;
157+
}
158+
159+
/**
160+
* Get operator as an enum constant for easier programmatic access.
161+
* @return ranking operator constant
162+
*/
163+
@JsonIgnore
164+
public RankingFilterOperator getOperator() {
165+
return RankingFilterOperator.of(this.operator);
166+
}
167+
168+
/**
169+
* Get operator as a string representation of the {@link RankingFilterOperator} enum constant as it was parsed from the JSON.
170+
* @return string operator provided at the time of the filter instance creation
171+
*/
172+
@JsonProperty("operator")
173+
public String getOperatorAsString() {
174+
return operator;
175+
}
176+
177+
/**
178+
* @return number of ranked records
179+
*/
180+
public Integer getValue() {
181+
return value;
182+
}
183+
184+
@Override
185+
public boolean equals(final Object o) {
186+
if (this == o) return true;
187+
if (o == null || getClass() != o.getClass()) return false;
188+
final RankingFilter that = (RankingFilter) o;
189+
return Objects.equals(measures, that.measures) &&
190+
Objects.equals(attributes, that.attributes) &&
191+
Objects.equals(operator, that.operator) &&
192+
Objects.equals(value, that.value);
193+
}
194+
195+
@Override
196+
public int hashCode() {
197+
return Objects.hash(measures, attributes, operator, value);
198+
}
199+
200+
@Override
201+
public String toString() {
202+
return GoodDataToStringBuilder.defaultToString(this);
203+
}
204+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright (C) 2007-2020, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
7+
package com.gooddata.sdk.model.executeafm.afm.filter;
8+
9+
import com.fasterxml.jackson.annotation.JsonCreator;
10+
import com.fasterxml.jackson.annotation.JsonValue;
11+
12+
import static com.gooddata.sdk.common.util.Validate.notNull;
13+
import static java.lang.String.format;
14+
import static java.util.Arrays.stream;
15+
import static java.util.stream.Collectors.joining;
16+
17+
/**
18+
* Represents all the possible operators of {@link RankingFilter}.
19+
*/
20+
public enum RankingFilterOperator {
21+
TOP,
22+
BOTTOM;
23+
24+
@JsonValue
25+
@Override
26+
public String toString() {
27+
return name();
28+
}
29+
30+
@JsonCreator
31+
public static RankingFilterOperator of(final String operator) {
32+
notNull(operator, "operator");
33+
try {
34+
return RankingFilterOperator.valueOf(operator);
35+
} catch (IllegalArgumentException e) {
36+
throw new UnsupportedOperationException(
37+
format("Unknown value for ranking filter operator: \"%s\", supported values are: [%s]",
38+
operator, stream(RankingFilterOperator.values()).map(Enum::name).collect(joining(","))),
39+
e);
40+
}
41+
}
42+
}

gooddata-java-model/src/main/java/com/gooddata/sdk/model/md/visualization/VisualizationConverter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.gooddata.sdk.model.executeafm.afm.filter.NegativeAttributeFilter;
2121
import com.gooddata.sdk.model.executeafm.afm.filter.PositiveAttributeFilter;
2222
import com.gooddata.sdk.model.executeafm.afm.SimpleMeasureDefinition;
23+
import com.gooddata.sdk.model.executeafm.afm.filter.RankingFilter;
2324
import com.gooddata.sdk.model.executeafm.resultspec.Dimension;
2425
import com.gooddata.sdk.model.executeafm.resultspec.ResultSpec;
2526
import com.gooddata.sdk.model.executeafm.resultspec.SortItem;
@@ -310,7 +311,7 @@ private static <T> List<T> removeIrrelevantFilters(final List<T> filters) {
310311
return ((MeasureValueFilter) f).getCondition() != null;
311312
} else if (f instanceof NegativeAttributeFilter) {
312313
return !((NegativeAttributeFilter) f).isAllSelected();
313-
} else return f instanceof PositiveAttributeFilter;
314+
} else return f instanceof PositiveAttributeFilter || f instanceof RankingFilter;
314315

315316
})
316317
.collect(Collectors.toList());

gooddata-java-model/src/test/groovy/com/gooddata/sdk/model/executeafm/afm/filter/CompatibilityFilterTest.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ class CompatibilityFilterTest extends Specification {
1919
private static final String ABSOLUTE_DATE_FILTER_NO_ZERO_PAD_DATE_JSON = '/executeafm/afm/absoluteDateFilter_noZeroPadDate.json'
2020
private static final String RELATIVE_DATE_FILTER_JSON = '/executeafm/afm/relativeDateFilter.json'
2121
private static final String MEASURE_VALUE_FILTER_JSON = '/executeafm/afm/measureValueFilter.json'
22+
private static final String RANKING_FILTER_JSON = '/executeafm/afm/rankingFilter.json'
2223

2324
@Unroll
2425
def "should deserialize as #type"() {
@@ -37,6 +38,7 @@ class CompatibilityFilterTest extends Specification {
3738
AbsoluteDateFilter | ABSOLUTE_DATE_FILTER_NO_ZERO_PAD_DATE_JSON
3839
RelativeDateFilter | RELATIVE_DATE_FILTER_JSON
3940
MeasureValueFilter | MEASURE_VALUE_FILTER_JSON
41+
RankingFilter | RANKING_FILTER_JSON
4042

4143
type = typeClass.simpleName
4244
}

gooddata-java-model/src/test/groovy/com/gooddata/sdk/model/executeafm/afm/filter/ExtendedFilterTest.groovy

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class ExtendedFilterTest extends Specification {
1818
private static final String ABSOLUTE_DATE_FILTER_NO_ZERO_PAD_DATE_JSON = '/executeafm/afm/absoluteDateFilter_noZeroPadDate.json'
1919
private static final String RELATIVE_DATE_FILTER_JSON = '/executeafm/afm/relativeDateFilter.json'
2020
private static final String MEASURE_VALUE_FILTER_JSON = '/executeafm/afm/measureValueFilter.json'
21+
private static final String RANKING_FILTER_JSON = '/executeafm/afm/rankingFilter.json'
2122

2223
@Unroll
2324
def "should deserialize as #type"() {
@@ -35,6 +36,7 @@ class ExtendedFilterTest extends Specification {
3536
AbsoluteDateFilter | ABSOLUTE_DATE_FILTER_NO_ZERO_PAD_DATE_JSON
3637
RelativeDateFilter | RELATIVE_DATE_FILTER_JSON
3738
MeasureValueFilter | MEASURE_VALUE_FILTER_JSON
39+
RankingFilter | RANKING_FILTER_JSON
3840

3941
type = typeClass.simpleName
4042
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright (C) 2007-2020, GoodData(R) Corporation. All rights reserved.
3+
* This source code is licensed under the BSD-style license found in the
4+
* LICENSE.txt file in the root directory of this source tree.
5+
*/
6+
7+
package com.gooddata.sdk.model.executeafm.afm.filter
8+
9+
import spock.lang.Specification
10+
import spock.lang.Unroll
11+
12+
class RankingFilterOperatorTest extends Specification {
13+
14+
@Unroll
15+
def "'of' method should throw #exception when invalid value #value is provided"() {
16+
when:
17+
RankingFilterOperator.of(value)
18+
19+
then:
20+
thrown(exception)
21+
22+
where:
23+
value | exception
24+
'UNKNOWN' | UnsupportedOperationException
25+
null | IllegalArgumentException
26+
}
27+
28+
@Unroll
29+
def "'of' method should resolve the correct enum value when string '#name' is provided"() {
30+
when:
31+
def resolvedOperator = RankingFilterOperator.of(name)
32+
33+
then:
34+
resolvedOperator == operator
35+
36+
where:
37+
operator | name
38+
RankingFilterOperator.TOP | 'TOP'
39+
RankingFilterOperator.BOTTOM | 'BOTTOM'
40+
}
41+
42+
@Unroll
43+
def "'toString' method should return correct representation of #operator"() {
44+
when:
45+
def resolvedName = RankingFilterOperator.of(name)
46+
47+
then:
48+
resolvedName.toString() == name
49+
50+
where:
51+
operator | name
52+
RankingFilterOperator.TOP | 'TOP'
53+
RankingFilterOperator.BOTTOM | 'BOTTOM'
54+
}
55+
}

0 commit comments

Comments
 (0)