Brion VIBBER has submitted this change and it was merged.

Change subject: Add EventLogging for Edit Funnel
......................................................................


Add EventLogging for Edit Funnel

- Fix EL to actually work
- Send UA properly

Change-Id: I6225c3df43f739b38f145669b4ae7146735a6383
---
M wikipedia/src/main/java/org/wikipedia/Utils.java
M wikipedia/src/main/java/org/wikipedia/WikipediaApp.java
A wikipedia/src/main/java/org/wikipedia/analytics/EditFunnel.java
A wikipedia/src/main/java/org/wikipedia/analytics/EventLoggingEvent.java
A wikipedia/src/main/java/org/wikipedia/analytics/Funnel.java
A wikipedia/src/main/java/org/wikipedia/analytics/FunnelManager.java
M wikipedia/src/main/java/org/wikipedia/editing/CaptchaHandler.java
M wikipedia/src/main/java/org/wikipedia/editing/DoEditTask.java
M wikipedia/src/main/java/org/wikipedia/editing/EditSectionActivity.java
M wikipedia/src/main/java/org/wikipedia/editing/SuccessEditResult.java
D wikipedia/src/main/java/org/wikipedia/eventlogging/EventLoggingEvent.java
11 files changed, 369 insertions(+), 103 deletions(-)

Approvals:
  Brion VIBBER: Verified; Looks good to me, approved
  jenkins-bot: Verified



diff --git a/wikipedia/src/main/java/org/wikipedia/Utils.java 
b/wikipedia/src/main/java/org/wikipedia/Utils.java
index ea7ed45..56144ee 100644
--- a/wikipedia/src/main/java/org/wikipedia/Utils.java
+++ b/wikipedia/src/main/java/org/wikipedia/Utils.java
@@ -386,4 +386,16 @@
             view.setTextDirection(Utils.isLangRTL(lang) ? 
View.TEXT_DIRECTION_RTL : View.TEXT_DIRECTION_LTR);
         }
     }
+
+    /**
+     * Returns db name for given site
+     *
+     * WARNING: HARDCODED TO WORK FOR WIKIPEDIA ONLY
+     *
+     * @param site Site object to get dbname for
+     * @return dbname for given site object
+     */
+    public static String getDBNameForSite(Site site) {
+        return site.getLanguage() + "wiki";
+    }
 }
diff --git a/wikipedia/src/main/java/org/wikipedia/WikipediaApp.java 
b/wikipedia/src/main/java/org/wikipedia/WikipediaApp.java
index f233a96..c7f566a 100644
--- a/wikipedia/src/main/java/org/wikipedia/WikipediaApp.java
+++ b/wikipedia/src/main/java/org/wikipedia/WikipediaApp.java
@@ -12,6 +12,7 @@
 import org.acra.*;
 import org.acra.annotation.*;
 import org.mediawiki.api.json.*;
+import org.wikipedia.analytics.*;
 import org.wikipedia.data.*;
 import org.wikipedia.editing.*;
 import org.wikipedia.editing.summaries.*;
@@ -279,6 +280,15 @@
         return userInfoStorage;
     }
 
+    private FunnelManager funnelManager;
+    public FunnelManager getFunnelManager() {
+        if (funnelManager == null) {
+            funnelManager = new FunnelManager(this);
+        }
+
+        return funnelManager;
+    }
+
     private static boolean wikipediaZeroDisposition = false;
     public static void setWikipediaZeroDisposition(boolean b) {
         wikipediaZeroDisposition = b;
diff --git a/wikipedia/src/main/java/org/wikipedia/analytics/EditFunnel.java 
b/wikipedia/src/main/java/org/wikipedia/analytics/EditFunnel.java
new file mode 100644
index 0000000..6962f40
--- /dev/null
+++ b/wikipedia/src/main/java/org/wikipedia/analytics/EditFunnel.java
@@ -0,0 +1,120 @@
+package org.wikipedia.analytics;
+
+import org.json.*;
+import org.wikipedia.*;
+
+import java.util.*;
+
+public class EditFunnel extends Funnel {
+    private static final String SCHEMA_NAME = "MobileWikiAppEdit";
+    private static final int REV_ID = 8198182;
+
+    private final String editSessionToken;
+    private final PageTitle title;
+
+    public EditFunnel(WikipediaApp app, PageTitle title) {
+        super(app, SCHEMA_NAME, REV_ID);
+        editSessionToken = UUID.randomUUID().toString();
+        this.title = title;
+    }
+
+    @Override
+    protected JSONObject preprocessData(JSONObject eventData) {
+        try {
+            eventData.put("editSessionToken", editSessionToken);
+            if (getApp().getUserInfoStorage().isLoggedIn()) {
+                eventData.put("userName", 
getApp().getUserInfoStorage().getUser().getUsername());
+            }
+            eventData.put("pageNS", title.getNamespace());
+        } catch (JSONException e) {
+            // This never happens either
+            throw new RuntimeException(e);
+        }
+        return eventData;
+    }
+
+    protected void log(Object... params) {
+        super.log(title.getSite(), params);
+    }
+
+    public void logStart() {
+        log(
+                "action", "start"
+        );
+    }
+
+    public void logPreview() {
+        log(
+                "action", "preview"
+        );
+    }
+
+    public void logSaved(int revID) {
+        log(
+                "action", "saved",
+                "revID", revID
+        );
+
+    }
+
+    public void logLoginAttempt() {
+        log(
+                "action", "loginAttempt"
+        );
+
+    }
+
+    public void logLoginSuccess() {
+        log(
+                "action", "loginSuccess"
+        );
+    }
+
+    public void logLoginFailure() {
+        log(
+                "action", "loginFailure"
+        );
+
+    }
+
+    public void logCaptchaShown() {
+        log(
+                "action", "captchaShown"
+        );
+
+    }
+
+    public void logCaptchaFailure() {
+        log(
+                "action", "captchaFailure"
+        );
+    }
+
+    public void logAbuseFilterWarning(String code) {
+        log(
+                "action", "abuseFilterWarning",
+                "abuseFilterCode", code
+        );
+    }
+
+    public void logAbuseFilterError(String code) {
+        log(
+                "action", "abuseFilterError",
+                "abuseFilterCode", code
+        );
+
+    }
+
+    public void logSaveAnonExplicit() {
+        log(
+                "action", "saveAnonExplicit"
+        );
+    }
+
+    public void logError(String code) {
+        log(
+                "action", "error",
+                "errorText", code
+        );
+    }
+}
diff --git 
a/wikipedia/src/main/java/org/wikipedia/analytics/EventLoggingEvent.java 
b/wikipedia/src/main/java/org/wikipedia/analytics/EventLoggingEvent.java
new file mode 100644
index 0000000..3b79820
--- /dev/null
+++ b/wikipedia/src/main/java/org/wikipedia/analytics/EventLoggingEvent.java
@@ -0,0 +1,82 @@
+package org.wikipedia.analytics;
+
+import android.net.*;
+import android.util.*;
+import com.github.kevinsawicki.http.*;
+import org.json.*;
+import org.wikipedia.concurrency.*;
+
+/**
+ * Base class for all various types of events that are logged to EventLogging.
+ *
+ * Each Schema has its own class, and has its own constructor that makes it 
easy
+ * to call from everywhere without having to duplicate param info at all 
places.
+ * Updating schemas / revisions is also easier this way.
+ */
+public class EventLoggingEvent {
+    private static final String EVENTLOG_URL = 
"https://bits.wikimedia.org/event.gif";;
+
+    private final JSONObject data;
+    private final String userAgent;
+
+    /**
+     * Create an EventLoggingEvent that logs to a given revision of a given 
schema with
+     * the gven data payload.
+     *
+     * @param schema Schema name (as specified on meta.wikimedia.org)
+     * @param revID Revision of the schema to log to
+     * @param wiki DBName (enwiki, dewiki, etc) of the wiki in which we are 
operating
+     * @param userAgent User-Agent string to use for this request
+     * @param eventData Data for the actual event payload. Considered to be
+     *
+     */
+    public EventLoggingEvent(String schema, int revID, String wiki, String 
userAgent, JSONObject eventData) {
+        data = new JSONObject();
+        try {
+            data.put("schema", schema);
+            data.put("revision", revID);
+            data.put("wiki", wiki);
+            data.put("event", eventData);
+        } catch (JSONException e) {
+            throw new RuntimeException(e);
+        }
+        this.userAgent = userAgent;
+    }
+
+    /**
+     * Log the current event.
+     *
+     * Returns immediately after queueing the network request in the 
background.
+     */
+    public void log() {
+        new LogEventTask(data).execute();
+    }
+
+    private class LogEventTask extends SaneAsyncTask<Integer> {
+        private final JSONObject data;
+        public LogEventTask(JSONObject data) {
+            super(SINGLE_THREAD);
+            this.data = data;
+        }
+
+        @Override
+        public Integer performTask() throws Throwable {
+            String dataURL = Uri.parse(EVENTLOG_URL)
+                    .buildUpon().query(data.toString())
+                    .build().toString();
+            Log.d("Wikipedia", "hitting " + dataURL);
+            return HttpRequest.get(dataURL).header("User-Agent", 
userAgent).code();
+        }
+
+        @Override
+        public void onFinish(Integer result) {
+            Log.d("Wikipedia", "result is " + result);
+        }
+
+        @Override
+        public void onCatch(Throwable caught) {
+            // Do nothing bad. EL data is ok to lose.
+            Log.d("Wikipedia", "Lost EL data: " + data.toString());
+        }
+    }
+}
diff --git a/wikipedia/src/main/java/org/wikipedia/analytics/Funnel.java 
b/wikipedia/src/main/java/org/wikipedia/analytics/Funnel.java
new file mode 100644
index 0000000..0e63647
--- /dev/null
+++ b/wikipedia/src/main/java/org/wikipedia/analytics/Funnel.java
@@ -0,0 +1,80 @@
+package org.wikipedia.analytics;
+
+
+import android.util.*;
+import org.json.*;
+import org.wikipedia.*;
+
+public abstract class Funnel {
+    private final String schemaName;
+    private final int revision;
+    private final WikipediaApp app;
+
+    protected Funnel(WikipediaApp app, String schemaName, int revision) {
+        this.app = app;
+        this.schemaName = schemaName;
+        this.revision = revision;
+    }
+
+    protected WikipediaApp getApp() {
+        return app;
+    }
+    /**
+     * Optionally pre-process the event data before sending to EL.
+     *
+     * @param eventData Event Data so far collected
+     * @return Event Data to be sent to server
+     */
+    protected JSONObject preprocessData(JSONObject eventData) {
+        return eventData;
+    }
+
+    /**
+     * Logs an event.
+     *
+     * @param site    The wiki in which this action was performed.
+     * @param params  Actual data for the event. Considered to be an array
+     *                of alternating key and value items (for easier
+     *                use in subclass constructors).
+     *
+     *                For example, what would be expressed in a more sane
+     *                language as:
+     *
+     *                .log({
+     *                  "page": "List of mass murderers",
+     *                  "section": "2014"
+     *                });
+     *
+     *                would be expressed here as
+     *
+     *                .log(
+     *                  "page", "List of mass murderers",
+     *                  "section", "2014"
+     *                );
+     *
+     *                This format should be only used in subclass methods 
directly.
+     *                The subclass methods should take more explicit parameters
+     *                depending on what they are logging.
+     */
+    protected void log(Site site, Object... params) {
+        JSONObject eventData = new JSONObject();
+
+        try {
+            for (int i = 0; i < params.length; i += 2) {
+                eventData.put(params[i].toString(), params[i + 1]);
+                Log.d("Wikipedia", params[i] + " " + params[i + 1]);
+            }
+        } catch (JSONException e) {
+            // This does not happen
+            throw new RuntimeException(e);
+        }
+
+        new EventLoggingEvent(
+                schemaName,
+                revision,
+                Utils.getDBNameForSite(site),
+                app.getUserAgent(),
+                preprocessData(eventData)
+        ).log();
+    }
+}
diff --git a/wikipedia/src/main/java/org/wikipedia/analytics/FunnelManager.java 
b/wikipedia/src/main/java/org/wikipedia/analytics/FunnelManager.java
new file mode 100644
index 0000000..3dc0b09
--- /dev/null
+++ b/wikipedia/src/main/java/org/wikipedia/analytics/FunnelManager.java
@@ -0,0 +1,25 @@
+package org.wikipedia.analytics;
+
+import org.wikipedia.*;
+
+import java.util.*;
+
+/**
+ * Creates and stores analytics tracking funnels.
+ */
+public class FunnelManager {
+    private final WikipediaApp app;
+    private final Hashtable<PageTitle, EditFunnel> editFunnels = new 
Hashtable<PageTitle, EditFunnel>();
+
+    public FunnelManager(WikipediaApp app) {
+        this.app = app;
+    }
+
+    public EditFunnel getEditFunnel(PageTitle title) {
+        if (!editFunnels.containsKey(title)) {
+            editFunnels.put(title, new EditFunnel(app, title));
+        }
+
+        return editFunnels.get(title);
+    }
+}
diff --git a/wikipedia/src/main/java/org/wikipedia/editing/CaptchaHandler.java 
b/wikipedia/src/main/java/org/wikipedia/editing/CaptchaHandler.java
index 2da61b8..ae23bfd 100644
--- a/wikipedia/src/main/java/org/wikipedia/editing/CaptchaHandler.java
+++ b/wikipedia/src/main/java/org/wikipedia/editing/CaptchaHandler.java
@@ -3,13 +3,13 @@
 import android.app.*;
 import android.net.*;
 import android.os.*;
-import android.support.v7.app.ActionBarActivity;
-import android.util.*;
+import android.support.v7.app.*;
 import android.view.*;
 import android.widget.*;
 import com.squareup.picasso.*;
 import org.mediawiki.api.json.*;
 import org.wikipedia.*;
+import org.wikipedia.Utils;
 
 public class CaptchaHandler {
     private final Activity activity;
@@ -68,6 +68,10 @@
         outState.putParcelable("captcha", captchaResult);
     }
 
+    public boolean isActive() {
+        return captchaResult != null;
+    }
+
     public void handleCaptcha(CaptchaResult captchaResult) {
         this.captchaResult = captchaResult;
         handleCaptcha(false);
diff --git a/wikipedia/src/main/java/org/wikipedia/editing/DoEditTask.java 
b/wikipedia/src/main/java/org/wikipedia/editing/DoEditTask.java
index 7ee6672..c5d8070 100644
--- a/wikipedia/src/main/java/org/wikipedia/editing/DoEditTask.java
+++ b/wikipedia/src/main/java/org/wikipedia/editing/DoEditTask.java
@@ -57,7 +57,7 @@
         JSONObject edit = resultJSON.optJSONObject("edit");
         String status = edit.optString("result");
         if (status.equals("Success")) {
-            return new SuccessEditResult();
+            return new SuccessEditResult(edit.optInt("newrevid"));
         } else if (status.equals("Failure")) {
             if (edit.has("captcha")) {
                 return new CaptchaResult(
diff --git 
a/wikipedia/src/main/java/org/wikipedia/editing/EditSectionActivity.java 
b/wikipedia/src/main/java/org/wikipedia/editing/EditSectionActivity.java
index 449caa0..59b5c8b 100644
--- a/wikipedia/src/main/java/org/wikipedia/editing/EditSectionActivity.java
+++ b/wikipedia/src/main/java/org/wikipedia/editing/EditSectionActivity.java
@@ -13,6 +13,7 @@
 import org.mediawiki.api.json.*;
 import org.wikipedia.*;
 import org.wikipedia.Utils;
+import org.wikipedia.analytics.*;
 import org.wikipedia.editing.summaries.*;
 import org.wikipedia.login.*;
 import org.wikipedia.page.*;
@@ -52,6 +53,8 @@
     private View editSaveOptionsContainer;
     private View editSaveOptionAnon;
     private View editSaveOptionLogIn;
+
+    private EditFunnel funnel;
 
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -125,6 +128,7 @@
             public void onClick(View view) {
                 wasSaveOptionsUsed = true;
                 Utils.fadeOut(editSaveOptionsContainer);
+                funnel.logSaveAnonExplicit();
                 doSave();
             }
         });
@@ -134,6 +138,7 @@
             public void onClick(View view) {
                 wasSaveOptionsUsed = true;
                 Intent loginIntent = new Intent(EditSectionActivity.this, 
LoginActivity.class);
+                funnel.logLoginAttempt();
                 startActivityForResult(loginIntent, 
LoginActivity.REQUEST_LOGIN);
             }
         });
@@ -141,6 +146,10 @@
         Utils.setTextDirection(sectionText, title.getSite().getLanguage());
 
         fetchSectionText();
+
+        funnel = app.getFunnelManager().getEditFunnel(title);
+
+        funnel.logStart();
     }
 
     @Override
@@ -149,6 +158,9 @@
             if (resultCode == LoginActivity.RESULT_LOGIN_SUCCESS) {
                 Utils.fadeOut(editSaveOptionsContainer);
                 doSave();
+                funnel.logLoginSuccess();
+            } else {
+                funnel.logLoginFailure();
             }
         }
     }
@@ -162,7 +174,6 @@
         app.getEditTokenStorage().get(title.getSite(), new 
EditTokenStorage.TokenRetreivedCallback() {
             @Override
             public void onTokenRetreived(final String token) {
-
                 new DoEditTask(EditSectionActivity.this, title, 
sectionText.getText().toString(), section.getId(), token, 
editSummaryHandler.getSummary(section.getHeading())) {
                     @Override
                     public void onBeforeExecute() {
@@ -226,17 +237,24 @@
                     @Override
                     public void onFinish(EditingResult result) {
                         if (result instanceof SuccessEditResult) {
+                            funnel.logSaved(((SuccessEditResult) 
result).getRevID());
                             progressDialog.dismiss();
                             setResult(EditHandler.RESULT_REFRESH_PAGE);
                             Toast.makeText(EditSectionActivity.this, 
R.string.edit_saved_successfully, Toast.LENGTH_LONG).show();
                             Utils.hideSoftKeyboard(EditSectionActivity.this);
                             finish();
                         } else if (result instanceof CaptchaResult) {
+                            if (captchaHandler.isActive()) {
+                                // Captcha entry failed!
+                                funnel.logCaptchaFailure();
+                            }
                             captchaHandler.handleCaptcha((CaptchaResult) 
result);
+                            funnel.logCaptchaShown();
                         } else if (result instanceof AbuseFilterEditResult) {
                             abusefilterEditResult = (AbuseFilterEditResult) 
result;
                             handleAbuseFilter();
                         } else {
+                            funnel.logError(result.getResult());
                             // Expand to do everything.
                             onCatch(null);
                         }
@@ -250,6 +268,11 @@
     private void handleAbuseFilter() {
         if (abusefilterEditResult == null) {
             return;
+        }
+        if (abusefilterEditResult.getType() == 
AbuseFilterEditResult.TYPE_ERROR) {
+            funnel.logAbuseFilterError(abusefilterEditResult.getCode());
+        } else {
+            funnel.logAbuseFilterWarning(abusefilterEditResult.getCode());
         }
         JSONObject payload = new JSONObject();
         try {
@@ -304,6 +327,7 @@
                 } else {
                     Utils.hideSoftKeyboard(this);
                     editPreviewFragment.showPreview(title, 
sectionText.getText().toString());
+                    funnel.logPreview();
                 }
                 return true;
             default:
diff --git 
a/wikipedia/src/main/java/org/wikipedia/editing/SuccessEditResult.java 
b/wikipedia/src/main/java/org/wikipedia/editing/SuccessEditResult.java
index e444030..6420e21 100644
--- a/wikipedia/src/main/java/org/wikipedia/editing/SuccessEditResult.java
+++ b/wikipedia/src/main/java/org/wikipedia/editing/SuccessEditResult.java
@@ -3,12 +3,19 @@
 import android.os.*;
 
 public class SuccessEditResult extends EditingResult {
-    public SuccessEditResult() {
+    private final int revID;
+    public SuccessEditResult(int revID) {
         super("Success");
+        this.revID = revID;
     }
 
     private SuccessEditResult(Parcel in) {
         super(in);
+        revID = in.readInt();
+    }
+
+    public int getRevID() {
+        return revID;
     }
 
     public static final Parcelable.Creator<SuccessEditResult> CREATOR
diff --git 
a/wikipedia/src/main/java/org/wikipedia/eventlogging/EventLoggingEvent.java 
b/wikipedia/src/main/java/org/wikipedia/eventlogging/EventLoggingEvent.java
deleted file mode 100644
index 3709289..0000000
--- a/wikipedia/src/main/java/org/wikipedia/eventlogging/EventLoggingEvent.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.wikipedia.eventlogging;
-
-import android.net.*;
-import android.util.*;
-import com.github.kevinsawicki.http.*;
-import org.json.*;
-import org.wikipedia.concurrency.*;
-
-/**
- * Base class for all various types of events that are logged to EventLogging.
- *
- * Each Schema has its own class, and has its own constructor that makes it 
easy
- * to call from everywhere without having to duplicate param info at all 
places.
- * Updating schemas / revisions is also easier this way.
- */
-public abstract class EventLoggingEvent {
-    private static final String EVENTLOG_URL = 
"https://bits.wikimedia.org/event.gif";;
-
-    private final JSONObject data;
-
-    /**
-     * Create an EventLoggingEvent that logs to a given revision of a given 
schema with
-     * the gven data payload.
-     *
-     * @param schema Schema name (as specified on meta.wikimedia.org)
-     * @param revID Revision of the schema to log to
-     * @param payload Data for the actual event payload. Considered to be
-     *                an array of alternating key and value items (for easier
-     *                construction in subclass constructors).
-     *
-     *                For example, what would be expressed in a more sane
-     *                language as:
-     *
-     *                new SomeSubClass("Schema", 4200, {
-     *                  "page": "List of mass murderers",
-     *                  "section": "2014"
-     *                });
-     *
-     *                would be expressed here as
-     *
-     *                new SomeSubClass("Schema", 4200,
-     *                  "page", "List of mass murderers",
-     *                  "section", "2014"
-     *                );
-     *
-     *                This format should be only used in subclass constructors.
-     *                The subclass constructors should take more explicit 
parameters
-     *                depending on what they are logging.
-     */
-    protected EventLoggingEvent(String schema, int revID, String... payload) {
-        data = new JSONObject();
-        try {
-            data.put("schema", schema);
-            data.put("revision", revID);
-
-            JSONObject event = new JSONObject();
-
-            for (int i = 0; i < payload.length; i += 2) {
-                event.put(payload[i], payload[i + 1]);
-            }
-
-            data.put("event", event);
-        } catch (JSONException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Log the current event.
-     *
-     * Returns immediately after queueing the network request in the 
background.
-     */
-    public void log() {
-        new LogEventTask(data).execute();
-    }
-
-    private static class LogEventTask extends SaneAsyncTask<Boolean> {
-        private final JSONObject data;
-        public LogEventTask(JSONObject data) {
-            super(SINGLE_THREAD);
-            this.data = data;
-        }
-
-        @Override
-        public Boolean performTask() throws Throwable {
-            String dataURL = Uri.parse(EVENTLOG_URL)
-                    .buildUpon().query(data.toString())
-                    .build().toString();
-            return HttpRequest.get(dataURL).ok();
-        }
-
-        @Override
-        public void onCatch(Throwable caught) {
-            // Do nothing bad. EL data is ok to lose.
-            Log.d("Wikipedia", "Lost EL data: " + data.toString());
-        }
-    }
-}

-- 
To view, visit https://gerrit.wikimedia.org/r/126849
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I6225c3df43f739b38f145669b4ae7146735a6383
Gerrit-PatchSet: 9
Gerrit-Project: apps/android/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Yuvipanda <yuvipa...@gmail.com>
Gerrit-Reviewer: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to