Skip to content
Open
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 @@ -619,7 +619,8 @@ public void DownLoadDiagnosesServicesItems(@Nullable final String officerCode) {
Thread thread = new Thread() {
public void run() {
try {
DiagnosesServicesMedications diagnosesServicesMedications = new FetchDiagnosesServicesItems().execute();
FetchDiagnosesServicesItems fetchDiagnosesServicesItems = new FetchDiagnosesServicesItems();
DiagnosesServicesMedications diagnosesServicesMedications = fetchDiagnosesServicesItems.execute();
saveLastUpdateDate(diagnosesServicesMedications.getLastUpdated());
sqlHandler.ClearAll("tblReferences");
sqlHandler.ClearAll("tblHealthFacilities");
Expand Down Expand Up @@ -688,9 +689,7 @@ public void run() {

runOnUiThread(() -> {
progressDialog.dismiss();
if (officerCode != null) {
DownLoadServicesItemsPriceList(officerCode);
}
handlePostMasterDataSync(officerCode, fetchDiagnosesServicesItems.getSkippedMedicationPages());
});
} catch (Exception e) {
e.printStackTrace();
Expand All @@ -713,7 +712,27 @@ public void run() {
}
}

private void DownLoadServicesItemsPriceList(@NonNull final String claimAdministratorCode) {
void handlePostMasterDataSync(
@Nullable String officerCode,
@NonNull List<Integer> skippedMedicationPages
) {
if (!skippedMedicationPages.isEmpty()) {
final String errorLogMessage = "Master data synced with partial medications.\nSkipped pages: "
+ skippedMedicationPages;
showDialog(
errorLogMessage,
(dialog, which) -> {
if (officerCode != null) {
DownLoadServicesItemsPriceList(officerCode);
}
}
);
} else if (officerCode != null) {
DownLoadServicesItemsPriceList(officerCode);
}
}

protected void DownLoadServicesItemsPriceList(@NonNull final String claimAdministratorCode) {
if (global.isNetworkAvailable()) {
String progress_message = getResources().getString(R.string.Services) + ", " + getResources().getString(R.string.Items) + "...";
progressDialog = ProgressDialog.show(this, getResources().getString(R.string.mapping), progress_message);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;

import org.openimis.imisclaims.network.exception.HttpException;
import org.openimis.imisclaims.network.request.BaseFHIRGetPaginatedRequest;
import org.openimis.imisclaims.network.response.PaginatedResponse;
import org.openimis.imisclaims.tools.Log;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

public class PaginatedResponseUtils {
private static final String LOG_TAG = "FHIR_SYNC_TOLERANCE";

private PaginatedResponseUtils() {
throw new IllegalAccessError("This constructor is private");
Expand Down Expand Up @@ -50,10 +54,154 @@ public static <T, U> List<U> downloadAll(
return list;
}

@NonNull
@WorkerThread
public static <T, U> DownloadResult<U> downloadAllSkipFailedPages(
@NonNull String endpoint,
@NonNull RequestExecutor<T> executor,
@Nullable Mapper.Transformer<T, U> transformer,
int maxPages,
int maxConsecutiveFailures
) throws Exception {
int page = 0;
int consecutiveFailures = 0;
boolean hasMore = true;
boolean hasSkippedPages = false;
List<U> list = new ArrayList<>();
List<Integer> skippedPages = new ArrayList<>();
List<Integer> recoveredPages = new ArrayList<>();
Mapper<T, U> mapper = transformer != null ? new Mapper<>(transformer) : null;

while (hasMore && page < maxPages) {
final int displayPage = page + 1;
try {
PaginatedResponse<T> response = executor.download(page);
if (mapper != null) {
list.addAll(mapper.map(response.getValue()));
} else {
list.addAll((Collection<? extends U>) response.getValue());
}
hasMore = response.hasMore();
consecutiveFailures = 0;
if (hasSkippedPages) {
recoveredPages.add(displayPage);
}
} catch (Exception exception) {
skippedPages.add(displayPage);
hasSkippedPages = true;
consecutiveFailures++;
logSkippedPage(endpoint, displayPage, exception);
if (consecutiveFailures >= maxConsecutiveFailures) {
Log.w(
LOG_TAG,
String.format(
"endpoint=%s action=stop reason=maxConsecutiveFailures reached skippedPages=%s",
endpoint,
skippedPages
)
);
break;
}
}
page++;
}

if (page >= maxPages && hasMore) {
Log.w(
LOG_TAG,
String.format(
"endpoint=%s action=stop reason=maxPages reached maxPages=%s skippedPages=%s",
endpoint,
maxPages,
skippedPages
)
);
}

Log.i(
LOG_TAG,
String.format(
"endpoint=%s summary skippedPages=%s recoveredPages=%s totalItems=%s",
endpoint,
skippedPages,
recoveredPages,
list.size()
)
);
return new DownloadResult<>(list, skippedPages, recoveredPages);
}

private static void logSkippedPage(@NonNull String endpoint, int page, @NonNull Exception exception) {
String errorType = exception.getClass().getSimpleName();
if (exception instanceof HttpException) {
HttpException httpException = (HttpException) exception;
Log.w(
LOG_TAG,
String.format(
"endpoint=%s page=%s action=skip errorType=%s code=%s message=%s",
endpoint,
page,
errorType,
httpException.getCode(),
exception.getMessage()
)
);
} else {
Log.w(
LOG_TAG,
String.format(
"endpoint=%s page=%s action=skip errorType=%s message=%s",
endpoint,
page,
errorType,
exception.getMessage()
)
);
}
}

public interface RequestExecutor<T> {

@NonNull
@WorkerThread
PaginatedResponse<T> download(int page) throws Exception;
}

public static class DownloadResult<U> {
@NonNull
private final List<U> items;
@NonNull
private final List<Integer> skippedPages;
@NonNull
private final List<Integer> recoveredPages;

public DownloadResult(
@NonNull List<U> items,
@NonNull List<Integer> skippedPages,
@NonNull List<Integer> recoveredPages
) {
this.items = items;
this.skippedPages = skippedPages;
this.recoveredPages = recoveredPages;
}

@NonNull
public List<U> getItems() {
return items;
}

@NonNull
public List<Integer> getSkippedPages() {
return Collections.unmodifiableList(skippedPages);
}

@NonNull
public List<Integer> getRecoveredPages() {
return Collections.unmodifiableList(recoveredPages);
}

public boolean hasSkippedPages() {
return !skippedPages.isEmpty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,20 @@
import org.openimis.imisclaims.util.DateUtils;

import java.util.Date;
import java.util.List;

public class FetchDiagnosesServicesItems {
private static final int MAX_PAGES = 1000;
private static final int MAX_CONSECUTIVE_FAILURES = 3;

@NonNull
private final GetActivityDefinitionsRequest getActivityDefinitionsRequest;
@NonNull
private final GetDiagnosesRequest getDiagnosesRequest;
@NonNull
private final GetMedicationsRequest getMedicationsRequest;
@NonNull
private List<Integer> skippedMedicationPages = java.util.Collections.emptyList();

public FetchDiagnosesServicesItems() {
this(
Expand All @@ -50,6 +55,16 @@ public FetchDiagnosesServicesItems(
@NonNull
@WorkerThread
public DiagnosesServicesMedications execute() throws Exception {
PaginatedResponseUtils.DownloadResult<Medication> medicationDownloadResult =
PaginatedResponseUtils.downloadAllSkipFailedPages(
"Medication",
getMedicationsRequest::get,
this::toMedication,
MAX_PAGES,
MAX_CONSECUTIVE_FAILURES
);
skippedMedicationPages = medicationDownloadResult.getSkippedPages();

// previous code was passing sometimes a `last_updated_date` but it was either empty or
// `new Date(0)`. I'm still returning the last updated date in case it's one day used
// again.¯\_(ツ)_/¯
Expand All @@ -60,13 +75,15 @@ public DiagnosesServicesMedications execute() throws Exception {
getActivityDefinitionsRequest::get,
this::toService
),
/* medications = */ PaginatedResponseUtils.downloadAll(
getMedicationsRequest::get,
this::toMedication
)
/* medications = */ medicationDownloadResult.getItems()
);
}

@NonNull
public List<Integer> getSkippedMedicationPages() {
return skippedMedicationPages;
}

@NonNull
private Diagnosis toDiagnosis(@NonNull DiagnosisDto dto) {
return new Diagnosis(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.openimis.imisclaims;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import android.app.AlertDialog;
import android.content.DialogInterface;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collections;
public class MainActivityPostSyncTest {

private TestMainActivity activity;

@Before
public void setup() {
activity = new TestMainActivity();
}

@Test
public void downloadAllData_showsPartialMedicationDialog_whenSkippedMedicationPagesNotEmpty() {
activity.handlePostMasterDataSync("OFFICER", Arrays.asList(2, 4));

assertTrue(activity.dialogShown);
assertNotNull(activity.lastDialogMessage);
assertTrue(activity.lastDialogMessage.contains("partial medications"));
assertTrue(activity.lastDialogMessage.contains("[2, 4]"));
}

@Test
public void downloadAllData_doesNotAutoCallDownloadPriceList_beforeDialogConfirmation_whenSkippedPagesExist() {
activity.handlePostMasterDataSync("OFFICER", Collections.singletonList(2));

assertTrue(activity.dialogShown);
assertFalse(activity.downloadCalled);
}

@Test
public void downloadAllData_callsDownloadPriceListDirectly_whenNoSkippedPages_andOfficerCodePresent() {
activity.handlePostMasterDataSync("OFFICER", Collections.emptyList());

assertTrue(activity.downloadCalled);
assertEquals("OFFICER", activity.downloadCalledWith);
assertFalse(activity.dialogShown);
}

@Test
public void downloadAllData_afterDialogConfirmation_callsDownloadPriceList_whenOfficerCodePresent() {
activity.handlePostMasterDataSync("OFFICER", Collections.singletonList(3));

assertFalse(activity.downloadCalled);
activity.lastOkCallback.onClick(null, 0);

assertTrue(activity.downloadCalled);
assertEquals("OFFICER", activity.downloadCalledWith);
}

public static class TestMainActivity extends MainActivity {
boolean dialogShown = false;
boolean downloadCalled = false;
String downloadCalledWith;
String lastDialogMessage;
DialogInterface.OnClickListener lastOkCallback;

@Override
protected AlertDialog showDialog(String msg, DialogInterface.OnClickListener okCallback) {
this.dialogShown = true;
this.lastDialogMessage = msg;
this.lastOkCallback = okCallback;
return null;
}

@Override
protected void DownLoadServicesItemsPriceList(String claimAdministratorCode) {
this.downloadCalled = true;
this.downloadCalledWith = claimAdministratorCode;
}
}
}
Loading
Loading