Skip to content

Commit 4b7e960

Browse files
committed
introduce execution of visualization object in ExecuteAfmService
1 parent 04edbb9 commit 4b7e960

8 files changed

Lines changed: 294 additions & 22 deletions

File tree

src/main/java/com/gooddata/GoodData.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,4 @@ public ExecuteAfmService getExecuteAfmService() {
518518
public LcmService getLcmService() {
519519
return lcmService;
520520
}
521-
522-
523521
}

src/main/java/com/gooddata/executeafm/ExecuteAfmService.java

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,22 @@
2222
import static com.gooddata.util.Validate.notNull;
2323

2424
/**
25-
* Service for executeAfm resource
25+
* Service for executing reports with new visualization structures like AFM or Visualization Object.
26+
* This includes GD API endpoints:
27+
* <ul>
28+
* <li>{@code /executeAfm}</li>
29+
* <li>{@code /executeVisualization}</li>
30+
* </ul>
2631
*/
2732
public class ExecuteAfmService extends AbstractService {
2833

29-
private static final String EXECUTION_URI = "/gdc/app/projects/{projectId}/executeAfm";
34+
private static final String AFM_EXECUTION_URI = "/gdc/app/projects/{projectId}/executeAfm";
35+
private static final String VISUALIZATION_EXECUTION_URI = "/gdc/app/projects/{projectId}/executeVisualization";
3036
private static final String RESULT_OFFSET = "offset";
3137
private static final String RESULT_LIMIT = "limit";
3238

3339
/**
34-
* Service for executeAfm resource
40+
* Constructor.
3541
*
3642
* @param restTemplate rest template
3743
* @param settings settings
@@ -41,17 +47,25 @@ public ExecuteAfmService(final RestTemplate restTemplate, final GoodDataSettings
4147
}
4248

4349
/**
44-
* Executes the given execution returning the execution response
50+
* @deprecated use {@link ExecuteAfmService#executeAfm(Project, Execution)}
51+
*/
52+
@Deprecated
53+
public ExecutionResponse execute(final Project project, final Execution execution) {
54+
return executeAfm(project, execution);
55+
}
56+
57+
/**
58+
* Executes the given AFM execution returning the execution response
4559
* @param project project of the execution
4660
* @param execution execution
4761
* @return response of the submitted execution
4862
*/
49-
public ExecutionResponse execute(final Project project, final Execution execution) {
63+
public ExecutionResponse executeAfm(final Project project, final Execution execution) {
5064
final String projectId = notNull(notNull(project, "project").getId(), "projectId");
5165
final ExecutionResponse response;
5266
try {
5367
response = restTemplate.postForObject(
54-
EXECUTION_URI,
68+
AFM_EXECUTION_URI,
5569
notNull(execution, "execution"),
5670
ExecutionResponse.class,
5771
projectId);
@@ -66,6 +80,32 @@ public ExecutionResponse execute(final Project project, final Execution executio
6680
return response;
6781
}
6882

83+
/**
84+
* Executes the given execution returning the execution response
85+
* @param project project of the execution
86+
* @param execution execution
87+
* @return response of the submitted execution
88+
*/
89+
public ExecutionResponse executeVisualization(final Project project, final VisualizationExecution execution) {
90+
final String projectId = notNull(notNull(project, "project").getId(), "projectId");
91+
final ExecutionResponse response;
92+
try {
93+
response = restTemplate.postForObject(
94+
VISUALIZATION_EXECUTION_URI,
95+
notNull(execution, "execution"),
96+
ExecutionResponse.class,
97+
projectId);
98+
} catch (GoodDataException | RestClientException e) {
99+
throw new GoodDataException("Unable to execute visualization", e);
100+
}
101+
102+
if (response == null) {
103+
throw new GoodDataException("Empty response when execution posted to API");
104+
}
105+
106+
return response;
107+
}
108+
69109
/**
70110
* Get for result of given response.
71111
* @param executionResponse response to get the result
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright (C) 2004-2018, 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.executeafm;
8+
9+
import com.fasterxml.jackson.annotation.JsonCreator;
10+
import com.fasterxml.jackson.annotation.JsonInclude;
11+
import com.fasterxml.jackson.annotation.JsonProperty;
12+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
13+
import com.fasterxml.jackson.annotation.JsonTypeName;
14+
import com.gooddata.executeafm.afm.CompatibilityFilter;
15+
import com.gooddata.executeafm.resultspec.ResultSpec;
16+
17+
import java.util.ArrayList;
18+
import java.util.Collections;
19+
import java.util.List;
20+
21+
/**
22+
* Represents structure for triggering execution with reference to visualization object.
23+
* Contains additional filters which should be merged with original ones defined in viz. object.
24+
*/
25+
@JsonTypeInfo(include = JsonTypeInfo.As.WRAPPER_OBJECT, use = JsonTypeInfo.Id.NAME)
26+
@JsonInclude(JsonInclude.Include.NON_NULL)
27+
@JsonTypeName("visualizationExecution")
28+
public class VisualizationExecution {
29+
30+
private final String reference;
31+
private List<CompatibilityFilter> filters;
32+
private ResultSpec resultSpec;
33+
34+
/**
35+
* Constructor.
36+
* Creates execution of visualization object without any additional filters or result specification.
37+
*
38+
* @param reference reference uri to visualization object metadata
39+
*/
40+
public VisualizationExecution(final String reference) {
41+
this(reference, null, null);
42+
}
43+
44+
/**
45+
* @param reference reference uri to visualization object metadata
46+
* @param filters additional filters which should be merged
47+
* @param resultSpec result specification of executed viz. object
48+
*/
49+
@JsonCreator
50+
VisualizationExecution(@JsonProperty("reference") final String reference,
51+
@JsonProperty("filters") final List<CompatibilityFilter> filters,
52+
@JsonProperty("resultSpec") final ResultSpec resultSpec) {
53+
this.reference = reference;
54+
this.resultSpec = resultSpec;
55+
this.filters = filters;
56+
}
57+
58+
/**
59+
* @return reference uri to visualization object metadata
60+
*/
61+
public String getReference() {
62+
return reference;
63+
}
64+
65+
public List<CompatibilityFilter> getFilters() {
66+
return filters;
67+
}
68+
69+
/**
70+
* Sets the result specification and returns this instance
71+
*
72+
* @param resultSpec result specification of executed viz. object
73+
* @return updated execution
74+
*/
75+
public VisualizationExecution setResultSpec(final ResultSpec resultSpec) {
76+
this.resultSpec = resultSpec;
77+
return this;
78+
}
79+
80+
/**
81+
* @return result specification of executed viz. object
82+
*/
83+
public ResultSpec getResultSpec() {
84+
return resultSpec;
85+
}
86+
87+
/**
88+
* Sets additional filters to this execution.
89+
*
90+
* @param filters additional filters
91+
* @return updated execution
92+
*/
93+
public VisualizationExecution setFilters(final List<CompatibilityFilter> filters) {
94+
this.filters = filters;
95+
return this;
96+
}
97+
}

src/test/groovy/com/gooddata/executeafm/ExecuteAfmServiceIT.groovy

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2007-2017, GoodData(R) Corporation. All rights reserved.
2+
* Copyright (C) 2007-2018, GoodData(R) Corporation. All rights reserved.
33
* This source code is licensed under the BSD-style license found in the
44
* LICENSE.txt file in the root directory of this source tree.
55
*/
@@ -42,10 +42,13 @@ class ExecuteAfmServiceIT extends GoodDataITBase<ExecuteAfmService> {
4242
Project project = readObjectFromResource('/project/project.json', Project)
4343

4444
@Shared
45-
Execution execution = new Execution(new Afm()
45+
Execution afmExecution = new Execution(new Afm()
4646
.addAttribute(new AttributeItem(ATTR_QUALIFIER, 'a1'))
4747
.addMeasure(new MeasureItem(new SimpleMeasureDefinition(MEASURE_QUALIFIER), 'm1')))
4848

49+
@Shared
50+
VisualizationExecution visualizationExecution = new VisualizationExecution('/gdc/md/PROJECT_ID/obj/4')
51+
4952
@Shared
5053
ExecutionResponse response = new ExecutionResponse([
5154
new ResultDimension(new AttributeHeader('name', 'a1', DF_URI, 'a1DfId', new AttributeInHeader('aName', ATTR_URI, 'a1Id'))),
@@ -57,45 +60,45 @@ class ExecuteAfmServiceIT extends GoodDataITBase<ExecuteAfmService> {
5760
onRequest()
5861
.havingMethodEqualTo('POST')
5962
.havingPathEqualTo('/gdc/app/projects/PROJECT_ID/executeAfm')
60-
.havingBody(jsonEquals(execution))
61-
.respond()
63+
.havingBody(jsonEquals(afmExecution))
64+
.respond()
6265
.withBody(OBJECT_MAPPER.writeValueAsString(response))
6366
.withStatus(200)
6467

6568
when:
66-
ExecutionResponse executed = service.execute(project, execution)
69+
ExecutionResponse executed = service.executeAfm(project, afmExecution)
6770

6871
then:
6972
executed?.dimensions?.size() == 2
7073
executed?.executionResultUri == RESULT_URI
7174
}
7275

73-
def "should handle failed execution request"() {
76+
def "should handle failed AFM execution request"() {
7477
given:
7578
onRequest()
7679
.havingMethodEqualTo('POST')
7780
.havingPathEqualTo('/gdc/app/projects/PROJECT_ID/executeAfm')
78-
.havingBody(jsonEquals(execution))
79-
.respond()
81+
.havingBody(jsonEquals(afmExecution))
82+
.respond()
8083
.withStatus(400)
8184

8285
when:
83-
service.execute(project, execution)
86+
service.executeAfm(project, afmExecution)
8487

8588
then:
8689
def ex = thrown(GoodDataException)
8790
ex.message == 'Unable to execute AFM'
8891
}
8992

9093
@Unroll
91-
def "should get result #order page"() {
94+
"should get execution result #order page"() {
9295
given:
9396
ExecutionResult result = new ExecutionResult(new String[0], new Paging([0], [0], [0]))
9497

9598
onRequest()
9699
.respond()
97100
.withStatus(202)
98-
.thenRespond()
101+
.thenRespond()
99102
.withBody(OBJECT_MAPPER.writeValueAsString(result))
100103
.withStatus(200)
101104

@@ -116,13 +119,13 @@ class ExecuteAfmServiceIT extends GoodDataITBase<ExecuteAfmService> {
116119
}
117120

118121
@Unroll
119-
def "should handle failed result with status #statusCode"() {
122+
"should handle failed result with status #statusCode"() {
120123
given:
121124
onRequest()
122125
.havingMethodEqualTo('GET')
123126
.havingPathEqualTo(RESULT_PATH)
124127
.havingQueryStringEqualTo(RESULT_QUERY)
125-
.respond()
128+
.respond()
126129
.withStatus(statusCode)
127130

128131
when:
@@ -141,6 +144,41 @@ class ExecuteAfmServiceIT extends GoodDataITBase<ExecuteAfmService> {
141144

142145
}
143146

147+
def "should execute visualization object"() {
148+
given:
149+
onRequest()
150+
.havingMethodEqualTo('POST')
151+
.havingPathEqualTo('/gdc/app/projects/PROJECT_ID/executeVisualization')
152+
.havingBody(jsonEquals(visualizationExecution))
153+
.respond()
154+
.withBody(OBJECT_MAPPER.writeValueAsString(response))
155+
.withStatus(200)
156+
157+
when:
158+
ExecutionResponse executed = service.executeVisualization(project, visualizationExecution)
159+
160+
then:
161+
executed?.dimensions?.size() == 2
162+
executed?.executionResultUri == RESULT_URI
163+
}
164+
165+
def "should handle failed visualization execution request"() {
166+
given:
167+
onRequest()
168+
.havingMethodEqualTo('POST')
169+
.havingPathEqualTo('/gdc/app/projects/PROJECT_ID/executeVisualization')
170+
.havingBody(jsonEquals(visualizationExecution))
171+
.respond()
172+
.withStatus(400)
173+
174+
when:
175+
service.executeVisualization(project, visualizationExecution)
176+
177+
then:
178+
def ex = thrown(GoodDataException)
179+
ex.message == 'Unable to execute visualization'
180+
}
181+
144182
@Override
145183
protected ExecuteAfmService getService() {
146184
return gd.executeAfmService
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (C) 2004-2018, 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.executeafm
8+
9+
import com.gooddata.executeafm.afm.ExpressionFilter
10+
import com.gooddata.executeafm.resultspec.AttributeSortItem
11+
import com.gooddata.executeafm.resultspec.Dimension
12+
import com.gooddata.executeafm.resultspec.ResultSpec
13+
import com.gooddata.executeafm.resultspec.TotalItem
14+
import com.gooddata.md.report.Total
15+
import spock.lang.Specification
16+
17+
import static com.gooddata.util.ResourceUtils.readObjectFromResource
18+
import static net.javacrumbs.jsonunit.JsonMatchers.jsonEquals
19+
import static net.javacrumbs.jsonunit.core.util.ResourceUtils.resource
20+
import static spock.util.matcher.HamcrestSupport.that
21+
22+
23+
class VisualizationExecutionTest extends Specification {
24+
25+
static final String VIZ_EXECUTION_JSON = 'executeafm/visualizationExecution.json'
26+
static final String VIZ_EXECUTION_FULL_JSON = 'executeafm/visualizationExecutionFull.json'
27+
28+
def "should deserialize with reference only"() {
29+
when:
30+
def execution = readObjectFromResource("/$VIZ_EXECUTION_JSON", VisualizationExecution)
31+
32+
then:
33+
execution?.reference == '/gdc/md/PROJECT/vizObjUri'
34+
!execution?.filters
35+
!execution?.resultSpec
36+
}
37+
38+
def "should serialize"() {
39+
expect:
40+
that new VisualizationExecution(
41+
'/gdc/md/PROJECT/vizObjUri',
42+
[new ExpressionFilter('some expression')],
43+
new ResultSpec([new Dimension(['i1'], [new TotalItem('mId', Total.AVG, 'a1')].toSet())],
44+
[new AttributeSortItem('asc', 'aId')])
45+
), jsonEquals(resource(VIZ_EXECUTION_FULL_JSON))
46+
}
47+
48+
def "should deserialize"() {
49+
when:
50+
def execution = readObjectFromResource("/$VIZ_EXECUTION_FULL_JSON", VisualizationExecution)
51+
52+
then:
53+
execution?.reference == '/gdc/md/PROJECT/vizObjUri'
54+
execution?.filters?.every { it.class == ExpressionFilter && it.value == 'some expression' }
55+
execution?.resultSpec?.sorts?.every { it instanceof AttributeSortItem && it.attributeIdentifier == 'aId' }
56+
execution?.resultSpec?.dimensions?.every { it.itemIdentifiers == ['i1'] && it.totals*.measureIdentifier == ['mId'] }
57+
}
58+
}

src/test/java/com/gooddata/executeafm/ExecuteAfmServiceAT.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public void testExecuteAfm() {
4444
.addMeasure(new MeasureItem(new SimpleMeasureDefinition(new UriObjQualifier(metric.getUri())), "m1"))
4545
);
4646

47-
response = gd.getExecuteAfmService().execute(project, execution);
47+
response = gd.getExecuteAfmService().executeAfm(project, execution);
4848

4949
assertThat(response, notNullValue());
5050
assertThat("should have 2 dimensions", response.getDimensions(), hasSize(2));

0 commit comments

Comments
 (0)