Skip to content

Commit 82f2b2d

Browse files
fix: prevent NPE in event report table-layout download when dimensions/items metadata entries are missing (#23940)
1 parent af8bf3d commit 82f2b2d

2 files changed

Lines changed: 130 additions & 3 deletions

File tree

dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/event/data/EventAggregateService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,7 @@ private void addEventReportDimensionalItems(
371371
(List<String>)
372372
((Map<String, Object>) grid.getMetaData().get(DIMENSIONS.getKey())).get(dimension);
373373

374-
if (legendOptions.isEmpty()) {
374+
if (legendOptions == null || legendOptions.isEmpty()) {
375375
List<Legend> legends = eventDimensionalItemObject.getLegendSet().getSortedLegends();
376376
addLegends(dimensionalItems, parentUid, legends);
377377
} else {
@@ -495,7 +495,7 @@ private Grid generateOutputGrid(
495495
MetadataItem metadataItem =
496496
(MetadataItem) ((Map<String, Object>) grid.getMetaData().get(ITEMS.getKey())).get(row);
497497

498-
String name = defaultIfEmpty(metadataItem.getName(), row);
498+
String name = metadataItem == null ? row : defaultIfEmpty(metadataItem.getName(), row);
499499
String col = defaultIfEmpty(COLUMN_NAMES.get(row), row);
500500

501501
outputGrid.addHeader(new GridHeader(name, col, TEXT, false, true));
@@ -516,7 +516,7 @@ private Grid generateOutputGrid(
516516
});
517517

518518
String display =
519-
builder.length() > 0
519+
!builder.isEmpty()
520520
? builder.substring(0, builder.lastIndexOf(DASH_PRETTY_SEPARATOR))
521521
: TOTAL_COLUMN_PRETTY_NAME;
522522

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright (c) 2004-2026, University of Oslo
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are met:
7+
*
8+
* 1. Redistributions of source code must retain the above copyright notice, this
9+
* list of conditions and the following disclaimer.
10+
*
11+
* 2. Redistributions in binary form must reproduce the above copyright notice,
12+
* this list of conditions and the following disclaimer in the documentation
13+
* and/or other materials provided with the distribution.
14+
*
15+
* 3. Neither the name of the copyright holder nor the names of its contributors
16+
* may be used to endorse or promote products derived from this software without
17+
* specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23+
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24+
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25+
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26+
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29+
*/
30+
package org.hisp.dhis.analytics.event.data;
31+
32+
import static org.hisp.dhis.test.TestBase.createDataElement;
33+
import static org.hisp.dhis.test.TestBase.createLegend;
34+
import static org.hisp.dhis.test.TestBase.createLegendSet;
35+
import static org.junit.jupiter.api.Assertions.assertFalse;
36+
37+
import java.lang.reflect.Method;
38+
import java.util.ArrayList;
39+
import java.util.HashMap;
40+
import java.util.List;
41+
import java.util.Map;
42+
import org.hisp.dhis.analytics.AnalyticsMetaDataKey;
43+
import org.hisp.dhis.analytics.EventAnalyticsDimensionalItem;
44+
import org.hisp.dhis.analytics.event.EventQueryParams;
45+
import org.hisp.dhis.common.Grid;
46+
import org.hisp.dhis.common.ValueType;
47+
import org.hisp.dhis.common.ValueTypedDimensionalItemObject;
48+
import org.hisp.dhis.dataelement.DataElement;
49+
import org.hisp.dhis.legend.Legend;
50+
import org.hisp.dhis.legend.LegendSet;
51+
import org.hisp.dhis.system.grid.ListGrid;
52+
import org.junit.jupiter.api.Test;
53+
54+
class EventAggregateServiceTest {
55+
private final EventAggregateService service =
56+
new EventAggregateService(null, null, null, null, null, null, null, null, null, null, null);
57+
58+
@Test
59+
void shouldFallBackToAllLegendsWhenDimensionsMetadataOmitsLegendSetDataElement()
60+
throws Exception {
61+
Legend legendA = createLegend('A', 0.0, 2.0);
62+
Legend legendB = createLegend('B', 2.0, 4.0);
63+
LegendSet legendSet = createLegendSet('A', legendA, legendB);
64+
65+
DataElement dataElement = createDataElement('A');
66+
dataElement.setValueType(ValueType.NUMBER);
67+
dataElement.setLegendSets(List.of(legendSet));
68+
69+
Grid grid = new ListGrid();
70+
grid.getMetaData().put(AnalyticsMetaDataKey.DIMENSIONS.getKey(), new HashMap<String, Object>());
71+
72+
List<EventAnalyticsDimensionalItem> dimensionalItems = new ArrayList<>();
73+
74+
invokeAddEventReportDimensionalItems(dataElement, dimensionalItems, grid, dataElement.getUid());
75+
76+
assertFalse(dimensionalItems.isEmpty());
77+
}
78+
79+
@Test
80+
void shouldNotNpeWhenItemsMetadataOmitsRowDimension() throws Exception {
81+
String missingDimensionUid = "deUidAAAAA";
82+
83+
Grid grid = new ListGrid();
84+
grid.getMetaData().put(AnalyticsMetaDataKey.ITEMS.getKey(), new HashMap<String, Object>());
85+
86+
EventQueryParams params = new EventQueryParams.Builder().build();
87+
88+
invokeGenerateOutputGrid(grid, params, List.of(), List.of(), List.of(missingDimensionUid));
89+
}
90+
91+
private void invokeAddEventReportDimensionalItems(
92+
ValueTypedDimensionalItemObject item,
93+
List<EventAnalyticsDimensionalItem> dimensionalItems,
94+
Grid grid,
95+
String dimension)
96+
throws Exception {
97+
Method method =
98+
EventAggregateService.class.getDeclaredMethod(
99+
"addEventReportDimensionalItems",
100+
ValueTypedDimensionalItemObject.class,
101+
List.class,
102+
Grid.class,
103+
String.class);
104+
method.setAccessible(true);
105+
method.invoke(service, item, dimensionalItems, grid, dimension);
106+
}
107+
108+
private Grid invokeGenerateOutputGrid(
109+
Grid grid,
110+
EventQueryParams params,
111+
List<Map<String, EventAnalyticsDimensionalItem>> rowPermutations,
112+
List<Map<String, EventAnalyticsDimensionalItem>> columnPermutations,
113+
List<String> rowDimensions)
114+
throws Exception {
115+
Method method =
116+
EventAggregateService.class.getDeclaredMethod(
117+
"generateOutputGrid",
118+
Grid.class,
119+
EventQueryParams.class,
120+
List.class,
121+
List.class,
122+
List.class);
123+
method.setAccessible(true);
124+
return (Grid)
125+
method.invoke(service, grid, params, rowPermutations, columnPermutations, rowDimensions);
126+
}
127+
}

0 commit comments

Comments
 (0)