=== modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java 2013-01-28 15:55:56 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DataQueryParams.java 2013-01-30 13:46:01 +0000 @@ -34,6 +34,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; +import java.util.ListIterator; import java.util.Map; import org.apache.commons.lang.StringUtils; @@ -53,6 +54,7 @@ @JacksonXmlRootElement( localName = "dxf2", namespace = Dxf2Namespace.NAMESPACE ) public class DataQueryParams { + public static final String DATA_X_DIM_ID = "dx"; // IN, DE, DS public static final String INDICATOR_DIM_ID = "in"; public static final String DATAELEMENT_DIM_ID = "de"; public static final String DATASET_DIM_ID = "ds"; @@ -69,6 +71,8 @@ private static final DimensionOption[] DIM_OPT_ARR = new DimensionOption[0]; private static final DimensionOption[][] DIM_OPT_2D_ARR = new DimensionOption[0][]; + private static final List DATA_DIMS = Arrays.asList( INDICATOR_DIM_ID, DATAELEMENT_DIM_ID, DATASET_DIM_ID ); + private List dimensions = new ArrayList(); private List filters = new ArrayList(); @@ -115,7 +119,34 @@ // ------------------------------------------------------------------------- /** - * Creates a list of the names of all dimensions for this query. + * Creates a list of dimensions for use as headers. Will replace any of + * the indicator, data element or data set dimensions with the common + * data x dimension. + */ + public List getHeaderDimensions() + { + List list = new ArrayList( dimensions ); + + ListIterator iter = list.listIterator(); + + dimensions : while ( iter.hasNext() ) + { + if ( DATA_DIMS.contains( iter.next().getDimension() ) ) + { + iter.set( new Dimension( DATA_X_DIM_ID, DimensionType.DATA_X ) ); + break dimensions; + } + } + + list.remove( new Dimension( INDICATOR_DIM_ID ) ); + list.remove( new Dimension( DATAELEMENT_DIM_ID ) ); + list.remove( new Dimension( DATASET_DIM_ID ) ); + + return list; + } + + /** + * Creates a list of dimensions used to query. */ public List getQueryDimensions() { @@ -137,11 +168,9 @@ /** * Returns the index of the indicator dimension in the dimension map. */ - public int getDataElementOrIndicatorDimensionIndex() + public int getIndicatorDimensionIndex() { - List dims = getInputDimensionNamesAsList(); - - return dims.contains( DATAELEMENT_DIM_ID ) ? dims.indexOf( DATAELEMENT_DIM_ID ) : dims.indexOf( INDICATOR_DIM_ID ); + return getInputDimensionNamesAsList().indexOf( INDICATOR_DIM_ID ); } /** @@ -377,6 +406,9 @@ return valueMap; } + /** + * Retrieves the options for the given dimension. + */ public List getDimensionOptions( String dimension ) { int index = dimensions.indexOf( new Dimension( dimension ) ); @@ -384,6 +416,9 @@ return index != -1 ? dimensions.get( index ).getOptions() : null; } + /** + * Sets the options for the given dimension. + */ public void setDimensionOptions( String dimension, DimensionType type, List options ) { int index = dimensions.indexOf( new Dimension( dimension ) ); @@ -398,6 +433,9 @@ } } + /** + * Retrieves the options for the given filter. + */ public List getFilterOptions( String filter ) { int index = filters.indexOf( new Dimension( filter ) ); @@ -405,6 +443,9 @@ return index != -1 ? filters.get( index ).getOptions() : null; } + /** + * Sets the options for the given filter. + */ public void setFilterOptions( String filter, DimensionType type, List options ) { int index = filters.indexOf( new Dimension( filter ) ); === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/Dimension.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/Dimension.java 2013-01-28 10:55:48 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/Dimension.java 2013-01-30 13:46:01 +0000 @@ -51,6 +51,12 @@ this.dimension = dimension; } + public Dimension( String dimension, DimensionType type ) + { + this.dimension = dimension; + this.type = type; + } + public Dimension( String dimension, DimensionType type, List options ) { this.dimension = dimension; === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DimensionType.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DimensionType.java 2013-01-22 07:59:25 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/DimensionType.java 2013-01-30 13:46:01 +0000 @@ -1,10 +1,38 @@ package org.hisp.dhis.analytics; +/* + * Copyright (c) 2004-2012, University of Oslo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of the HISP project nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + public enum DimensionType { INDICATOR, DATAELEMENT, DATASET, + DATA_X, CATEGORY_OPTION_COMBO, PERIOD, ORGANISATIONUNIT, === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java' --- dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java 2013-01-30 09:01:04 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/main/java/org/hisp/dhis/analytics/data/DefaultAnalyticsService.java 2013-01-30 13:46:01 +0000 @@ -29,9 +29,10 @@ import static org.hisp.dhis.analytics.AnalyticsTableManager.ANALYTICS_TABLE_NAME; import static org.hisp.dhis.analytics.AnalyticsTableManager.COMPLETENESS_TABLE_NAME; +import static org.hisp.dhis.analytics.DataQueryParams.CATEGORYOPTIONCOMBO_DIM_ID; import static org.hisp.dhis.analytics.DataQueryParams.DATAELEMENT_DIM_ID; -import static org.hisp.dhis.analytics.DataQueryParams.CATEGORYOPTIONCOMBO_DIM_ID; import static org.hisp.dhis.analytics.DataQueryParams.DATASET_DIM_ID; +import static org.hisp.dhis.analytics.DataQueryParams.DATA_X_DIM_ID; import static org.hisp.dhis.analytics.DataQueryParams.DIMENSION_SEP; import static org.hisp.dhis.analytics.DataQueryParams.INDICATOR_DIM_ID; import static org.hisp.dhis.analytics.DataQueryParams.ORGUNIT_DIM_ID; @@ -42,12 +43,15 @@ import static org.hisp.dhis.common.IdentifiableObjectUtils.asTypedList; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Future; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.hisp.dhis.analytics.AggregationType; import org.hisp.dhis.analytics.AnalyticsManager; import org.hisp.dhis.analytics.AnalyticsService; @@ -61,9 +65,11 @@ import org.hisp.dhis.common.GridHeader; import org.hisp.dhis.common.IdentifiableObject; import org.hisp.dhis.constant.ConstantService; +import org.hisp.dhis.dataelement.DataElement; import org.hisp.dhis.dataelement.DataElementGroupSet; import org.hisp.dhis.dataelement.DataElementOperand; import org.hisp.dhis.dataelement.DataElementService; +import org.hisp.dhis.dataset.DataSet; import org.hisp.dhis.dataset.DataSetService; import org.hisp.dhis.expression.ExpressionService; import org.hisp.dhis.i18n.I18nFormat; @@ -86,9 +92,12 @@ public class DefaultAnalyticsService implements AnalyticsService { + private static final Log log = LogFactory.getLog( DefaultAnalyticsService.class ); + private static final String VALUE_HEADER_NAME = "Value"; //TODO completeness + //TODO make sure data x dims are successive @Autowired private AnalyticsManager analyticsManager; @@ -123,6 +132,8 @@ public Grid getAggregatedDataValues( DataQueryParams params ) throws Exception { + log.info( "Query: " + params ); + Grid grid = new ListGrid(); // --------------------------------------------------------------------- @@ -131,7 +142,7 @@ grid.setMetaData( params.getUidNameMap() ); - for ( Dimension col : params.getDimensions() ) + for ( Dimension col : params.getHeaderDimensions() ) { grid.addHeader( new GridHeader( col.getDimensionName(), col.getDimension(), String.class.getName(), false, true ) ); } @@ -143,12 +154,15 @@ // --------------------------------------------------------------------- if ( params.getIndicators() != null ) - { - int indicatorIndex = params.getDataElementOrIndicatorDimensionIndex(); - + { + int indicatorIndex = params.getIndicatorDimensionIndex(); List indicators = asTypedList( params.getIndicators() ); + + DataQueryParams dataSourceParams = new DataQueryParams( params ); + dataSourceParams.removeDimension( DATAELEMENT_DIM_ID ); + dataSourceParams.removeDimension( DATASET_DIM_ID ); - DataQueryParams dataSourceParams = setDataElementsFromIndicators( params ); + dataSourceParams = replaceIndicatorsWithDataElements( dataSourceParams, indicatorIndex ); Map aggregatedDataMap = getAggregatedDataValueMap( dataSourceParams, ANALYTICS_TABLE_NAME ); @@ -300,7 +314,7 @@ if ( dimension != null && options != null ) { - params.getDimensions().add( getDimension( dimension, options, format ) ); + params.getDimensions().addAll( getDimension( dimension, options, format ) ); } } } @@ -314,7 +328,7 @@ if ( dimension != null && options != null ) { - params.getFilters().add( getDimension( dimension, options, format ) ); + params.getFilters().addAll( getDimension( dimension, options, format ) ); } } } @@ -331,33 +345,75 @@ // Supportive methods // ------------------------------------------------------------------------- - private Dimension getDimension( String dimension, List options, I18nFormat format ) + private List getDimension( String dimension, List options, I18nFormat format ) { - if ( INDICATOR_DIM_ID.equals( dimension ) ) - { - return new Dimension( dimension, DimensionType.INDICATOR, asList( indicatorService.getIndicatorsByUid( options ) ) ); - } - else if ( DATAELEMENT_DIM_ID.equals( dimension ) ) - { - return new Dimension( dimension, DimensionType.DATAELEMENT, asList( dataElementService.getDataElementsByUid( options ) ) ); - } - else if ( DATASET_DIM_ID.equals( dimension ) ) - { - return new Dimension( dimension, DimensionType.DATASET, asList( dataSetService.getDataSetsByUid( options ) ) ); + if ( DATA_X_DIM_ID.equals( dimension ) ) + { + List dataDimensions = new ArrayList(); + + List indicators = new ArrayList(); + List dataElements = new ArrayList(); + List dataSets = new ArrayList(); + + options : for ( String uid : options ) + { + Indicator in = indicatorService.getIndicator( uid ); + + if ( in != null ) + { + indicators.add( in ); + continue options; + } + + DataElement de = dataElementService.getDataElement( uid ); + + if ( de != null ) + { + dataElements.add( de ); + continue options; + } + + DataSet ds = dataSetService.getDataSet( uid ); + + if ( ds != null ) + { + dataSets.add( ds ); + continue options; + } + + throw new IllegalQueryException( "Data dimension option identifier does not reference any option: " + uid ); + } + + if ( !indicators.isEmpty() ) + { + dataDimensions.add( new Dimension( INDICATOR_DIM_ID, DimensionType.INDICATOR, indicators ) ); + } + + if ( !dataElements.isEmpty() ) + { + dataDimensions.add( new Dimension( DATAELEMENT_DIM_ID, DimensionType.DATAELEMENT, dataElements ) ); + } + + if ( !dataSets.isEmpty() ) + { + dataDimensions.add( new Dimension( DATASET_DIM_ID, DimensionType.DATASET, dataSets ) ); + } + + return dataDimensions; } else if ( CATEGORYOPTIONCOMBO_DIM_ID.equals( dimension ) ) { - return new Dimension( dimension, DimensionType.CATEGORY_OPTION_COMBO, new ArrayList() ); + return Arrays.asList( new Dimension( dimension, DimensionType.CATEGORY_OPTION_COMBO, new ArrayList() ) ); } else if ( ORGUNIT_DIM_ID.equals( dimension ) ) { - return new Dimension( dimension, DimensionType.ORGANISATIONUNIT, asList( organisationUnitService.getOrganisationUnitsByUid( options ) ) ); + return Arrays.asList( new Dimension( dimension, DimensionType.ORGANISATIONUNIT, asList( organisationUnitService.getOrganisationUnitsByUid( options ) ) ) ); } else if ( PERIOD_DIM_ID.equals( dimension ) ) { List list = new ArrayList(); - - periodLoop : for ( String isoPeriod : options ) + + periods : for ( String isoPeriod : options ) { Period period = PeriodType.getPeriodFromIsoString( isoPeriod ); @@ -365,49 +421,49 @@ { period.setName( format != null ? format.formatPeriod( period ) : null ); list.add( period ); - continue periodLoop; + continue periods; } if ( RelativePeriodEnum.contains( isoPeriod ) ) { RelativePeriodEnum relativePeriod = RelativePeriodEnum.valueOf( isoPeriod ); list.addAll( RelativePeriods.getRelativePeriodsFromEnum( relativePeriod, format, true ) ); - continue periodLoop; + continue periods; } } - return new Dimension( dimension, DimensionType.PERIOD, list ); + return Arrays.asList( new Dimension( dimension, DimensionType.PERIOD, list ) ); } OrganisationUnitGroupSet orgUnitGroupSet = organisationUnitGroupService.getOrganisationUnitGroupSet( dimension ); if ( orgUnitGroupSet != null ) { - return new Dimension( dimension, DimensionType.ORGANISATIONUNIT_GROUPSET, asList( organisationUnitGroupService.getOrganisationUnitGroupsByUid( options ) ) ); + List ous = asList( organisationUnitGroupService.getOrganisationUnitGroupsByUid( options ) ); + + return Arrays.asList( new Dimension( dimension, DimensionType.ORGANISATIONUNIT_GROUPSET, ous ) ); } DataElementGroupSet dataElementGroupSet = dataElementService.getDataElementGroupSet( dimension ); if ( dataElementGroupSet != null ) { - return new Dimension( dimension, DimensionType.DATAELEMENT_GROUPSET, asList( dataElementService.getDataElementGroupsByUid( options ) ) ); + List des = asList( dataElementService.getDataElementGroupsByUid( options ) ); + + return Arrays.asList( new Dimension( dimension, DimensionType.DATAELEMENT_GROUPSET, des ) ); } throw new IllegalQueryException( "Dimension identifier does not reference any dimension: " + dimension ); } - private DataQueryParams setDataElementsFromIndicators( DataQueryParams params ) + private DataQueryParams replaceIndicatorsWithDataElements( DataQueryParams params, int indicatorIndex ) { - DataQueryParams immutableParams = new DataQueryParams( params ); - - List indicators = asTypedList( immutableParams.getIndicators() ); + List indicators = asTypedList( params.getIndicators() ); List dataElements = asList( expressionService.getDataElementsInIndicators( indicators ) ); - immutableParams.setDataElements( dataElements ); - immutableParams.removeDimension( INDICATOR_DIM_ID ); - immutableParams.removeDimension( DATASET_DIM_ID ); - immutableParams.enableCategoryOptionCombos(); + params.getDimensions().set( indicatorIndex, new Dimension( DATAELEMENT_DIM_ID, DimensionType.DATAELEMENT, dataElements ) ); + params.enableCategoryOptionCombos(); - return immutableParams; + return params; } } === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsServiceTest.java' --- dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsServiceTest.java 2013-01-28 15:55:56 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/AnalyticsServiceTest.java 2013-01-30 13:46:01 +0000 @@ -123,7 +123,7 @@ public void testGetFromUrl() { Set dimensionParams = new HashSet(); - dimensionParams.add( "de:" + BASE_UID + "A," + BASE_UID + "B," + BASE_UID + "C," + BASE_UID + "D" ); + dimensionParams.add( "dx:" + BASE_UID + "A," + BASE_UID + "B," + BASE_UID + "C," + BASE_UID + "D" ); dimensionParams.add( "pe:2012,2012S1,2012S2" ); dimensionParams.add( ouGroupSetA.getUid() + ":" + ouGroupA.getUid() + "," + ouGroupB.getUid() + "," + ouGroupC.getUid() ); === modified file 'dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerTest.java' --- dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerTest.java 2013-01-28 15:55:56 +0000 +++ dhis-2/dhis-services/dhis-service-analytics/src/test/java/org/hisp/dhis/analytics/data/QueryPlannerTest.java 2013-01-30 13:46:01 +0000 @@ -29,7 +29,7 @@ import static org.hisp.dhis.analytics.DataQueryParams.DIMENSION_SEP; import static org.hisp.dhis.analytics.DataQueryParams.ORGUNIT_DIM_ID; -import static org.hisp.dhis.analytics.DataQueryParams.PERIOD_DIM_ID; +import static org.hisp.dhis.analytics.DataQueryParams.*; import static org.hisp.dhis.common.IdentifiableObjectUtils.getList; import static org.hisp.dhis.analytics.AnalyticsTableManager.ANALYTICS_TABLE_NAME; import static org.junit.Assert.assertEquals; @@ -53,6 +53,8 @@ import org.hisp.dhis.dataelement.DataElementCategoryService; import org.hisp.dhis.dataelement.DataElementOperand; import org.hisp.dhis.dataelement.DataElementService; +import org.hisp.dhis.indicator.Indicator; +import org.hisp.dhis.indicator.IndicatorService; import org.hisp.dhis.organisationunit.OrganisationUnit; import org.hisp.dhis.organisationunit.OrganisationUnitService; import org.hisp.dhis.period.Cal; @@ -77,12 +79,17 @@ private DataElementCategoryService categoryService; @Autowired + private IndicatorService indicatorService; + + @Autowired private OrganisationUnitService organisationUnitService; // ------------------------------------------------------------------------- // Fixture // ------------------------------------------------------------------------- + private Indicator inA; + private DataElement deA; private DataElement deB; private DataElement deC; @@ -99,6 +106,10 @@ @Override public void setUpTest() { + inA = createIndicator( 'A', null ); + + indicatorService.addIndicator( inA ); + deA = createDataElement( 'A' ); deB = createDataElement( 'B' ); deC = createDataElement( 'C' ); @@ -129,6 +140,40 @@ // ------------------------------------------------------------------------- @Test + public void testGetHeaderDimensions() + { + List expected = new ArrayList(); + expected.add( new Dimension( DATA_X_DIM_ID ) ); + expected.add( new Dimension( ORGUNIT_DIM_ID ) ); + expected.add( new Dimension( PERIOD_DIM_ID ) ); + + DataQueryParams params = new DataQueryParams(); + params.setDataElements( getList( deA, deB ) ); + params.setOrganisationUnits( getList( ouA, ouB ) ); + params.setPeriods( getList( createPeriod( "2000Q1" ), createPeriod( "2000Q2" ) ) ); + + assertEquals( expected, params.getHeaderDimensions() ); + + params = new DataQueryParams(); + params.setDataElements( getList( deA, deB ) ); + params.setIndicators( getList( inA ) ); + params.setOrganisationUnits( getList( ouA, ouB ) ); + params.setPeriods( getList( createPeriod( "2000Q1" ), createPeriod( "2000Q2" ) ) ); + + assertEquals( expected, params.getHeaderDimensions() ); + + expected = new ArrayList(); + expected.add( new Dimension( ORGUNIT_DIM_ID ) ); + expected.add( new Dimension( PERIOD_DIM_ID ) ); + + params = new DataQueryParams(); + params.setOrganisationUnits( getList( ouA, ouB ) ); + params.setPeriods( getList( createPeriod( "2000Q1" ), createPeriod( "2000Q2" ) ) ); + + assertEquals( expected, params.getHeaderDimensions() ); + } + + @Test public void testSetGetCopy() { List desA = getList( deA, deB ); === modified file 'dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AnalyticsController.java' --- dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AnalyticsController.java 2013-01-30 08:53:54 +0000 +++ dhis-2/dhis-web/dhis-web-api/src/main/java/org/hisp/dhis/api/controller/AnalyticsController.java 2013-01-30 13:46:01 +0000 @@ -193,6 +193,8 @@ return false; } + //TODO check if any dimension occur more than once + return true; } }