Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: program param is optional in TEI controller [DHIS2-16051] #15517

Merged
merged 3 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -187,10 +187,10 @@ public class CommonQueryRequest {

private String lastUpdated;

/** weather the query should consider only items with lat/long coordinates */
/** whether the query should consider only items with lat/long coordinates */
private boolean coordinatesOnly;

/** weather the query should consider only items with geometry */
/** whether the query should consider only items with geometry */
private boolean geometryOnly;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,6 @@ public enum ErrorCode {
E7133("Query cannot be executed, possibly because of invalid types or invalid operation"),
E7134("Cannot retrieve total value for data elements with skip total category combination"),
E7135("Date time is not parsable: `{0}`"),
E7136("Program is not specified"),
E7137("Expression is not parsable: `{0}`"),
E7138("Invalid offset: `{0}`"),
E7139("Parameters programStatus and enrollmentStatus cannot be used together"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.With;
import org.hisp.dhis.analytics.common.params.dimension.DimensionIdentifier;
import org.hisp.dhis.analytics.common.params.dimension.DimensionParam;
import org.hisp.dhis.common.DimensionalItemObject;
Expand Down Expand Up @@ -148,12 +149,15 @@ public class CommonParams {
/** Indicates if additional ou hierarchy data should be provided. */
private final boolean showHierarchy;

/** weather the query should consider only items with lat/long coordinates */
/** whether the query should consider only items with lat/long coordinates */
private boolean coordinatesOnly;

/** weather the query should consider only items with geometry */
/** whether the query should consider only items with geometry */
private boolean geometryOnly;

/** whether the programs comes from the request or not */
@With private final boolean programsFromRequest;

/**
* Indicates whether this query defines a master identifier scheme different from the default
* (UID).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public class DimensionParam implements UidObject {
* @param dimensionalObjectOrQueryItem either a {@link DimensionalObject} or {@link QueryItem}, or
* a static dimension.
* @param dimensionParamType the {@link DimensionParamType} for the {@link DimensionParam}
* returned (weather it's a filter or a dimension).
* returned (whether it's a filter or a dimension).
* @param items the list of items parameters for this DimensionParam.
* @return a new instance of {@link DimensionParam}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import static org.hisp.dhis.common.CodeGenerator.isValidUid;
import static org.hisp.dhis.feedback.ErrorCode.E4014;
import static org.hisp.dhis.feedback.ErrorCode.E7136;
import static org.hisp.dhis.feedback.ErrorCode.E7139;

import org.hisp.dhis.analytics.common.CommonQueryRequest;
Expand All @@ -51,9 +50,6 @@ public class CommonQueryRequestValidator implements Validator<CommonQueryRequest
*/
@Override
public void validate(CommonQueryRequest commonQueryRequest) {
if (!commonQueryRequest.hasPrograms()) {
throw new IllegalQueryException(new ErrorMessage(E7136));
}

for (String programUid : commonQueryRequest.getProgram()) {
if (!isValidUid(programUid)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
*/
package org.hisp.dhis.analytics.tei;

import static java.util.stream.Collectors.toList;
import static org.apache.commons.collections4.CollectionUtils.isNotEmpty;
import static org.hisp.dhis.analytics.tei.query.TeiFields.getTrackedEntityAttributes;
import static org.hisp.dhis.feedback.ErrorCode.E7125;
import static org.hisp.dhis.feedback.ErrorCode.E7142;

import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -77,24 +77,41 @@ public TeiQueryParams map(QueryRequest<TeiQueryRequest> queryRequest) {
TrackedEntityType trackedEntityType =
getTrackedEntityType(queryRequest.getRequest().getTrackedEntityType());

checkTetForPrograms(queryRequest, trackedEntityType);
boolean programsFromRequest = true;

if (!queryRequest.getCommonQueryRequest().hasPrograms()) {
queryRequest
.getCommonQueryRequest()
.setProgram(getProgramUidsFromTrackedEntityType(trackedEntityType));
programsFromRequest = false;
} else {
checkTetForPrograms(queryRequest, trackedEntityType);
}

// Adding tracked entity type attributes to the list of dimensions.
queryRequest
.getCommonQueryRequest()
.getDimension()
.addAll(
getTrackedEntityAttributes(trackedEntityType)
.map(IdentifiableObject::getUid)
.collect(toList()));
getTrackedEntityAttributes(trackedEntityType).map(IdentifiableObject::getUid).toList());

return teiQueryParamPostProcessor.process(
TeiQueryParams.builder()
.trackedEntityType(trackedEntityType)
.commonParams(commonQueryRequestMapper.map(queryRequest.getCommonQueryRequest()))
.commonParams(
commonQueryRequestMapper
.map(queryRequest.getCommonQueryRequest())
.withProgramsFromRequest(programsFromRequest))
.build());
}

private Set<String> getProgramUidsFromTrackedEntityType(TrackedEntityType trackedEntityType) {
return programService.getAllPrograms().stream()
.filter(program -> matchesTet(program, trackedEntityType))
.map(Program::getUid)
.collect(Collectors.toSet());
}

/**
* Checks if the given programs are valid for the given tracked entity type.
*
Expand Down Expand Up @@ -127,7 +144,8 @@ private void checkTetForPrograms(
* @return true if the program matches the tracked entity type.
*/
private boolean matchesTet(Program program, TrackedEntityType trackedEntityType) {
return StringUtils.equals(program.getTrackedEntityType().getUid(), trackedEntityType.getUid());
return Objects.nonNull(program.getTrackedEntityType())
&& StringUtils.equals(program.getTrackedEntityType().getUid(), trackedEntityType.getUid());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
package org.hisp.dhis.analytics.tei.query.context.querybuilder;

import java.util.List;
import java.util.function.Function;
import java.util.stream.Stream;
import org.hisp.dhis.analytics.common.params.dimension.DimensionIdentifier;
import org.hisp.dhis.analytics.common.params.dimension.DimensionParam;
Expand All @@ -43,13 +44,31 @@
* generated conditions are "ungrouped", since each one needs to be a separate "AND" condition.
*/
@Service
public class ProgramEnrolledQueryBuilder extends SqlQueryBuilderAdaptor {
public class EnrolledInProgramQueryBuilder extends SqlQueryBuilderAdaptor {

private static final String PROGRAM_ENROLLED_GROUP = "PROGRAM_ENROLLED_GROUP";

@Override
protected Stream<GroupableCondition> getWhereClauses(
QueryContext queryContext, List<DimensionIdentifier<DimensionParam>> unused) {

boolean programsFromRequest =
queryContext.getTeiQueryParams().getCommonParams().isProgramsFromRequest();

Function<String, GroupableCondition> conditionMapper =
EnrolledInProgramQueryBuilder::asGroupedEnrolledInProgramCondition;

if (programsFromRequest) {
conditionMapper = EnrolledInProgramQueryBuilder::asUngroupedEnrolledInProgramCondition;
}

return queryContext.getTeiQueryParams().getCommonParams().getPrograms().stream()
.map(IdentifiableObject::getUid)
.map(ProgramEnrolledQueryBuilder::asUngroupedEnrolledInProgramCondition);
.map(conditionMapper);
}

private static GroupableCondition asGroupedEnrolledInProgramCondition(String programUid) {
return GroupableCondition.of(PROGRAM_ENROLLED_GROUP, EnrolledInProgramCondition.of(programUid));
}

private static GroupableCondition asUngroupedEnrolledInProgramCondition(String programUid) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.Set;
import org.hisp.dhis.analytics.common.CommonQueryRequest;
import org.hisp.dhis.analytics.common.QueryRequest;
import org.hisp.dhis.analytics.common.params.CommonParams;
import org.hisp.dhis.analytics.common.processing.CommonQueryRequestMapper;
import org.hisp.dhis.analytics.common.processing.Processor;
import org.hisp.dhis.common.IllegalQueryException;
Expand All @@ -53,11 +55,14 @@ class TeiQueryRequestMapperTest {

private TeiQueryRequestMapper teiQueryRequestMapper;

private CommonQueryRequestMapper commonQueryRequestMapper;

@BeforeEach
public void setUp() {
commonQueryRequestMapper = mock(CommonQueryRequestMapper.class);
teiQueryRequestMapper =
new TeiQueryRequestMapper(
mock(CommonQueryRequestMapper.class),
commonQueryRequestMapper,
trackedEntityTypeService,
mock(Processor.class),
programService);
Expand Down Expand Up @@ -119,6 +124,30 @@ void testOK() {

when(programService.getPrograms(Set.of("A", "B"))).thenReturn(Set.of(programA, programB));

when(commonQueryRequestMapper.map(any())).thenReturn(CommonParams.builder().build());

TeiQueryParams mapped = teiQueryRequestMapper.map(queryRequest);
Dismissed Show dismissed Hide dismissed
}

@Test
void testOKWhenNoPrograms() {
String trackedEntityTypeUid = "T1";

TrackedEntityType trackedEntityType = stubTrackedEntityType(trackedEntityTypeUid);

QueryRequest<TeiQueryRequest> queryRequest =
QueryRequest.<TeiQueryRequest>builder()
.request(new TeiQueryRequest(trackedEntityTypeUid))
.commonQueryRequest(new CommonQueryRequest())
.build();

when(trackedEntityTypeService.getTrackedEntityType(trackedEntityTypeUid))
.thenReturn(trackedEntityType);

when(programService.getPrograms(Set.of("A", "B"))).thenReturn(Set.of());

when(commonQueryRequestMapper.map(any())).thenReturn(CommonParams.builder().build());

TeiQueryParams mapped = teiQueryRequestMapper.map(queryRequest);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,32 +67,6 @@ void testValidateWithSuccess() {
assertDoesNotThrow(() -> teiQueryRequestValidator.validate(queryRequest));
}

@Test
void testValidateWhenProgramIsNotDefined() {
// Given
TeiQueryRequest teiQueryRequest = new TeiQueryRequest("uidabcdef11");
CommonQueryRequest commonQueryRequest = new CommonQueryRequest();
commonQueryRequest.setDimension(Set.of("ou:jmIPBj66vD6"));

QueryRequest<TeiQueryRequest> queryRequest =
QueryRequest.<TeiQueryRequest>builder()
.commonQueryRequest(commonQueryRequest)
.request(teiQueryRequest)
.build();

CommonQueryRequestValidator commonQueryRequestValidator = new CommonQueryRequestValidator();
TeiQueryRequestValidator teiQueryRequestValidator =
new TeiQueryRequestValidator(commonQueryRequestValidator);

// When
IllegalQueryException exception =
assertThrows(
IllegalQueryException.class, () -> teiQueryRequestValidator.validate(queryRequest));

// Then
assertEquals("Program is not specified", exception.getMessage());
}

@Test
void testValidateWhenPeIsDefined() {
// Given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import static org.hisp.dhis.utils.Assertions.assertContains;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.util.List;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -43,11 +44,11 @@
import org.hisp.dhis.analytics.common.params.dimension.ElementWithOffset;
import org.hisp.dhis.analytics.tei.TeiQueryParams;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.DataElementQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.EnrolledInProgramQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.LimitOffsetQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.MainTableQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.OrgUnitQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.PeriodQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.ProgramEnrolledQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.ProgramIndicatorQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.querybuilder.TeiQueryBuilder;
import org.hisp.dhis.analytics.tei.query.context.sql.SqlQueryBuilder;
Expand Down Expand Up @@ -79,7 +80,7 @@ void setUp() {
new MainTableQueryBuilder(),
new OrgUnitQueryBuilder(),
new PeriodQueryBuilder(),
new ProgramEnrolledQueryBuilder(),
new EnrolledInProgramQueryBuilder(),
new TeiQueryBuilder(),
new ProgramIndicatorQueryBuilder(programIndicatorService));
sqlQueryCreatorService = new SqlQueryCreatorService(queryBuilders);
Expand Down Expand Up @@ -125,6 +126,60 @@ void testSqlQueryRenderingWithCommonDimensionalObject() {
"(\"prabcdefghA[1].pgabcdefghS[1]\".\"eventdatavalues\" -> 'abc' ->> 'value')::TEXT as \"prabcdefghA[1].pgabcdefghS[1].abc\""));
}

@Test
void testEnrolledInProgramWhenSpecifiedInRequest() {
// given
CommonParams commonParams =
CommonParams.builder()
.programs(List.of(mockProgram("program1"), mockProgram("program2")))
.build()
.withProgramsFromRequest(true);

TeiQueryParams teiQueryParams =
TeiQueryParams.builder()
.trackedEntityType(createTrackedEntityType('A'))
.commonParams(commonParams)
.build();

// when
String sql =
sqlQueryCreatorService.getSqlQueryCreator(teiQueryParams).createForSelect().getStatement();

// then
assertTrue(sql.contains("ouname"));
assertContains("t_1.\"program1\" and t_1.\"program2\"", sql);
}

@Test
void testEnrolledInProgramWhenNotSpecifiedInRequest() {
// given
CommonParams commonParams =
CommonParams.builder()
.programs(List.of(mockProgram("program1"), mockProgram("program2")))
.build()
.withProgramsFromRequest(false);

TeiQueryParams teiQueryParams =
TeiQueryParams.builder()
.trackedEntityType(createTrackedEntityType('A'))
.commonParams(commonParams)
.build();

// when
String sql =
sqlQueryCreatorService.getSqlQueryCreator(teiQueryParams).createForSelect().getStatement();

// then
assertTrue(sql.contains("ouname"));
assertContains("(t_1.\"program1\" or t_1.\"program2\")", sql);
}

private Program mockProgram(String uid) {
Program program = mock(Program.class);
when(program.getUid()).thenReturn(uid);
return program;
}

private CommonParams stubSortingCommonParams(
Program program, int offset, Object dimensionalObject) {
ElementWithOffset<Program> prg =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
import org.junit.jupiter.api.Test;

/**
* Tests weather or not the {@link ObjectBundleHooks#getObjectHooks(Object)} and {@link
* Tests whether or not the {@link ObjectBundleHooks#getObjectHooks(Object)} and {@link
* ObjectBundleHooks#getTypeImportHooks(Class)} methods assemble the expected lists of {@link
* ObjectBundleHook}s.
*
Expand Down
Loading