diff --git a/.travis.yml b/.travis.yml index 1ebe4d21..05733cb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,14 +14,6 @@ cache: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ -env: - matrix: - - ANDROID_TARGET=android-22 ANDROID_ABI=armeabi-v7a - global: - # wait up to 10 minutes for adb to connect to emulator - - MALLOC_ARENA_MAX=2 - - ADB_INSTALL_TIMEOUT=20 #increment timeout to 20 mins - android: components: # tools required @@ -35,17 +27,6 @@ android: - extra-google-m2repository - extra-android-m2repository - # Specify at least one system image, - # if you need to run emulator(s) during your tests - - sys-img-armeabi-v7a-android-22 - -before_script: - # Emulator Management: Create, Start and Wait - - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI - - emulator -avd test -no-skin -no-audio -no-window & - - android-wait-for-emulator - - adb shell input keyevent 82 & - script: - echo "Travis branch is $TRAVIS_BRANCH" - echo "Travis branch is in pull request $TRAVIS_PULL+REQUEST" diff --git a/gradle.properties b/gradle.properties index 540e6375..114fb136 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -VERSION_NAME=0.0.8-SNAPSHOT +VERSION_NAME=0.0.9-SNAPSHOT VERSION_CODE=1 GROUP=org.smartregister POM_SETTING_DESCRIPTION=OpenSRP Client Reporting Library diff --git a/opensrp-reporting/src/androidTest/java/org/smartregister/reporting/ExampleInstrumentedTest.java b/opensrp-reporting/src/androidTest/java/org/smartregister/reporting/ExampleInstrumentedTest.java deleted file mode 100644 index 17c995de..00000000 --- a/opensrp-reporting/src/androidTest/java/org/smartregister/reporting/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.smartregister.reporting; - -import android.content.Context; -import android.support.test.InstrumentationRegistry; -import android.support.test.runner.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertEquals; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("org.smartregister.reporting", appContext.getPackageName()); - } -} diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/ReportingLibrary.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/ReportingLibrary.java index 0d8b6a77..66a931af 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/ReportingLibrary.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/ReportingLibrary.java @@ -226,9 +226,11 @@ public void readConfigFile(String configFilePath, SQLiteDatabase sqLiteDatabase) indicatorQuery = new IndicatorQuery(null, indicatorYamlConfigItem.getKey() , indicatorYamlConfigItem.getIndicatorQuery() , 0 - , indicatorYamlConfigItem.isMultiResult()); + , indicatorYamlConfigItem.isMultiResult() + , indicatorYamlConfigItem.getExpectedIndicators()); indicatorQueries.add(indicatorQuery); } + reportIndicators.add(indicator); } } diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/dao/ReportIndicatorDaoImpl.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/dao/ReportIndicatorDaoImpl.java index cb544c20..7229bdb6 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/dao/ReportIndicatorDaoImpl.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/dao/ReportIndicatorDaoImpl.java @@ -2,6 +2,8 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.text.TextUtils; import com.google.gson.Gson; @@ -16,12 +18,12 @@ import org.smartregister.reporting.repository.DailyIndicatorCountRepository; import org.smartregister.reporting.repository.IndicatorQueryRepository; import org.smartregister.reporting.repository.IndicatorRepository; -import org.smartregister.reporting.util.AppProperties; import org.smartregister.repository.EventClientRepository; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.LinkedHashMap; @@ -39,12 +41,12 @@ */ public class ReportIndicatorDaoImpl implements ReportIndicatorDao { - public static final String REPORT_LAST_PROCESSED_DATE = "REPORT_LAST_PROCESSED_DATE"; + public static String DAILY_TALLY_DATE_FORMAT = "yyyy-MM-dd"; + public static String PREVIOUS_REPORT_DATES_QUERY = "select distinct eventDate, " + EventClientRepository.event_column.updatedAt + " from " + EventClientRepository.Table.event.name(); - private static String TAG = ReportIndicatorDaoImpl.class.getCanonicalName(); private static String eventDateFormat = "yyyy-MM-dd HH:mm:ss"; private IndicatorQueryRepository indicatorQueryRepository; private DailyIndicatorCountRepository dailyIndicatorCountRepository; @@ -80,13 +82,19 @@ public void generateDailyIndicatorTallies(String lastProcessedDate) { float count; SQLiteDatabase database = ReportingLibrary.getInstance().getRepository().getWritableDatabase(); - LinkedHashMap reportEventDates = getReportEventDates(lastProcessedDate, database); + Date timeNow = Calendar.getInstance().getTime(); + LinkedHashMap reportEventDates = getReportEventDates(timeNow, lastProcessedDate, database); + Map indicatorQueries = indicatorQueryRepository.getAllIndicatorQueries(); if (!reportEventDates.isEmpty() && !indicatorQueries.isEmpty()) { - String lastUpdatedDate = ""; + String lastUpdatedDate = null; + for (Map.Entry dates : reportEventDates.entrySet()) { - lastUpdatedDate = new SimpleDateFormat(eventDateFormat, Locale.getDefault()).format(dates.getValue()); + if (dates.getValue().getTime() != timeNow.getTime()) { + lastUpdatedDate = new SimpleDateFormat(eventDateFormat, Locale.getDefault()).format(dates.getValue()); + } + for (Map.Entry entry : indicatorQueries.entrySet()) { IndicatorQuery indicatorQuery = entry.getValue(); CompositeIndicatorTally tally = null; @@ -95,10 +103,11 @@ public void generateDailyIndicatorTallies(String lastProcessedDate) { ArrayList result = executeQueryAndReturnMultiResult(indicatorQuery.getQuery(), dates.getKey(), database); // If the size contains actual result other than the column names which are at index 0 - if (result.size() > 1) { + if (result.size() > 1 || (indicatorQuery.getExpectedIndicators() != null && indicatorQuery.getExpectedIndicators().size() > 0)) { tally = new CompositeIndicatorTally(); - tally.setValueSet(new Gson().toJson(result)); tally.setValueSetFlag(true); + tally.setValueSet(new Gson().toJson(result)); + tally.setExpectedIndicators(indicatorQuery.getExpectedIndicators()); } } else { count = executeQueryAndReturnCount(indicatorQuery.getQuery(), dates.getKey(), database); @@ -122,12 +131,17 @@ public void generateDailyIndicatorTallies(String lastProcessedDate) { } } - ReportingLibrary.getInstance().getContext().allSharedPreferences().savePreference(REPORT_LAST_PROCESSED_DATE, lastUpdatedDate); + if (!TextUtils.isEmpty(lastUpdatedDate)) { + ReportingLibrary.getInstance().getContext().allSharedPreferences().savePreference(REPORT_LAST_PROCESSED_DATE, lastUpdatedDate); + } + Timber.i("generateDailyIndicatorTallies: Generate daily tallies complete"); } } - private LinkedHashMap getReportEventDates(String lastProcessedDate, SQLiteDatabase database) { + @VisibleForTesting + @NonNull + protected LinkedHashMap getReportEventDates(@NonNull Date timeNow, @Nullable String lastProcessedDate, @NonNull SQLiteDatabase database) { ArrayList> values; if (lastProcessedDate == null || lastProcessedDate.isEmpty()) { @@ -136,7 +150,7 @@ private LinkedHashMap getReportEventDates(String lastProcessedDate values = dailyIndicatorCountRepository.rawQuery(database, PREVIOUS_REPORT_DATES_QUERY.concat(" where " + EventClientRepository.event_column.updatedAt + " > '" + lastProcessedDate + "'" + " order by eventDate asc")); } - LinkedHashMap result = new LinkedHashMap<>(); + LinkedHashMap reportEventDates = new LinkedHashMap<>(); Date eventDate; Date updateDate; @@ -144,29 +158,31 @@ private LinkedHashMap getReportEventDates(String lastProcessedDate eventDate = formatDate(val.get(EventClientRepository.event_column.eventDate.name()), eventDateFormat); updateDate = formatDate(val.get(EventClientRepository.event_column.updatedAt.name()), eventDateFormat); - String keyDate = new SimpleDateFormat(eventDateFormat, Locale.getDefault()).format(eventDate); + String keyDate = new SimpleDateFormat(DAILY_TALLY_DATE_FORMAT, Locale.getDefault()).format(eventDate); - if (result.get(keyDate) != null && updateDate != null) { - if (result.get(keyDate).getTime() < updateDate.getTime()) { - result.put(keyDate, updateDate); + if (reportEventDates.get(keyDate) != null && updateDate != null) { + if (reportEventDates.get(keyDate).getTime() < updateDate.getTime()) { + reportEventDates.put(keyDate, updateDate); } } else { - result.put(keyDate, updateDate); + reportEventDates.put(keyDate, updateDate); } } - return result; + + String dateToday = new SimpleDateFormat(DAILY_TALLY_DATE_FORMAT, Locale.getDefault()).format(timeNow); + + if (reportEventDates.get(dateToday) == null) { + reportEventDates.put(dateToday, timeNow); + } + + return reportEventDates; } private float executeQueryAndReturnCount(String queryString, String date, SQLiteDatabase database) { // Use date in querying if specified String query = ""; if (date != null) { - if(!ReportingLibrary.getInstance().getAppProperties().hasProperty(AppProperties.KEY.COUNT_INCREMENTAL) - || ReportingLibrary.getInstance().getAppProperties().getPropertyBoolean(AppProperties.KEY.COUNT_INCREMENTAL)) { - query = queryString.contains("%s") ? queryString.replaceAll("%s", date) : queryString; - } else { - query = queryString.contains("%s") ? queryString.replaceAll("%s", date.split(" ")[0]) : queryString; - } + query = queryString.contains("%s") ? queryString.replaceAll("%s", date) : queryString; Timber.i("QUERY : %s", query); } @@ -198,12 +214,13 @@ private float executeQueryAndReturnCount(String queryString, String date, SQLite return count; } + @NonNull private ArrayList executeQueryAndReturnMultiResult(@NonNull String queryString, @Nullable String date, @NonNull SQLiteDatabase database) { // Use date in querying if specified String query = ""; if (date != null) { Timber.i("QUERY : %s", queryString); - query = queryString.contains("'%s'") ? String.format(queryString, date) : queryString; + query = queryString.contains("%s") ? queryString.replaceAll("%s", date) : queryString; } Cursor cursor = null; ArrayList rows = new ArrayList<>(); @@ -254,6 +271,7 @@ private ArrayList executeQueryAndReturnMultiResult(@NonNull String query return rows; } + @Nullable private Date formatDate(String date, String format) { try { return new SimpleDateFormat(format, Locale.getDefault()).parse(date); diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/CompositeIndicatorTally.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/CompositeIndicatorTally.java index ba9675c4..a7ea7f4d 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/CompositeIndicatorTally.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/CompositeIndicatorTally.java @@ -1,8 +1,10 @@ package org.smartregister.reporting.domain; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import java.util.Date; +import java.util.List; /** * Created by Ephraim Kigamba - ekigamba@ona.io on 2019-07-10 @@ -13,6 +15,9 @@ public class CompositeIndicatorTally extends IndicatorTally { private String valueSet; private boolean isValueSet; + @Nullable + private List expectedIndicators; + public CompositeIndicatorTally() { } @@ -42,4 +47,13 @@ public boolean isValueSet() { public void setValueSetFlag(boolean valueSet) { isValueSet = valueSet; } + + @Nullable + public List getExpectedIndicators() { + return expectedIndicators; + } + + public void setExpectedIndicators(@Nullable List expectedIndicators) { + this.expectedIndicators = expectedIndicators; + } } diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/IndicatorQuery.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/IndicatorQuery.java index 6409e5db..7d58fc15 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/IndicatorQuery.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/IndicatorQuery.java @@ -1,18 +1,24 @@ package org.smartregister.reporting.domain; +import android.support.annotation.Nullable; + +import java.util.List; + public class IndicatorQuery { private Long id; private String indicatorCode; private String query; private int dbVersion; private boolean isMultiResult; + private List expectedIndicators; - public IndicatorQuery(Long id, String indicatorCode, String query, int dbVersion, boolean isMultiResult) { + public IndicatorQuery(Long id, String indicatorCode, String query, int dbVersion, boolean isMultiResult, @Nullable List expectedIndicators) { this.id = id; this.indicatorCode = indicatorCode; this.query = query; this.dbVersion = dbVersion; this.isMultiResult = isMultiResult; + this.expectedIndicators = expectedIndicators; } public IndicatorQuery() { @@ -57,4 +63,12 @@ public boolean isMultiResult() { public void setMultiResult(boolean multiResult) { isMultiResult = multiResult; } + + public List getExpectedIndicators() { + return expectedIndicators; + } + + public void setExpectedIndicators(List expectedIndicators) { + this.expectedIndicators = expectedIndicators; + } } diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/IndicatorYamlConfigItem.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/IndicatorYamlConfigItem.java index f87f8188..7096a421 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/IndicatorYamlConfigItem.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/domain/IndicatorYamlConfigItem.java @@ -1,5 +1,7 @@ package org.smartregister.reporting.domain; +import java.util.List; + public class IndicatorYamlConfigItem { public static String INDICATOR_PROPERTY = "indicatorData"; @@ -8,6 +10,8 @@ public class IndicatorYamlConfigItem { private String description; private String indicatorQuery; private boolean isMultiResult; + private List expectedIndicators; + public String getKey() { return key; @@ -40,4 +44,12 @@ public boolean isMultiResult() { public void setMultiResult(boolean multiResult) { isMultiResult = multiResult; } + + public List getExpectedIndicators() { + return expectedIndicators; + } + + public void setExpectedIndicators(List expectedIndicators) { + this.expectedIndicators = expectedIndicators; + } } diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/repository/DailyIndicatorCountRepository.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/repository/DailyIndicatorCountRepository.java index 98cecf14..4b35b670 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/repository/DailyIndicatorCountRepository.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/repository/DailyIndicatorCountRepository.java @@ -3,6 +3,7 @@ import android.content.ContentValues; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -11,12 +12,13 @@ import net.sqlcipher.database.SQLiteDatabase; import org.smartregister.reporting.ReportingLibrary; +import org.smartregister.reporting.dao.ReportIndicatorDaoImpl; import org.smartregister.reporting.domain.CompositeIndicatorTally; import org.smartregister.reporting.domain.IndicatorTally; import org.smartregister.reporting.exception.MultiResultProcessorException; import org.smartregister.reporting.processor.MultiResultProcessor; import org.smartregister.reporting.util.Constants; -import org.smartregister.reporting.util.Utils; +import org.smartregister.reporting.util.ReportingUtils; import org.smartregister.repository.BaseRepository; import org.smartregister.repository.Repository; @@ -58,11 +60,21 @@ public DailyIndicatorCountRepository(Repository repository) { public static void performMigrations(@NonNull SQLiteDatabase database) { // Perform migrations - if (Utils.isTableExists(database, Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE) - && !Utils.isColumnExists(database, Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE + if (ReportingUtils.isTableExists(database, Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE) + && !ReportingUtils.isColumnExists(database, Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET)) { addValueSetColumns(database); - aggregateDailyTallies(database); + } + + if (ReportingUtils.isTableExists(database, Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE)) { + // If there are multiple indicator tallies for a single day the we need to aggregate + ArrayList results = ReportingUtils.performQuery(database, + "SELECT count(*) AS total_count FROM indicator_daily_tally GROUP BY indicator_code" + + ", strftime('%Y-%m-%d', day), indicator_is_value_set ORDER BY total_count DESC LIMIT 1"); + + if (results.size() > 0 & ((int) results.get(1)[0]) > 1) { + aggregateDailyTallies(database); + } } } @@ -78,8 +90,15 @@ public void add(@Nullable CompositeIndicatorTally indicatorTally) { SQLiteDatabase database = getWritableDatabase(); database.delete(Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE - , Constants.DailyIndicatorCountRepository.INDICATOR_CODE + " = ? AND " + Constants.DailyIndicatorCountRepository.DAY + " = ? " - , new String[]{indicatorTally.getIndicatorCode(), new SimpleDateFormat(ReportingLibrary.getInstance().getDateFormat(), Locale.getDefault()).format(indicatorTally.getCreatedAt())}); + , Constants.DailyIndicatorCountRepository.INDICATOR_CODE + " = ? AND " + + Constants.DailyIndicatorCountRepository.DAY + " = ? " + , new String[]{ + indicatorTally.getIndicatorCode(), + new SimpleDateFormat( + ReportIndicatorDaoImpl.DAILY_TALLY_DATE_FORMAT, + Locale.getDefault()).format(indicatorTally.getCreatedAt() + ) + }); database.insert(Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, null, createContentValues(indicatorTally)); } @@ -88,17 +107,24 @@ public List> getAllDailyTallies() { Map tallyMap; SQLiteDatabase database = getReadableDatabase(); - String[] columns = {Constants.DailyIndicatorCountRepository.ID - , Constants.DailyIndicatorCountRepository.INDICATOR_CODE - , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE - , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET - , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET_FLAG - , Constants.DailyIndicatorCountRepository.DAY}; + Object[] queryArgs = { + Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.ID + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_CODE + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_VALUE + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET_FLAG + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.DAY + , Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE, Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE + , Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_CODE + , Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE, Constants.IndicatorQueryRepository.INDICATOR_CODE + }; Cursor cursor = null; try { - cursor = database.query(Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE - , columns, null, null, null, null, null, null); + cursor = database.rawQuery(String.format("SELECT %s.%s, %s.%s, %s.%s, %s.%s, %s.%s, %s.%s, %s.%s FROM %s INNER JOIN %s ON %s.%s = %s.%s", queryArgs) + , null); if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) { MultiResultProcessor defaultMultiResultProcessor = ReportingLibrary.getInstance().getDefaultMultiResultProcessor(); ArrayList multiResultProcessors = ReportingLibrary.getInstance().getMultiResultProcessors(); @@ -162,6 +188,11 @@ private void extractIndicatorTalliesFromMultiResult(@NonNull Map 0) { + uncondensedTallies = extractOnlyRequiredIndicatorTalliesAndProvideDefault(compositeIndicatorTally, uncondensedTallies); + } + if (uncondensedTallies != null) { for (IndicatorTally indicatorTally: uncondensedTallies) { tallyMap.put(indicatorTally.getIndicatorCode(), indicatorTally); @@ -169,11 +200,60 @@ private void extractIndicatorTalliesFromMultiResult(@NonNull Map extractOnlyRequiredIndicatorTalliesAndProvideDefault( + @NonNull CompositeIndicatorTally compositeIndicatorTally, + @Nullable List uncondensedTallies) { + + List expectedIndicators = compositeIndicatorTally.getExpectedIndicators(); + + if (uncondensedTallies == null) { + List tallies = new ArrayList<>(); + + for (String expectedIndicatorCode: expectedIndicators) { + IndicatorTally indicatorTally = new IndicatorTally(); + indicatorTally.setCount(0F); + indicatorTally.setIndicatorCode(expectedIndicatorCode); + indicatorTally.setId(compositeIndicatorTally.getId()); + indicatorTally.setCreatedAt(compositeIndicatorTally.getCreatedAt()); + + tallies.add(indicatorTally); + } + + return tallies; + } else { + + List tallies = new ArrayList<>(); + HashMap indicatorTallyHashMap = new HashMap<>(); + + for (IndicatorTally indicatorTally: uncondensedTallies) { + indicatorTallyHashMap.put(indicatorTally.getIndicatorCode(), indicatorTally); + } + + for (String expectedIndicatorCode: expectedIndicators) { + if (indicatorTallyHashMap.containsKey(expectedIndicatorCode)) { + tallies.add(indicatorTallyHashMap.get(expectedIndicatorCode)); + } else { + IndicatorTally indicatorTally = new IndicatorTally(); + indicatorTally.setCount(0F); + indicatorTally.setIndicatorCode(expectedIndicatorCode); + indicatorTally.setId(compositeIndicatorTally.getId()); + indicatorTally.setCreatedAt(compositeIndicatorTally.getCreatedAt()); + + tallies.add(indicatorTally); + } + } + + return tallies; + } + } + @Nullable - private List processMultipleTallies(@NonNull MultiResultProcessor defaultMultiResultProcessor + public static List processMultipleTallies(@NonNull MultiResultProcessor multiResultProcessor , @NonNull CompositeIndicatorTally compositeIndicatorTally) { try { - return defaultMultiResultProcessor.processMultiResultTally(compositeIndicatorTally); + return multiResultProcessor.processMultiResultTally(compositeIndicatorTally); } catch (MultiResultProcessorException ex) { Timber.e(ex); return null; @@ -184,12 +264,22 @@ public Map getDailyTallies(@NonNull Date date) { Map tallyMap = new HashMap<>(); SQLiteDatabase database = getReadableDatabase(); - String[] columns = {Constants.DailyIndicatorCountRepository.ID - , Constants.DailyIndicatorCountRepository.INDICATOR_CODE - , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE - , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET - , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET_FLAG - , Constants.DailyIndicatorCountRepository.DAY}; + + Object[] queryArgs = { + Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.ID + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_CODE + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_VALUE + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET_FLAG + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.DAY + , Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE, Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE + , Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.INDICATOR_CODE + , Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE, Constants.IndicatorQueryRepository.INDICATOR_CODE + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.DAY + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE, Constants.DailyIndicatorCountRepository.DAY + }; Cursor cursor = null; @@ -206,11 +296,10 @@ public Map getDailyTallies(@NonNull Date date) { long dayEndMillis = dayStart.getTimeInMillis(); try { - cursor = database.query(Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE - , columns - , Constants.DailyIndicatorCountRepository.DAY + " >= ? AND " + Constants.DailyIndicatorCountRepository.DAY + " < ?", - new String[]{String.valueOf(dayStartMillis), String.valueOf(dayEndMillis)} - , null,null, null, null); + cursor = database.rawQuery(String.format( + "SELECT %s.%s, %s.%s, %s.%s, %s.%s, %s.%s, %s.%s, %s.%s FROM %s INNER JOIN %s ON %s.%s = %s.%s WHERE %s.%s >= ? %s.%s AND < ?" + , queryArgs), + new String[]{String.valueOf(dayStartMillis), String.valueOf(dayEndMillis)}); if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) { while (!cursor.isAfterLast()) { CompositeIndicatorTally compositeIndicatorTally = processCursorRow(cursor); @@ -240,13 +329,21 @@ private CompositeIndicatorTally processCursorRow(@NonNull Cursor cursor) { compositeIndicatorTally.setCount(cursor.getFloat(cursor.getColumnIndex(Constants.DailyIndicatorCountRepository.INDICATOR_VALUE))); } + if (cursor.getColumnIndex(Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS) != -1) { + compositeIndicatorTally.setExpectedIndicators( + (List) new Gson().fromJson(cursor.getString(cursor.getColumnIndex( + Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS)) + , new TypeToken>() {}.getType()) + ); + } + compositeIndicatorTally.setCreatedAt(new Date(cursor.getLong(cursor.getColumnIndex(Constants.DailyIndicatorCountRepository.DAY)))); return compositeIndicatorTally; } public ContentValues createContentValues(@NonNull CompositeIndicatorTally compositeIndicatorTally) { ContentValues values = new ContentValues(); - SimpleDateFormat dateFormat = new SimpleDateFormat(ReportingLibrary.getInstance().getDateFormat(), Locale.getDefault()); + SimpleDateFormat dateFormat = new SimpleDateFormat(ReportIndicatorDaoImpl.DAILY_TALLY_DATE_FORMAT, Locale.getDefault()); values.put(Constants.DailyIndicatorCountRepository.INDICATOR_CODE, compositeIndicatorTally.getIndicatorCode()); if (compositeIndicatorTally.isValueSet()) { @@ -292,6 +389,38 @@ public static void addValueSetColumns(@NonNull SQLiteDatabase database) { public static void aggregateDailyTallies(@NonNull SQLiteDatabase database) { // Code to migrate the code over from incremental tallies should be written here + database.execSQL("PRAGMA foreign_keys=off"); + database.beginTransaction(); + + // Read all the indicator counts & clear the table + + database.execSQL("CREATE TABLE indicator_daily_tally_old(_id INTEGER NOT NULL PRIMARY KEY, indicator_code TEXT NOT NULL, indicator_value INTEGER, indicator_value_set TEXT, indicator_is_value_set BOOLEAN NOT NULL default 0, day DATETIME NOT NULL DEFAULT (DATETIME('now')))"); + database.execSQL("INSERT INTO indicator_daily_tally_old SELECT * FROM indicator_daily_tally"); + + // Delete the old values + database.execSQL("DELETE FROM indicator_daily_tally"); + database.execSQL(String.format("INSERT INTO %s(%s, %s, %s, %s) SELECT %s, sum(%s), %s, strftime('%%Y-%%m-%%d', %s) FROM %s GROUP BY %s, strftime('%%Y-%%m-%%d', %s), %s" + , Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE + , Constants.DailyIndicatorCountRepository.INDICATOR_CODE + , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE + , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET_FLAG + , Constants.DailyIndicatorCountRepository.DAY + // SELECT args START HERE + , Constants.DailyIndicatorCountRepository.INDICATOR_CODE + , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE + , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET_FLAG + , Constants.DailyIndicatorCountRepository.DAY + , "indicator_daily_tally_old" + // GROUP BY args START HERE + , Constants.DailyIndicatorCountRepository.INDICATOR_CODE + , Constants.DailyIndicatorCountRepository.DAY + , Constants.DailyIndicatorCountRepository.INDICATOR_VALUE_SET_FLAG)); + + database.execSQL("DROP TABLE indicator_daily_tally_old"); + + database.setTransactionSuccessful(); + database.endTransaction(); + database.execSQL("PRAGMA foreign_keys=on;"); } } diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/repository/IndicatorQueryRepository.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/repository/IndicatorQueryRepository.java index ca9de882..b1523f65 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/repository/IndicatorQueryRepository.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/repository/IndicatorQueryRepository.java @@ -3,17 +3,22 @@ import android.content.ContentValues; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; import net.sqlcipher.Cursor; import net.sqlcipher.database.SQLiteDatabase; import org.smartregister.reporting.domain.IndicatorQuery; import org.smartregister.reporting.util.Constants; -import org.smartregister.reporting.util.Utils; +import org.smartregister.reporting.util.ReportingUtils; import org.smartregister.repository.BaseRepository; import org.smartregister.repository.Repository; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -28,6 +33,7 @@ public class IndicatorQueryRepository extends BaseRepository { + "(" + Constants.IndicatorQueryRepository.ID + " INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " + Constants.IndicatorQueryRepository.QUERY + " TEXT NOT NULL, " + Constants.IndicatorQueryRepository.INDICATOR_CODE + " TEXT NOT NULL, " + Constants.IndicatorQueryRepository.INDICATOR_QUERY_IS_MULTI_RESULT + " BOOLEAN NOT NULL DEFAULT 0, " + + Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS + " TEXT, " + Constants.IndicatorQueryRepository.DB_VERSION + " INTEGER)"; public IndicatorQueryRepository(Repository repository) { @@ -36,10 +42,15 @@ public IndicatorQueryRepository(Repository repository) { public static void performMigrations(@NonNull SQLiteDatabase database) { // Perform migrations - if (Utils.isTableExists(database, Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE) - && !Utils.isColumnExists(database, Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE, Constants.IndicatorQueryRepository.INDICATOR_QUERY_IS_MULTI_RESULT)) { + if (ReportingUtils.isTableExists(database, Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE) + && !ReportingUtils.isColumnExists(database, Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE, Constants.IndicatorQueryRepository.INDICATOR_QUERY_IS_MULTI_RESULT)) { addMultiResultFlagField(database); - aggregateDailyTallies(database); + } + + if (ReportingUtils.isTableExists(database, Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE) + && !ReportingUtils.isColumnExists(database, Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE + , Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS)) { + addExpectedIndicatorColumn(database); } } @@ -73,7 +84,7 @@ public Map getAllIndicatorQueries() { SQLiteDatabase database = getReadableDatabase(); String[] columns = {Constants.IndicatorQueryRepository.ID, Constants.IndicatorQueryRepository.INDICATOR_CODE , Constants.IndicatorQueryRepository.QUERY, Constants.IndicatorQueryRepository.INDICATOR_QUERY_IS_MULTI_RESULT - , Constants.IndicatorQueryRepository.DB_VERSION}; + , Constants.IndicatorQueryRepository.DB_VERSION, Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS}; Cursor cursor = database.query(Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE, columns , null, null, null, null, null, null); if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) { @@ -92,7 +103,7 @@ public IndicatorQuery findQueryByIndicatorCode(String indicatorCode) { IndicatorQuery indicatorQuery = null; String[] columns = {Constants.IndicatorQueryRepository.ID, Constants.IndicatorQueryRepository.INDICATOR_CODE , Constants.IndicatorQueryRepository.QUERY, Constants.IndicatorQueryRepository.INDICATOR_QUERY_IS_MULTI_RESULT - , Constants.IndicatorQueryRepository.DB_VERSION}; + , Constants.IndicatorQueryRepository.DB_VERSION, Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS}; String selection = Constants.IndicatorQueryRepository.QUERY + " = ?"; String[] selectionArgs = {indicatorCode}; @@ -116,6 +127,9 @@ public ContentValues createContentValues(IndicatorQuery indicatorQuery) { values.put(Constants.IndicatorQueryRepository.INDICATOR_CODE, indicatorQuery.getIndicatorCode()); values.put(Constants.IndicatorQueryRepository.DB_VERSION, indicatorQuery.getDbVersion()); values.put(Constants.IndicatorQueryRepository.INDICATOR_QUERY_IS_MULTI_RESULT, indicatorQuery.isMultiResult()); + values.put(Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS, + indicatorQuery.getExpectedIndicators() != null ? + new Gson().toJson(indicatorQuery.getExpectedIndicators()) : null); return values; } @@ -127,6 +141,11 @@ private IndicatorQuery processCursorRow(@NonNull Cursor cursor) { indicatorQuery.setMultiResult(cursor.getInt(cursor.getColumnIndex(Constants.IndicatorQueryRepository.INDICATOR_QUERY_IS_MULTI_RESULT)) == 1); indicatorQuery.setDbVersion(cursor.getInt(cursor.getColumnIndex(Constants.IndicatorQueryRepository.DB_VERSION))); + String expectedResults = cursor.getString(cursor.getColumnIndex(Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS)); + + indicatorQuery.setExpectedIndicators(TextUtils.isEmpty(expectedResults) ? null : + (List) new Gson().fromJson(expectedResults, new TypeToken>() {}.getType())); + return indicatorQuery; } @@ -148,8 +167,15 @@ public static void addMultiResultFlagField(@NonNull SQLiteDatabase database) { database.execSQL("PRAGMA foreign_keys=on;"); } - public static void aggregateDailyTallies(@NonNull SQLiteDatabase database) { - // Code to migrate the code over from incremental tallies should be written here + public static void addExpectedIndicatorColumn(@NonNull SQLiteDatabase database) { + database.execSQL("PRAGMA foreign_keys=off"); + database.beginTransaction(); + + database.execSQL("ALTER TABLE " + Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE + + " ADD COLUMN " + Constants.IndicatorQueryRepository.INDICATOR_QUERY_EXPECTED_INDICATORS + " TEXT"); + database.setTransactionSuccessful(); + database.endTransaction(); + database.execSQL("PRAGMA foreign_keys=on;"); } } diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/util/Constants.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/util/Constants.java index da589a5d..d82a0101 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/util/Constants.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/util/Constants.java @@ -31,5 +31,6 @@ interface IndicatorQueryRepository { String DB_VERSION = "db_version"; String INDICATOR_QUERY_TABLE = "indicator_queries"; String INDICATOR_QUERY_IS_MULTI_RESULT = "indicator_is_multi_result"; + String INDICATOR_QUERY_EXPECTED_INDICATORS = "expected_indicators"; } } diff --git a/opensrp-reporting/src/main/java/org/smartregister/reporting/util/Utils.java b/opensrp-reporting/src/main/java/org/smartregister/reporting/util/ReportingUtils.java similarity index 63% rename from opensrp-reporting/src/main/java/org/smartregister/reporting/util/Utils.java rename to opensrp-reporting/src/main/java/org/smartregister/reporting/util/ReportingUtils.java index f03c1da6..9d313de1 100644 --- a/opensrp-reporting/src/main/java/org/smartregister/reporting/util/Utils.java +++ b/opensrp-reporting/src/main/java/org/smartregister/reporting/util/ReportingUtils.java @@ -5,13 +5,13 @@ import net.sqlcipher.Cursor; import net.sqlcipher.database.SQLiteDatabase; +import java.util.ArrayList; + /** * Created by Ephraim Kigamba - ekigamba@ona.io on 2019-07-09 */ -public class Utils { - - public static final String TAG = Utils.class.getName(); +public class ReportingUtils { /** * Checks if a column exists on the table. An {@link Exception} is expected to be thrown by the sqlite @@ -39,10 +39,13 @@ public static boolean isColumnExists(@NonNull SQLiteDatabase sqliteDatabase, String name = cursor.getString(nameColumnIndex); if (name.equals(columnToFind)) { + cursor.close(); return true; } } + cursor.close(); + return false; } @@ -68,10 +71,48 @@ public static boolean isTableExists(@NonNull SQLiteDatabase sqliteDatabase, @Non String name = cursor.getString(nameColumnIndex); if (name.equals(tableName)) { + cursor.close(); return true; } } + cursor.close(); + return false; } + + public static ArrayList performQuery(@NonNull SQLiteDatabase sqliteDatabase, @NonNull String query) { + ArrayList rows = new ArrayList<>(); + Cursor cursor = sqliteDatabase.rawQuery(query,null); + if (null != cursor) { + int cols = cursor.getColumnCount(); + rows.add(cursor.getColumnNames()); + while (cursor.moveToNext()) { + Object[] col = new Object[cols]; + + for (int i = 0; i < cols; i++) { + int type = cursor.getType(i); + Object cellValue = null; + + if (type == Cursor.FIELD_TYPE_FLOAT) { + cellValue = (Float) cursor.getFloat(i); + } else if (type == Cursor.FIELD_TYPE_INTEGER) { + cellValue = (Integer) cursor.getInt(i); + } else if (type == Cursor.FIELD_TYPE_STRING) { + cellValue = (String) cursor.getString(i); + } + + // Types BLOB and NULL are ignored + // Blob is not supposed to a reporting result & NULL is already defined in the cellValue at the top + col[i] = cellValue; + } + + rows.add(col); + } + + cursor.close(); + } + + return rows; + } } diff --git a/opensrp-reporting/src/test/java/org/smartregister/reporting/dao/DaoTest.java b/opensrp-reporting/src/test/java/org/smartregister/reporting/dao/DaoTest.java index 0e0846cf..cb4013a4 100644 --- a/opensrp-reporting/src/test/java/org/smartregister/reporting/dao/DaoTest.java +++ b/opensrp-reporting/src/test/java/org/smartregister/reporting/dao/DaoTest.java @@ -21,7 +21,6 @@ import org.smartregister.reporting.repository.DailyIndicatorCountRepository; import org.smartregister.reporting.repository.IndicatorQueryRepository; import org.smartregister.reporting.repository.IndicatorRepository; -import org.smartregister.reporting.util.AppProperties; import org.smartregister.repository.AllSharedPreferences; import org.smartregister.repository.Repository; @@ -62,9 +61,6 @@ public class DaoTest { private ReportIndicatorDaoImpl daoSpy; - @Mock - private AppProperties appProperties; - @Before public void setUp() { MockitoAnnotations.initMocks(this); @@ -103,7 +99,7 @@ public void generateDailyIndicatorTalliesSetsLastProcessedDatePreference() throw reportEventDates.put("20190513", new SimpleDateFormat("yyyy-MM-dd hh:mm:ss", Locale.getDefault()).parse("2019-05-13 12:19:37")); Map indicatorQueries = new HashMap<>(); - indicatorQueries.put("INDI-100", new IndicatorQuery(1L, "INDI-100", "select count(*) from table", 4, false)); + indicatorQueries.put("INDI-100", new IndicatorQuery(1L, "INDI-100", "select count(*) from table", 4, false, null)); PowerMockito.mockStatic(ReportingLibrary.class); @@ -111,12 +107,9 @@ public void generateDailyIndicatorTalliesSetsLastProcessedDatePreference() throw Mockito.when(reportingLibrary.getRepository()).thenReturn(repository); Mockito.when(reportingLibrary.getContext()).thenReturn(context); Mockito.when(repository.getWritableDatabase()).thenReturn(sqLiteDatabase); - PowerMockito.doReturn(reportEventDates).when(daoSpy, "getReportEventDates", ArgumentMatchers.anyString(), ArgumentMatchers.any(SQLiteDatabase.class)); + PowerMockito.doReturn(reportEventDates).when(daoSpy, "getReportEventDates", ArgumentMatchers.any(Date.class), ArgumentMatchers.anyString(), ArgumentMatchers.any(SQLiteDatabase.class)); Mockito.when(indicatorQueryRepository.getAllIndicatorQueries()).thenReturn(indicatorQueries); Mockito.when(context.allSharedPreferences()).thenReturn(sharedPreferences); - PowerMockito.when(reportingLibrary.getAppProperties()).thenReturn(appProperties); - PowerMockito.when(appProperties.hasProperty("reporting.incremental")).thenReturn(true); - PowerMockito.when(appProperties.getPropertyBoolean("reporting.incremental")).thenReturn(true); daoSpy.generateDailyIndicatorTallies(lastProcessedDate); Mockito.verify(sharedPreferences, Mockito.times(1)).savePreference(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()); diff --git a/opensrp-reporting/src/test/java/org/smartregister/reporting/dao/ReportIndicatorDaoImplTest.java b/opensrp-reporting/src/test/java/org/smartregister/reporting/dao/ReportIndicatorDaoImplTest.java index 84123380..b386e3fc 100644 --- a/opensrp-reporting/src/test/java/org/smartregister/reporting/dao/ReportIndicatorDaoImplTest.java +++ b/opensrp-reporting/src/test/java/org/smartregister/reporting/dao/ReportIndicatorDaoImplTest.java @@ -18,11 +18,16 @@ import org.smartregister.reporting.repository.DailyIndicatorCountRepository; import org.smartregister.reporting.repository.IndicatorQueryRepository; import org.smartregister.reporting.repository.IndicatorRepository; -import org.smartregister.reporting.util.AppProperties; import org.smartregister.repository.Repository; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; + /** - * Created by Ephraim Kigamba - ekigamba@ona.io on 2019-08-15 + * Created by Ephraim Kigamba - ekigamba@ona.io on 2019-08-14 */ @RunWith(MockitoJUnitRunner.class) @@ -59,10 +64,6 @@ public void executeQueryAndReturnCountShouldReturnCountFromQuery() { ReflectionHelpers.setStaticField(ReportingLibrary.class, "instance", reportingLibrarySpy); Mockito.doReturn(matrixCursor).when(database).rawQuery(Mockito.anyString(), Mockito.isNull(String[].class)); - AppProperties appProperties = Mockito.mock(AppProperties.class); - Mockito.doReturn(appProperties).when(reportingLibrarySpy).getAppProperties(); - Mockito.doReturn(false).when(appProperties).hasProperty(Mockito.anyString()); - Float actualResult = ReflectionHelpers.callInstanceMethod(reportIndicatorDao, "executeQueryAndReturnCount" , ReflectionHelpers.ClassParameter.from(String.class, query) , ReflectionHelpers.ClassParameter.from(String.class, date) @@ -71,4 +72,16 @@ public void executeQueryAndReturnCountShouldReturnCountFromQuery() { Assert.assertEquals(67F, actualResult, 0); } + public void getReportEventDatesShouldReturnCurrentDateWhenNoDatesRetrievedFromEventTable() { + Date timeNow = Calendar.getInstance().getTime(); + SQLiteDatabase database = Mockito.mock(SQLiteDatabase.class); + + Mockito.doReturn(new ArrayList>()) + .when(dailyIndicatorCountRepository) + .rawQuery(Mockito.any(SQLiteDatabase.class), Mockito.anyString()); + + LinkedHashMap reportEventDates = reportIndicatorDao.getReportEventDates(timeNow, null, database); + + Assert.assertEquals(1, reportEventDates.size()); + } } \ No newline at end of file diff --git a/opensrp-reporting/src/test/java/org/smartregister/reporting/repository/DailyIndicatorCountRepositoryTest.java b/opensrp-reporting/src/test/java/org/smartregister/reporting/repository/DailyIndicatorCountRepositoryTest.java index 52ce1d3f..4f38c167 100644 --- a/opensrp-reporting/src/test/java/org/smartregister/reporting/repository/DailyIndicatorCountRepositoryTest.java +++ b/opensrp-reporting/src/test/java/org/smartregister/reporting/repository/DailyIndicatorCountRepositoryTest.java @@ -23,11 +23,17 @@ import org.smartregister.reporting.ReportingLibrary; import org.smartregister.reporting.domain.CompositeIndicatorTally; import org.smartregister.reporting.domain.IndicatorTally; +import org.smartregister.reporting.processor.DefaultMultiResultProcessor; +import org.smartregister.reporting.processor.MultiResultProcessor; import org.smartregister.reporting.util.Constants; import org.smartregister.repository.Repository; import java.sql.Date; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; @@ -70,8 +76,7 @@ public void addIndicatorTallyInvokesWritableDBInsert() throws Exception { public void getAllDailyTalliesInvokesReadableDBQuery() { Mockito.when(dailyIndicatorCountRepositorySpy.getReadableDatabase()).thenReturn(sqLiteDatabase); dailyIndicatorCountRepositorySpy.getAllDailyTallies(); - Mockito.verify(sqLiteDatabase, Mockito.times(1)).query(ArgumentMatchers.anyString(), ArgumentMatchers.any(String[].class), - ArgumentMatchers.isNull(String.class), ArgumentMatchers.isNull(String[].class), ArgumentMatchers.isNull(String.class), ArgumentMatchers.isNull(String.class), ArgumentMatchers.isNull(String.class), ArgumentMatchers.isNull(String.class)); + Mockito.verify(sqLiteDatabase, Mockito.times(1)).rawQuery(ArgumentMatchers.anyString(), ArgumentMatchers.isNull(String[].class)); } @Test @@ -103,14 +108,8 @@ public void getDailyTalliesShouldInvokeQueryAndReturnEmptyHashMap() { Map tallyMap = dailyIndicatorCountRepositorySpy.getDailyTallies(new Date(System.currentTimeMillis())); Mockito.verify(sqLiteDatabase, Mockito.times(1)) - .query(ArgumentMatchers.eq(Constants.DailyIndicatorCountRepository.INDICATOR_DAILY_TALLY_TABLE) - , MockitoHamcrest.argThat(IsArrayWithSize.arrayWithSize(6)) - , ArgumentMatchers.eq(Constants.DailyIndicatorCountRepository.DAY + " >= ? AND " + Constants.DailyIndicatorCountRepository.DAY + " < ?") - , MockitoHamcrest.argThat(IsArrayWithSize.arrayWithSize(2)) - , ArgumentMatchers.isNull(String.class) - , ArgumentMatchers.isNull(String.class) - , ArgumentMatchers.isNull(String.class) - , ArgumentMatchers.isNull(String.class)); + .rawQuery(ArgumentMatchers.anyString() + , MockitoHamcrest.argThat(IsArrayWithSize.arrayWithSize(2))); Assert.assertEquals(0, tallyMap.size()); } @@ -166,4 +165,89 @@ public void processCursorRowShouldReturnValueSetWhenCursorIsValueSetRow() { Assert.assertEquals(indicatorCode, indicatorTally.getIndicatorCode()); Assert.assertEquals(id, indicatorTally.getId().longValue()); } + + @Test + public void extractOnlyRequiredIndicatorTalliesAndProvideDefaultShouldReturnZeroValuesForUnlocatedIndicatorsWhenGivenExpectedIndicatorsAndUnwantedIndicators() { + java.util.Date timeNow = Calendar.getInstance().getTime(); + + List indicatorTally = new ArrayList<>(); + indicatorTally.add(new IndicatorTally(0L, 54, "ISN_OPV", timeNow)); + indicatorTally.add(new IndicatorTally(0L, 6, "ISN_HEPB", timeNow)); + indicatorTally.add(new IndicatorTally(0L, 9, "ISN_PCV", timeNow)); + indicatorTally.add(new IndicatorTally(0L, 5, "ISN_ROTA", timeNow)); + + ArrayList expectedIndicators = new ArrayList<>(); + expectedIndicators.add("ISN_BCG"); + expectedIndicators.add("ISN_OPV"); + expectedIndicators.add("ISN_PENTA"); + + CompositeIndicatorTally compositeIndicatorTally = new CompositeIndicatorTally(); + compositeIndicatorTally.setValueSetFlag(true); + compositeIndicatorTally.setExpectedIndicators(expectedIndicators); + compositeIndicatorTally.setIndicatorCode("ISN"); + compositeIndicatorTally.setCreatedAt(timeNow); + + List finalTallies = dailyIndicatorCountRepositorySpy.extractOnlyRequiredIndicatorTalliesAndProvideDefault(compositeIndicatorTally, indicatorTally); + + Assert.assertEquals(3, finalTallies.size()); + Assert.assertEquals("ISN_BCG", finalTallies.get(0).getIndicatorCode()); + Assert.assertEquals("ISN_OPV", finalTallies.get(1).getIndicatorCode()); + Assert.assertEquals("ISN_PENTA", finalTallies.get(2).getIndicatorCode()); + + Assert.assertEquals(0, finalTallies.get(0).getCount()); + Assert.assertEquals(54, finalTallies.get(1).getCount()); + Assert.assertEquals(0, finalTallies.get(2).getCount()); + } + + @Test + public void extractOnlyRequiredIndicatorTalliesAndProvideDefaultShouldReturnZeroValuesForAllUnlocatedIndicatorsWhenGivenNull() { + java.util.Date timeNow = Calendar.getInstance().getTime(); + + ArrayList expectedIndicators = new ArrayList<>(); + expectedIndicators.add("ISN_BCG"); + expectedIndicators.add("ISN_OPV"); + expectedIndicators.add("ISN_PENTA"); + + CompositeIndicatorTally compositeIndicatorTally = new CompositeIndicatorTally(); + compositeIndicatorTally.setValueSetFlag(true); + compositeIndicatorTally.setExpectedIndicators(expectedIndicators); + compositeIndicatorTally.setIndicatorCode("ISN"); + compositeIndicatorTally.setCreatedAt(timeNow); + + List finalTallies = dailyIndicatorCountRepositorySpy.extractOnlyRequiredIndicatorTalliesAndProvideDefault(compositeIndicatorTally, null); + + Assert.assertEquals(3, finalTallies.size()); + Assert.assertEquals("ISN_BCG", finalTallies.get(0).getIndicatorCode()); + Assert.assertEquals("ISN_OPV", finalTallies.get(1).getIndicatorCode()); + Assert.assertEquals("ISN_PENTA", finalTallies.get(2).getIndicatorCode()); + + Assert.assertEquals(0, finalTallies.get(0).getCount()); + Assert.assertEquals(0, finalTallies.get(1).getCount()); + Assert.assertEquals(0, finalTallies.get(2).getCount()); + } + + @Test + public void extractIndicatorTalliesFromMultiResultShouldAddSingleIndicatorTalliesToMapWhenGivenCompositeIndicatorTally() { + + DefaultMultiResultProcessor defaultMultiResultProcessor = new DefaultMultiResultProcessor(); + ArrayList multiResultProcessors = new ArrayList<>(); + CompositeIndicatorTally compositeIndicatorTally = new CompositeIndicatorTally(); + compositeIndicatorTally.setIndicatorCode("ISN"); + compositeIndicatorTally.setCreatedAt(Calendar.getInstance().getTime()); + compositeIndicatorTally.setValueSetFlag(true); + compositeIndicatorTally.setValueSet("[['gender', 'count'], ['Male', 98.56], ['Female', 89]]"); + + + HashMap tallyMap = new HashMap<>(); + + ReflectionHelpers.callInstanceMethod(dailyIndicatorCountRepositorySpy, "extractIndicatorTalliesFromMultiResult" + , ReflectionHelpers.ClassParameter.from(Map.class, tallyMap) + , ReflectionHelpers.ClassParameter.from(MultiResultProcessor.class, defaultMultiResultProcessor) + , ReflectionHelpers.ClassParameter.from(ArrayList.class, multiResultProcessors) + , ReflectionHelpers.ClassParameter.from(CompositeIndicatorTally.class, compositeIndicatorTally)); + + Assert.assertEquals(2, tallyMap.size()); + Assert.assertTrue(tallyMap.containsKey("ISN_Female")); + Assert.assertTrue(tallyMap.containsKey("ISN_Male")); + } } diff --git a/opensrp-reporting/src/test/java/org/smartregister/reporting/repository/IndicatorQueryRepositoryTest.java b/opensrp-reporting/src/test/java/org/smartregister/reporting/repository/IndicatorQueryRepositoryTest.java index 6590915b..446e47e3 100644 --- a/opensrp-reporting/src/test/java/org/smartregister/reporting/repository/IndicatorQueryRepositoryTest.java +++ b/opensrp-reporting/src/test/java/org/smartregister/reporting/repository/IndicatorQueryRepositoryTest.java @@ -61,7 +61,7 @@ public void findQueryByIndicatorCodeInvokeDbWithCorrectParams() { Mockito.verify(sqLiteDatabase, Mockito.times(1)).query( ArgumentMatchers.eq(Constants.IndicatorQueryRepository.INDICATOR_QUERY_TABLE) - , MockitoHamcrest.argThat(IsArrayWithSize.arrayWithSize(5)) + , MockitoHamcrest.argThat(IsArrayWithSize.arrayWithSize(6)) , ArgumentMatchers.eq(Constants.IndicatorQueryRepository.QUERY + " = ?") , MockitoHamcrest.argThat(IsArrayWithSize.arrayWithSize(1)) , ArgumentMatchers.isNull(String.class) diff --git a/opensrp-reporting/src/test/java/org/smartregister/reporting/util/UtilsTest.java b/opensrp-reporting/src/test/java/org/smartregister/reporting/util/UtilsTest.java new file mode 100644 index 00000000..135899f0 --- /dev/null +++ b/opensrp-reporting/src/test/java/org/smartregister/reporting/util/UtilsTest.java @@ -0,0 +1,48 @@ +package org.smartregister.reporting.util; + +import net.sqlcipher.MatrixCursor; +import net.sqlcipher.database.SQLiteDatabase; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner; +import java.util.ArrayList; + + +/** + * Created by Ephraim Kigamba - ekigamba@ona.io on 2019-08-15 + */ + +@RunWith(MockitoJUnitRunner.class) +public class UtilsTest { + + @Mock + private SQLiteDatabase database; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + } + + @Test + public void performQueryShouldReturnArrayListWithResultsWhenGivenValidQuery() { + MatrixCursor matrixCursor = new MatrixCursor(new String[]{"id", "first_name", "last_name", "id_number", "credit_score"}); + matrixCursor.addRow(new Object[]{1, "Adam", "Doe", "909092323", 89.56F}); + matrixCursor.addRow(new Object[]{1, "Jane", "Doe", "929300000", 3.2F}); + + Mockito.doReturn(matrixCursor) + .when(database) + .rawQuery(Mockito.anyString(), Mockito.isNull(String[].class)); + + ArrayList actualResults = ReportingUtils.performQuery(database, "SELECT * FROM persons"); + + Assert.assertEquals(3, actualResults.size()); + Assert.assertEquals(5, actualResults.get(1).length); + Assert.assertEquals(3.2F, (Float) actualResults.get(2)[4], 0); + } +} \ No newline at end of file diff --git a/sample/build.gradle b/sample/build.gradle index 5b5e844b..1571086f 100644 --- a/sample/build.gradle +++ b/sample/build.gradle @@ -43,13 +43,13 @@ android { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - buildConfigField "int", "DATABASE_VERSION", '1' + buildConfigField "int", "DATABASE_VERSION", '3' buildConfigField "int", "INDICATOR_TALLY_GENERATOR_MINUTES", '2' } debug { - buildConfigField "int", "DATABASE_VERSION", '1' + buildConfigField "int", "DATABASE_VERSION", '3' buildConfigField "int", "INDICATOR_TALLY_GENERATOR_MINUTES", '2' } } diff --git a/sample/src/main/assets/config/indicator-definitions.yml b/sample/src/main/assets/config/indicator-definitions.yml index 45082844..9508385b 100644 --- a/sample/src/main/assets/config/indicator-definitions.yml +++ b/sample/src/main/assets/config/indicator-definitions.yml @@ -18,4 +18,10 @@ indicators: - key: "S_IND_005" description: "A float count" - indicatorQuery: "select (count(indicator_code) * 0.11) from indicator_queries" \ No newline at end of file + indicatorQuery: "select (count(indicator_code) * 0.11) from indicator_queries" + + - key: "S_IND" + description: "A simple multi-result query where some results are ignored and no-results are prefilled with 0" + indicatorQuery: "select indicator_code, count(*) as count from indicator_queries group by indicator_code UNION ALL select 'TOTAL', count(*) from indicator_queries" + isMultiResult: true + expectedIndicators: ["S_IND_MALE", "S_IND_FEMALE", "S_IND_S_IND_001"] \ No newline at end of file diff --git a/sample/src/main/java/org/smartregister/sample/repository/SampleRepository.java b/sample/src/main/java/org/smartregister/sample/repository/SampleRepository.java index 93966e04..c3295837 100644 --- a/sample/src/main/java/org/smartregister/sample/repository/SampleRepository.java +++ b/sample/src/main/java/org/smartregister/sample/repository/SampleRepository.java @@ -17,6 +17,7 @@ import org.smartregister.reporting.repository.IndicatorRepository; import org.smartregister.repository.EventClientRepository; import org.smartregister.repository.Repository; +import org.smartregister.sample.BuildConfig; import org.smartregister.sample.utils.ChartUtil; import java.text.ParseException; @@ -33,10 +34,9 @@ public class SampleRepository extends Repository { private String databasePassword = "db_pass"; private static final String TAG = SampleRepository.class.getCanonicalName(); private Context context; - private static final int DB_VERSION = 2; public SampleRepository(Context context, org.smartregister.Context openSRPContext) { - super(context, AllConstants.DATABASE_NAME, DB_VERSION, openSRPContext.session(), null, openSRPContext.sharedRepositoriesArray()); + super(context, AllConstants.DATABASE_NAME, BuildConfig.DATABASE_VERSION, openSRPContext.session(), null, openSRPContext.sharedRepositoriesArray()); this.context = context; } @@ -50,9 +50,7 @@ public void onCreate(SQLiteDatabase database) { } public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion == 1 && newVersion == 2) { - ReportingLibrary.getInstance().performMigrations(db); - } + ReportingLibrary.getInstance().performMigrations(db); } @Override