Skip to content

Commit caa39b0

Browse files
committed
Add new AFM execution conversion methods with totals support
* `VisualizationConverter.convertToResultSpecWithTotals` * `VisualizationConverter.convertToAfmWithNativeTotals` * `VisualizationConverter.convertToExecutionWithTotals`
1 parent c8acc5c commit caa39b0

File tree

6 files changed

+605
-8
lines changed

6 files changed

+605
-8
lines changed

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

+161-6
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.gooddata.sdk.model.executeafm.Execution;
1212
import com.gooddata.sdk.model.executeafm.afm.Afm;
1313
import com.gooddata.sdk.model.executeafm.afm.AttributeItem;
14+
import com.gooddata.sdk.model.executeafm.afm.NativeTotalItem;
1415
import com.gooddata.sdk.model.executeafm.afm.filter.CompatibilityFilter;
1516
import com.gooddata.sdk.model.executeafm.afm.filter.DateFilter;
1617
import com.gooddata.sdk.model.executeafm.afm.filter.ExtendedFilter;
@@ -24,8 +25,11 @@
2425
import com.gooddata.sdk.model.executeafm.resultspec.Dimension;
2526
import com.gooddata.sdk.model.executeafm.resultspec.ResultSpec;
2627
import com.gooddata.sdk.model.executeafm.resultspec.SortItem;
28+
import com.gooddata.sdk.model.executeafm.resultspec.TotalItem;
29+
import com.gooddata.sdk.model.md.report.Total;
2730

2831
import java.util.ArrayList;
32+
import java.util.HashSet;
2933
import java.util.List;
3034
import java.util.function.Function;
3135
import java.util.stream.Collectors;
@@ -43,9 +47,12 @@ public abstract class VisualizationConverter {
4347

4448
/**
4549
* Generate Execution from Visualization object.
50+
* <p>
51+
* <b>NOTE: totals are not included in this conversion</b>
4652
*
4753
* @param visualizationObject which will be converted to {@link Execution}
48-
* @param visualizationClassGetter {@link Function} for fetching VisualizationClass, which is necessary for correct generation of {@link ResultSpec}
54+
* @param visualizationClassGetter {@link Function} for fetching VisualizationClass,
55+
* which is necessary for correct generation of {@link ResultSpec}
4956
* @return {@link Execution} object
5057
* @see #convertToExecution(VisualizationObject, VisualizationClass)
5158
*/
@@ -59,6 +66,8 @@ public static Execution convertToExecution(final VisualizationObject visualizati
5966

6067
/**
6168
* Generate Execution from Visualization object.
69+
* <p>
70+
* <b>NOTE: totals are not included in this conversion</b>
6271
*
6372
* @param visualizationObject which will be converted to {@link Execution}
6473
* @param visualizationClass visualizationClass, which is necessary for correct generation of {@link ResultSpec}
@@ -75,27 +84,80 @@ public static Execution convertToExecution(final VisualizationObject visualizati
7584
return new Execution(afm, resultSpec);
7685
}
7786

87+
/**
88+
* Generate Execution from Visualization object with totals included.
89+
*
90+
* @param visualizationObject which will be converted to {@link Execution}
91+
* @param visualizationClassGetter {@link Function} for fetching VisualizationClass,
92+
* which is necessary for correct generation of {@link ResultSpec}
93+
* @return {@link Execution} object
94+
* @see #convertToExecutionWithTotals(VisualizationObject, VisualizationClass)
95+
*/
96+
public static Execution convertToExecutionWithTotals(final VisualizationObject visualizationObject,
97+
final Function<String, VisualizationClass> visualizationClassGetter) {
98+
notNull(visualizationObject, "visualizationObject");
99+
notNull(visualizationClassGetter, "visualizationClassGetter");
100+
return convertToExecutionWithTotals(visualizationObject,
101+
visualizationClassGetter.apply(visualizationObject.getVisualizationClassUri()));
102+
}
103+
104+
/**
105+
* Generate Execution from Visualization object with totals included.
106+
*
107+
* @param visualizationObject which will be converted to {@link Execution}
108+
* @param visualizationClass visualizationClass, which is necessary for correct generation of {@link ResultSpec}
109+
* @return {@link Execution} object
110+
* @see #convertToAfmWithNativeTotals(VisualizationObject)
111+
* @see #convertToResultSpecWithTotals(VisualizationObject, VisualizationClass)
112+
*/
113+
public static Execution convertToExecutionWithTotals(final VisualizationObject visualizationObject,
114+
final VisualizationClass visualizationClass) {
115+
notNull(visualizationObject, "visualizationObject");
116+
notNull(visualizationClass, "visualizationClass");
117+
ResultSpec resultSpec = convertToResultSpecWithTotals(visualizationObject, visualizationClass);
118+
Afm afm = convertToAfmWithNativeTotals(visualizationObject);
119+
return new Execution(afm, resultSpec);
120+
}
121+
78122
/**
79123
* Generate Afm from Visualization object.
124+
* <p>
125+
* <b>NOTE: native totals are not included in this conversion</b>
80126
*
81127
* @param visualizationObject which will be converted to {@link Execution}
82128
* @return {@link Afm} object
83129
*/
84130
public static Afm convertToAfm(final VisualizationObject visualizationObject) {
131+
notNull(visualizationObject, "visualizationObject");
132+
final VisualizationObject visualizationObjectWithoutTotals = removeTotals(visualizationObject);
133+
return convertToAfmWithNativeTotals(visualizationObjectWithoutTotals);
134+
}
135+
136+
/**
137+
* Generate Afm from Visualization object with native totals included.
138+
*
139+
* @param visualizationObject which will be converted to {@link Execution}
140+
* @return {@link Afm} object
141+
*/
142+
public static Afm convertToAfmWithNativeTotals(final VisualizationObject visualizationObject) {
85143
notNull(visualizationObject, "visualizationObject");
86144
final List<AttributeItem> attributes = convertAttributes(visualizationObject.getAttributes());
87145
final List<CompatibilityFilter> filters = convertFilters(visualizationObject.getFilters());
88146
final List<MeasureItem> measures = convertMeasures(visualizationObject.getMeasures());
147+
final List<NativeTotalItem> totals = convertNativeTotals(visualizationObject);
89148

90-
return new Afm(attributes, filters, measures, null);
149+
return new Afm(attributes, filters, measures, totals);
91150
}
92151

93152
/**
94153
* Generate ResultSpec from Visualization object. Currently {@link ResultSpec}'s {@link Dimension}s can be generated
95154
* for table and four types of chart: bar, column, line and pie.
155+
* <p>
156+
* <b>NOTE: totals are not included in this conversion</b>
96157
*
97158
* @param visualizationObject which will be converted to {@link Execution}
98-
* @param visualizationClassGetter {@link Function} for fetching VisualizationClass, which is necessary for correct generation of {@link ResultSpec}
159+
* @param visualizationClassGetter {@link Function} for fetching VisualizationClass,
160+
* which is necessary for correct generation of {@link ResultSpec}
99161
* @return {@link Execution} object
100162
*/
101163
public static ResultSpec convertToResultSpec(final VisualizationObject visualizationObject,
@@ -109,6 +171,8 @@ public static ResultSpec convertToResultSpec(final VisualizationObject visualiza
109171
/**
110172
* Generate ResultSpec from Visualization object. Currently {@link ResultSpec}'s {@link Dimension}s can be generated
111173
* for table and four types of chart: bar, column, line and pie.
174+
* <p>
175+
* <b>NOTE: totals are not included in this conversion</b>
112176
*
113177
* @param visualizationObject which will be converted to {@link Execution}
114178
* @param visualizationClass VisualizationClass, which is necessary for correct generation of {@link ResultSpec}
@@ -118,6 +182,39 @@ public static ResultSpec convertToResultSpec(final VisualizationObject visualiza
118182
final VisualizationClass visualizationClass) {
119183
notNull(visualizationObject, "visualizationObject");
120184
notNull(visualizationClass, "visualizationClass");
185+
final VisualizationObject visualizationObjectWithoutTotals = removeTotals(visualizationObject);
186+
return convertToResultSpecWithTotals(visualizationObjectWithoutTotals, visualizationClass);
187+
}
188+
189+
/**
190+
* Generate ResultSpec from Visualization object with totals included. Currently {@link ResultSpec}'s {@link Dimension}s
191+
* can be generated for table and four types of chart: bar, column, line and pie.
192+
*
193+
* @param visualizationObject which will be converted to {@link Execution}
194+
* @param visualizationClassGetter {@link Function} for fetching VisualizationClass,
195+
* which is necessary for correct generation of {@link ResultSpec}
196+
* @return {@link Execution} object
197+
*/
198+
public static ResultSpec convertToResultSpecWithTotals(final VisualizationObject visualizationObject,
199+
final Function<String, VisualizationClass> visualizationClassGetter) {
200+
notNull(visualizationObject, "visualizationObject");
201+
notNull(visualizationClassGetter, "visualizationClassGetter");
202+
return convertToResultSpecWithTotals(visualizationObject,
203+
visualizationClassGetter.apply(visualizationObject.getVisualizationClassUri()));
204+
}
205+
206+
/**
207+
* Generate ResultSpec from Visualization object with totals included. Currently {@link ResultSpec}'s {@link Dimension}s
208+
* can be generated for table and four types of chart: bar, column, line and pie.
209+
*
210+
* @param visualizationObject which will be converted to {@link Execution}
211+
* @param visualizationClass VisualizationClass, which is necessary for correct generation of {@link ResultSpec}
212+
* @return {@link Execution} object
213+
*/
214+
public static ResultSpec convertToResultSpecWithTotals(final VisualizationObject visualizationObject,
215+
final VisualizationClass visualizationClass) {
216+
notNull(visualizationObject, "visualizationObject");
217+
notNull(visualizationClass, "visualizationClass");
121218
isTrue(visualizationObject.getVisualizationClassUri().equals(visualizationClass.getUri()),
122219
"visualizationClass URI does not match the URI within visualizationObject, "
123220
+ "you're trying to create ResultSpec for incompatible objects");
@@ -144,6 +241,21 @@ static List<SortItem> parseSorting(final String properties) throws Exception {
144241
return MAPPER.convertValue(nodeSortItems, mapType);
145242
}
146243

244+
/**
245+
* Creates a new {@link VisualizationObject} derived from the original one, with all "totals" removed from its buckets.
246+
* This is to ensure backward compatibility in cases where totals were not previously handled.
247+
*
248+
* @param visualizationObject original {@link VisualizationObject}
249+
* @return a new VisualizationObject derived from the original but without any totals in the buckets.
250+
*/
251+
private static VisualizationObject removeTotals(final VisualizationObject visualizationObject) {
252+
final List<Bucket> bucketsWithoutTotals = visualizationObject.getBuckets().stream()
253+
// create buckets without totals
254+
.map(bucket -> new Bucket(bucket.getLocalIdentifier(), bucket.getItems()))
255+
.collect(toList());
256+
return visualizationObject.withBuckets(bucketsWithoutTotals);
257+
}
258+
147259
private static List<Dimension> getDimensions(final VisualizationObject visualizationObject,
148260
final VisualizationType visualizationType) {
149261
switch (visualizationType) {
@@ -216,12 +328,16 @@ private static List<Dimension> getDimensionsForTable(final VisualizationObject v
216328
List<Dimension> dimensions = new ArrayList<>();
217329

218330
List<VisualizationAttribute> attributes = visualizationObject.getAttributes();
331+
List<TotalItem> totals = visualizationObject.getTotals();
219332

220333
if (!attributes.isEmpty()) {
221-
dimensions.add(new Dimension(attributes.stream()
334+
final Dimension attributeDimension = new Dimension(attributes.stream()
222335
.map(VisualizationAttribute::getLocalIdentifier)
223-
.collect(toList())
224-
));
336+
.collect(toList()));
337+
if (!totals.isEmpty()) {
338+
attributeDimension.setTotals(new HashSet<>(totals));
339+
}
340+
dimensions.add(attributeDimension);
225341
} else {
226342
dimensions.add(new Dimension(new ArrayList<>()));
227343
}
@@ -316,4 +432,43 @@ private static <T> List<T> removeIrrelevantFilters(final List<T> filters) {
316432
})
317433
.collect(Collectors.toList());
318434
}
435+
436+
private static List<NativeTotalItem> convertNativeTotals(final VisualizationObject visualizationObject) {
437+
final List<Bucket> attributeBuckets = getAttributeBuckets(visualizationObject);
438+
final List<String> attributeIds = getIdsFromAttributeBuckets(attributeBuckets);
439+
return attributeBuckets.stream()
440+
.filter(bucket -> bucket.getTotals() != null)
441+
.flatMap(bucket -> bucket.getTotals().stream())
442+
.filter(totalItem -> isNativeTotal(totalItem) && attributeIds.contains(totalItem.getAttributeIdentifier()))
443+
.map(totalItem -> convertToNativeTotalItem(totalItem, attributeIds))
444+
.collect(toList());
445+
}
446+
447+
private static NativeTotalItem convertToNativeTotalItem(TotalItem totalItem, List<String> attributeIds) {
448+
final int attributeIdx = attributeIds.indexOf(totalItem.getAttributeIdentifier());
449+
return new NativeTotalItem(
450+
totalItem.getMeasureIdentifier(),
451+
new ArrayList<>(attributeIds.subList(0, attributeIdx))
452+
);
453+
}
454+
455+
private static List<Bucket> getAttributeBuckets(final VisualizationObject visualizationObject) {
456+
return visualizationObject.getBuckets().stream()
457+
.filter(bucket -> bucket.getItems().stream().allMatch(AttributeItem.class::isInstance))
458+
.collect(toList());
459+
}
460+
461+
private static List<String> getIdsFromAttributeBuckets(final List<Bucket> attributeBuckets) {
462+
return attributeBuckets.stream()
463+
.flatMap(bucket ->
464+
bucket.getItems().stream()
465+
.map(AttributeItem.class::cast)
466+
.map(AttributeItem::getLocalIdentifier)
467+
)
468+
.collect(toList());
469+
}
470+
471+
private static boolean isNativeTotal(TotalItem totalItem) {
472+
return totalItem.getType() != null && Total.NAT.name().equals(totalItem.getType().toUpperCase());
473+
}
319474
}

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

+23
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import java.io.Serializable;
3535
import java.util.ArrayList;
36+
import java.util.HashMap;
3637
import java.util.List;
3738
import java.util.Map;
3839
import java.util.function.Function;
@@ -225,6 +226,14 @@ public void setBuckets(List<Bucket> buckets) {
225226
content.setBuckets(buckets);
226227
}
227228

229+
/**
230+
* @return a new copy of this {@link VisualizationObject} with the specified buckets
231+
*/
232+
@JsonIgnore
233+
public VisualizationObject withBuckets(List<Bucket> buckets) {
234+
return new VisualizationObject(content.withBuckets(buckets), meta);
235+
}
236+
228237
/**
229238
* @return filters from visualization object
230239
*/
@@ -433,5 +442,19 @@ public UriObjQualifier getVisualizationClass() {
433442
public Map<String, String> getReferenceItems() {
434443
return referenceItems;
435444
}
445+
446+
/**
447+
* @return a new copy of this {@link Content} with the specified buckets
448+
*/
449+
@JsonIgnore
450+
public Content withBuckets(List<Bucket> buckets) {
451+
return new Content(
452+
visualizationClass,
453+
buckets,
454+
filters != null ? new ArrayList<>(filters) : null,
455+
properties,
456+
referenceItems != null ? new HashMap<>(referenceItems) : null
457+
);
458+
}
436459
}
437460
}

0 commit comments

Comments
 (0)