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

Update analytics events #6220

Merged
merged 10 commits into from
Jun 21, 2024
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 @@ -18,7 +18,6 @@
import static android.content.DialogInterface.BUTTON_POSITIVE;
import static android.view.animation.AnimationUtils.loadAnimation;
import static org.javarosa.form.api.FormEntryController.EVENT_PROMPT_NEW_REPEAT;
import static org.odk.collect.android.analytics.AnalyticsEvents.OPEN_MAP_KIT_RESPONSE;
import static org.odk.collect.android.formentry.FormIndexAnimationHandler.Direction.BACKWARDS;
import static org.odk.collect.android.formentry.FormIndexAnimationHandler.Direction.FORWARDS;
import static org.odk.collect.android.utilities.AnimationUtils.areAnimationsEnabled;
Expand Down Expand Up @@ -80,7 +79,6 @@
import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;
import org.odk.collect.analytics.Analytics;
import org.odk.collect.android.R;
import org.odk.collect.android.analytics.AnalyticsUtils;
import org.odk.collect.android.application.Collect;
Expand Down Expand Up @@ -865,7 +863,6 @@ protected void onActivityResult(int requestCode, int resultCode, final Intent in

switch (requestCode) {
case RequestCodes.OSM_CAPTURE:
Analytics.log(OPEN_MAP_KIT_RESPONSE, "form");
setWidgetData(intent.getStringExtra("OSM_FILE_NAME"));
break;
case RequestCodes.EX_ARBITRARY_FILE_CHOOSER:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,6 @@ object AnalyticsEvents {
*/
const val SET_SERVER = "SetServer"

/**
* Track video requests with high resolution setting turned off. The action should be a hash of
* the form definition.
*/
const val REQUEST_VIDEO_NOT_HIGH_RES = "RequestVideoNotHighRes"

/**
* Track video requests with high resolution setting turned on. This is tracked to contextualize
* the counts with the high resolution setting turned off since we expect that video is not very
* common overall. The action should be a hash of the form definition.
*/
const val REQUEST_HIGH_RES_VIDEO = "RequestHighResVideo"

/**
* Track submission encryption. The action should be a hash of the form definition.
*/
Expand All @@ -31,28 +18,6 @@ object AnalyticsEvents {
*/
const val SUBMISSION = "Submission"

/**
* Tracks if any forms are being used as part of a workflow where instances are imported
* from disk
*/
const val IMPORT_INSTANCE = "ImportInstance"

/**
* Tracks if any forms are being used as part of a workflow where instances are imported
* from disk and then encrypted
*/
const val IMPORT_AND_ENCRYPT_INSTANCE = "ImportAndEncryptInstance"

/**
* Tracks responses from OpenMapKit to the OSMWidget
*/
const val OPEN_MAP_KIT_RESPONSE = "OpenMapKitResponse"

/**
* Tracks how often users create shortcuts to forms
*/
const val CREATE_SHORTCUT = "CreateShortcut"

/**
* Tracks how often instances that have been deleted on disk are opened for editing/viewing
*/
Expand Down Expand Up @@ -124,36 +89,16 @@ object AnalyticsEvents {

const val INSTANCE_PROVIDER_DELETE = "InstanceProviderDelete"

/**
* Tracks how often form-level auto-delete setting is used
*/
const val FORM_LEVEL_AUTO_DELETE = "FormLevelAutoDelete"

/**
* Tracks how often form-level auto-send setting is used
*/
const val FORM_LEVEL_AUTO_SEND = "FormLevelAutoSend"

/**
* Tracks how often a form is finalized using a `ref` attribute on the `submission` element
*/
const val PARTIAL_FORM_FINALIZED = "PartialFormFinalized"

/**
* Tracks how often drafts that can't be bulk finalized are attempted to be
*/
const val BULK_FINALIZE_ENCRYPTED_FORM = "BulkFinalizeEncryptedForm"
const val BULK_FINALIZE_SAVE_POINT = "BulkFinalizeSavePoint"

/**
* Tracks how often printing with the old ExPrinterWidget is triggered
*/
const val ZEBRA_PRINTER_STARTED = "ZebraPrinterStarted"

/**
* Tracks how often saved forms are manually deleted and in what number
*/
const val DELETE_SAVED_FORM_FEW = "DeleteSavedFormFew"
const val DELETE_SAVED_FORM_TENS = "DeleteSavedFormTens"
const val DELETE_SAVED_FORM_HUNDREDS = "DeleteSavedFormHundreds"
const val DELETE_SAVED_FORM_FEW = "DeleteSavedFormFew" // < 10
const val DELETE_SAVED_FORM_TENS = "DeleteSavedFormTens" // >= 10
const val DELETE_SAVED_FORM_HUNDREDS = "DeleteSavedFormHundreds" // >= 100
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.odk.collect.android.R
import org.odk.collect.android.analytics.AnalyticsEvents
import org.odk.collect.android.analytics.AnalyticsUtils
import org.odk.collect.android.formlists.blankformlist.BlankFormListItem
import org.odk.collect.android.formlists.blankformlist.BlankFormListViewModel
import org.odk.collect.android.injection.DaggerUtils
Expand Down Expand Up @@ -59,10 +57,6 @@ class AndroidShortcutsActivity : AppCompatActivity() {
.map { it.formName }
.toTypedArray()
) { _: DialogInterface?, item: Int ->
AnalyticsUtils.logServerEvent(
AnalyticsEvents.CREATE_SHORTCUT,
settingsProvider.getUnprotectedSettings()
)
val intent = getShortcutIntent(blankFormListItems, item)
setResult(RESULT_OK, intent)
finish()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@

package org.odk.collect.android.instancemanagement;

import static org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString;

import android.net.Uri;

import org.apache.commons.io.FileUtils;
import org.odk.collect.android.analytics.AnalyticsEvents;
import org.odk.collect.android.analytics.AnalyticsUtils;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.exception.EncryptionException;
import org.odk.collect.android.external.InstancesContract;
Expand Down Expand Up @@ -50,8 +50,6 @@

import timber.log.Timber;

import static org.odk.collect.strings.localization.LocalizedApplicationKt.getLocalizedString;

public class InstanceDiskSynchronizer {

private static int counter;
Expand Down Expand Up @@ -187,22 +185,11 @@ private String getInstanceIdFromInstance(final String instancePath) {
private void encryptInstanceIfNeeded(Form form, Instance instance) throws EncryptionException, IOException {
if (instance != null) {
if (shouldInstanceBeEncrypted(form)) {
logImportAndEncrypt(form);
encryptInstance(instance);
} else {
logImport(form);
}
}
}

private void logImport(Form form) {
AnalyticsUtils.logFormEvent(AnalyticsEvents.IMPORT_INSTANCE, form.getFormId(), form.getDisplayName());
}

private void logImportAndEncrypt(Form form) {
AnalyticsUtils.logFormEvent(AnalyticsEvents.IMPORT_AND_ENCRYPT_INSTANCE, form.getFormId(), form.getDisplayName());
}

private void encryptInstance(Instance instance) throws EncryptionException, IOException {
String instancePath = instance.getInstanceFilePath();
File instanceXml = new File(instancePath);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
package org.odk.collect.android.instancemanagement.autosend

import org.odk.collect.android.analytics.AnalyticsEvents
import org.odk.collect.android.analytics.AnalyticsUtils
import org.odk.collect.forms.Form

fun Form.shouldFormBeSentAutomatically(isAutoSendEnabledInSettings: Boolean): Boolean {
if (!autoSend.isNullOrEmpty()) {
AnalyticsUtils.logFormEvent(AnalyticsEvents.FORM_LEVEL_AUTO_SEND, formId, displayName)
}

return if (isAutoSendEnabledInSettings) {
getAutoSendMode() != FormAutoSendMode.OPT_OUT
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@
import org.json.JSONException;
import org.json.JSONObject;
import org.odk.collect.analytics.Analytics;
import org.odk.collect.android.analytics.AnalyticsEvents;
import org.odk.collect.android.application.Collect;
import org.odk.collect.android.database.instances.DatabaseInstanceColumns;
import org.odk.collect.android.exception.EncryptionException;
Expand Down Expand Up @@ -357,10 +356,6 @@ private Instance exportData(boolean markCompleted, FormSaver.ProgressListener pr
// now see if the packaging of the data for the server would make it
// non-reopenable (e.g., encryption or other fraction of the form).
boolean canEditAfterCompleted = formController.isSubmissionEntireForm();
if (!canEditAfterCompleted) {
Analytics.log(AnalyticsEvents.PARTIAL_FORM_FINALIZED, "form");
}

boolean isEncrypted = false;

// build a submission.xml to hold the data being submitted
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package org.odk.collect.android.utilities

import org.odk.collect.android.analytics.AnalyticsEvents
import org.odk.collect.android.analytics.AnalyticsUtils
import org.odk.collect.forms.FormsRepository
import org.odk.collect.forms.instances.Instance
import java.util.Locale
Expand All @@ -21,10 +19,6 @@ object InstanceAutoDeleteChecker {
instance: Instance
): Boolean {
formsRepository.getLatestByFormIdAndVersion(instance.formId, instance.formVersion)?.let { form ->
if (!form.autoDelete.isNullOrEmpty()) {
AnalyticsUtils.logFormEvent(AnalyticsEvents.FORM_LEVEL_AUTO_DELETE, form.formId, form.displayName)
}

return if (isAutoDeleteEnabledInProjectSettings) {
form.autoDelete == null || form.autoDelete.trim().lowercase(Locale.US) != "false"
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@
import androidx.annotation.NonNull;

import org.javarosa.core.model.data.IAnswerData;
import org.odk.collect.analytics.Analytics;
import org.odk.collect.android.analytics.AnalyticsEvents;
import org.javarosa.form.api.FormEntryPrompt;
import org.odk.collect.android.databinding.ExPrinterWidgetBinding;
import org.odk.collect.android.formentry.questions.QuestionDetails;
Expand Down Expand Up @@ -187,7 +185,6 @@ private void onButtonClick() {
try {
waitingForDataRegistry.waitForData(getFormEntryPrompt().getIndex());
firePrintingActivity(intentName);
Analytics.log(AnalyticsEvents.ZEBRA_PRINTER_STARTED, "form");
} catch (ActivityNotFoundException e) {
waitingForDataRegistry.cancelWaitingForData();
Toast.makeText(getContext(),
Expand Down Expand Up @@ -240,4 +237,4 @@ private void firePrintingActivity(String intentName) throws ActivityNotFoundExce
bcastIntent.putExtra("DATA", printDataBundle);
getContext().sendBroadcast(bcastIntent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

package org.odk.collect.android.widgets;

import static org.odk.collect.android.analytics.AnalyticsEvents.REQUEST_HIGH_RES_VIDEO;
import static org.odk.collect.android.analytics.AnalyticsEvents.REQUEST_VIDEO_NOT_HIGH_RES;
import static org.odk.collect.android.utilities.ApplicationConstants.RequestCodes;

import android.annotation.SuppressLint;
Expand All @@ -32,7 +30,6 @@
import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.core.model.data.StringData;
import org.javarosa.form.api.FormEntryPrompt;
import org.odk.collect.analytics.Analytics;
import org.odk.collect.android.databinding.VideoWidgetBinding;
import org.odk.collect.android.formentry.questions.QuestionDetails;
import org.odk.collect.android.utilities.Appearances;
Expand Down Expand Up @@ -163,10 +160,8 @@ private void captureVideo() {
boolean highResolution = settingsProvider.getUnprotectedSettings().getBoolean(ProjectKeys.KEY_HIGH_RESOLUTION);
if (highResolution) {
i.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
Analytics.log(REQUEST_HIGH_RES_VIDEO, "form");
} else {
Analytics.log(REQUEST_VIDEO_NOT_HIGH_RES, "form");
}

try {
waitingForDataRegistry.waitForData(getFormEntryPrompt().getIndex());
((Activity) getContext()).startActivityForResult(i, requestCode);
Expand Down
15 changes: 15 additions & 0 deletions docs/ANALYTICS-QUESTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Analytics questions

A list of questions asked and answered via analytics already sectioned by the date (with a 90 day recording window).

## June 2024

- How often is high-res video disabled? 2% of users using video widget.
- How often is `OSMWidget` used? Once.
- How many users create shortcuts? 126.
- How many user are using ADB to add forms/instances? 904 for Forms, 2728 for instances (500k events).
- How many users are importing encrypted instances? 10.
- How often is partial form finalization used? Never.
- How many forms use auto send attribute? 200+ (max recordable) with 1.5k users.
- How many forms use auto delete attribute? 200+ (max recordable) with 1k users.
- How often is old printer widget (`ExPrinterWidget`) used? Never.
1 change: 1 addition & 0 deletions maps/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies {
implementation(project(":settings"))
implementation(project(":strings"))
implementation(project(":web-page"))
implementation(project(":analytics"))
implementation(Dependencies.android_material)
implementation(Dependencies.kotlin_stdlib)
implementation(Dependencies.androidx_fragment_ktx)
Expand Down
11 changes: 11 additions & 0 deletions maps/src/main/java/org/odk/collect/maps/AnalyticsEvents.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.odk.collect.maps

object AnalyticsEvents {

/**
* Tracks how many offline layers people are importing at once
*/
const val IMPORT_LAYER_SINGLE = "ImportLayerSingle" // One
const val IMPORT_LAYER_FEW = "ImportLayerFew" // <= 5
const val IMPORT_LAYER_MANY = "ImportLayerMany" // > 5
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.odk.collect.analytics.Analytics
import org.odk.collect.androidshared.system.copyToFile
import org.odk.collect.androidshared.system.getFileExtension
import org.odk.collect.androidshared.system.getFileName
import org.odk.collect.async.Scheduler
import org.odk.collect.maps.AnalyticsEvents
import org.odk.collect.settings.SettingsProvider
import org.odk.collect.settings.keys.ProjectKeys
import org.odk.collect.shared.TempFiles
Expand Down Expand Up @@ -75,7 +77,10 @@ class OfflineMapLayersViewModel(
_isLoading.value = true
scheduler.immediate(
background = {
tempLayersDir.listFiles()?.forEach {
val layers = tempLayersDir.listFiles()
logImport(layers)

layers?.forEach {
referenceLayerRepository.addLayer(it, shared)
}
tempLayersDir.delete()
Expand All @@ -99,4 +104,15 @@ class OfflineMapLayersViewModel(
_isLoading.postValue(false)
}
}

private fun logImport(layers: Array<File>?) {
val count = layers?.size ?: return
val event = when {
count == 1 -> AnalyticsEvents.IMPORT_LAYER_SINGLE
count <= 5 -> AnalyticsEvents.IMPORT_LAYER_FEW
else -> AnalyticsEvents.IMPORT_LAYER_MANY
}

Analytics.log(event)
}
}