Article feedback/Version 5/Technical Design

This page describes technical designs for the Article Feedback Tool Version 5 (AFT V5).

See also: feature requirements page, project overview page, useful links, as well as data and metrics plan.

Overview
The front end of the AFTv5 extension builds upon the ArticleFeedback extension: on article pages, a set of javascript modules attach a new div to the bottom of the page and fill it with a feedback form. On submission, the form is replaced by a CTA (call to action). Most of this code has been rewritten to allow for the selection of one of a set of possible forms, and one of a set of possible CTAs. Right now, form options 1, 4, and 6 are available, and CTA 4, 5, and 6 being active (in part depending of whether the user is logged in or out).

The back end is entirely new: rather than displaying aggregate data, the Special page shows a feed of the responses to the article passed in to the url: /Special:ArticleFeedbackv5 for all feedback, /Special:ArticleFeedbackv5/Page for feedback for a certain page, or /Special:ArticleFeedbackv5/Page/FeedbackId for a specific feedback entry.

Database schema


"More detailed information is available on the schema documentation page"

Cache
Pretty much all data is meant to be read from cache. As soon as feedback is added or updated, the cached values will be updated immediately. Look at inline  comments if you're interested in the internals.

Upgrading and Maintenance
AFTv5 can be upgraded using.

If you are running are running AFTv5 from a different database, however, instead of running maintenance/update.php, you will need to manually apply updates though. SQL update files can be applied using. Maintenance scripts will read the correct cluster information from your config, so you won't have to explicitly specify the cluster there.

The upgrade hook is located in, in the   method. If you are bringing in new schema changes, you need to:


 * 1) Create a SQL upgrade file in the   directory
 * 2) Add a   call to   indicating what to check for and what file to run if it's missing.

There are two maintenance scripts available:


 * will purge all the cached data, forcing all data to be re-fetched from the database.
 * can be temporarily called (e.g. as a cronjob) to archive feedback that has not been moderated for a certain amount of time (as defined by  and  )

Other scripts in AFTv5's maintenance folder are update scripts, and are not meant to be run other otherwise.

Startup
AFTv5 mainly consists of three parts: a frontend form (displayed on pages in the namespaces defined in ), a link to the feedback page (displayed on corresponding talk pages), and the feedback page itself.

Actually, there's a fourth part: there's also a link to the feedback page from a user's watchlist (if there is feedback for his watchlisted pages). That feedback page will only show feedback from pages on that person's watchlist. For scaling reasons, it is not encouraged to enable this on wikis with a large number of pages.

Each of these parts can only appear when the main article falls within the configurable parameters. Below is a list of them, in order of precedence (the higher options will override the lower):


 * Not disabled via user preferences (AFT appears if other checks pass)
 * Category blacklist (AFTv5 never appears).
 * Page protection level: feedback can be enabled or disabled for in /Page?action=protect (fined-grained control for administrators), or from the special page directly via an API call (for editors).
 * Category whitelist (AFTv5 appears)
 * Lottery (AFTv5 appears if the page ID falls within range allowed)

We also have to make sure that the user's browser is supported.

All of these checks are performed both in JavaScript (when loading the form) and PHP (when submitting feedback). The reason for duplicating the verification checks in JavaScript is cached page outputs. If we were to perform the lottery check in PHP, to see if AFTv5 should by default be enabled on a certain page, this would work just fine. If that check works out, we could initialize our JavaScript. The problem, however, is that the result of this check may be cached. If something like Varnish is used to cache the page output, it will simply respond the output that was previously generated, not process the verification check again.

If at some point, we change the lottery-based check, we'd have to wait until the cached output for this page either expires or gets purged. For this reason, these verification checks are also included in JavaScript. E.g. we can calculate if an article wins the lottery based by the page id, a variable that will not change, will not be affected by any cached output. JavaScript has a reasonably short cache timeout compared to the back end, which only clears cache when the page is edited -- lower-traffic pages could take up to 30 days to clear.

Through PHP, we'll expose all of the required information to JavaScript in, in the   method (e.g. for talk pages, which can't just read the relevant article's id from JavaScript). Caution: this exposed data may be outdated cache!

The relevant information can be found in JavaScript through. (where location can be "article", "talk" or "special") is called to verify if AFTv5 can be displayed.


 * Articles:
 * Loads module
 * Loads module
 * Loads module
 * Talk pages:
 * Loads module
 * Special page:
 * Loads module
 * Loads module
 * Watchlist page:
 * Loads module

On the article and talk pages, AFT being not enabled will just stop the relevant JavaScript from loading and the page will appear as normal. On the special page, the entire contents are removed and replaced with an error message saying that AFT is not enabled for the requested page -- or, if it was just the user agent check that failed, the error message will say that.

Frontend
If startup is successful, the primary module (ext.articleFeedbackv5.js) is loaded. The primary module attaches a new div to the bottom of the page and invokes the jQuery plugin (jquery.articleFeedbackv5.js) on it -- if the user scrolls to the bottom of the page, they'll see the form there; if they click the toolbox link or one of the optional prominent links, they'll be scrolled down to the feedback form. The particular form the user sees, and the presence/location of the optional link, are chosen via bucketing.

If the user hits the submit button, it runs an error check and, if there are no issues, invokes an AJAX request. Upon successful return, the form is replaced with a CTA (call to action), which is also chosen via bucketing.

Flow

 * 1) Startup phase
 * Determines whether to display the tool :
 * If all is well, it loads the main module.
 * 1) Main phase
 * Creates a new div, inserts it at the bottom of the article, and invokes the jQuery plugin on it
 * Selects a trigger link option and adds it to the page (if any)
 * * NB: The click event calls the plugin's open-as-modal event
 * 1) Init phase (, method  )
 * Sets some state variables, chooses a display option, and binds the "appear" event to the holder div
 * 1) Load phase (, method  )
 * Sets up the bottom of the page and overlay containers, builds the selected form, and puts it in the appropriate container
 * 1) Submit phase (, method  )
 * Runs validation, locks the form, and sends off an ajax request
 * 1) Submit response phase (, method  )
 * On success, selects a CTA and loads it into the appropriate container, and removes the feedback link(s)
 * On error, sets an error state and unlocks the form

Query string options
You can choose a form and/or a trigger link option and avoid bucketing by passing the following in the url:

Form Bucketing Process


Bucketing basically works like this:


 * 1) If the plugin is in the debug state, and a form has been requested in the url, and that value is known, select it.
 * 2) Call , which will:
 * 3) Retrieve a previously selected value (unless already expired) from a cookie, or
 * 4) Select a value based on the given (percentage) odds, and save that value to a cookie
 * 5) Verify the selected value is valid (e.g. CTA4 should not be displayed to logged-in users), and if not, fallback to a valid value (based on what's enabled via bucketing)

When bucketing config (e.g. percentages) are updated, be sure to update the 'version' number. Otherwise, values previously saved to the cookie will continue to be read.

Currently, bucketing is used for:


 * : CTA buckets expire immediately. Instead of reading a previously selected value from cookie, one will be re-generated every time. As a result, people may get to see different CTA's each and every time.
 * : This is meant to enable clicktracking only for a select amount of visitors. Although the infrastructure is still there, clicktracking is no longer implemented.
 * : CTA buckets expire immediately. Instead of reading a previously selected value from cookie, one will be re-generated every time. As a result, people may get to see different CTA's each and every time.
 * : This is meant to enable clicktracking only for a select amount of visitors. Although the infrastructure is still there, clicktracking is no longer implemented.

Submit AJAX call
This call is made when the user submits any of the feedback forms.

Call can be found at:

, method

Response can be found at:

Parameters:

Success response:

JSON object with one key,, containing this object:

Error response:

JSON object with one key,, containing the   and   for the appropriate error.

Abuse Filtering


AFT makes use of, SpamBlacklist, and AbuseFilter. You can turn abuse filtering on by setting  to true. If any of these three are available they will be used to filter out abusive feedback. See flowchart for details.

,, and   can be set to AFTv5-specific values for AbuseFilter's emergency shutdown. The emergency shutdown mechanism was intended to disable filters that are hit too often (to make sure that a broken filter does not keep blocking edits). Since AFTv5 feedback is very accessible, it will likely solicit more unproductive feedback and as a result, it could make perfect sense for filters to be triggered much more compared to regular page edits.

Actions
AFT makes use of AbuseFilter's "disallow" and "warn" actions, and it provides custom actions to automatically flag incoming feedback:

Creating Filters
AFT makes use of AbuseFilter's new "group" feature -- any filters set to "feedback" will be processed; other ones will be ignored.

When you set up filters for AFT, you should:


 * 1) Be cautious about the filter name; if feedback offends a filter, the filter name will be displayed in the error message.
 * 2) Set "feedback" as the group
 * 3) Write conditions appropriate for feedback
 * 4) Select one of the action options above

Writing filters is just as risky for feedback as it is for edits. You should read through the AbuseFilter conditions documentation carefully before proceeding.

You will have access to the following variables when writing conditions:

If you're adapting an edit filter to work with feedback, you should change conditions checking  to use. You can remove any conditions related to the previous state of the article (e.g., ).

You can test these filters just as you would the edit filters, by watching the AbuseFilter log.

Feedback list
will initially load the first batch of feedback entries for the requested parameters: filter & sort, which are read from user preferences/cookie and saved there as soon as a user selects different ones. Upon choosing another filter or sort, or simply loading the next batch of feedback, will result in an AJAX call to  (from the   function in  ), which will respond with a JSON object that (among other values) contains the requested batch of feedback rendered in HTML. This provides for paginated (incremental) display of feedback without the need to reload the entire page.

The  function pulls the highlighted post(s) separately.

Feedback post actions
Implementation of actions for the special page revolves around the  array of objects. Each array element represents an action, keyed by the action name, and contains an object with the following structure:
 * hasTipsy - true if the action needs a flyover panel
 * tipsyHtml - html for the corresponding flyover panel
 * click - code to execute after clicking the element (after tipsy has been opened already, if applicable)
 * onSuccess - callback to execute after flagging action success. Callback parameters:
 * id - respective post id
 * data - any data returned by the AJAX call

These actions are tied to elements by adding the class  ("action" being the name of the key in the JS object). If the action is supposed to open a tipsy, you'll also have to add class.

Most actions will result in an API call to ArticleFeedbackv5Flagging, which will call the appropriate method to change the feedback's status. Because opening tipsy panels has been bound to this, some none-flagging related code will use this same structure (like activity, discuss or settings).

The following actions are implemented:

The actual AJAX call for flagging actions is performed by the  function. The function locks the flagging actions for its duration to avoid multiple simultaneous requests.

User activity tracking
For lack of account details to tie the details to, anonymous users' activity on the special page is stored client-side, via cookies. This functionality provides some limitation on actions repetition by the user: if a user has marked feedback as abusive, we'll no longer want to present that action, but only the possibility to undo that flag. This is done using  in.

The user's filter & sort state will also be saved to a cookie.

Filters
The feedback page allows the user to see feedback grouped according to a set of filters. These are available according to permissions (e.g., to see hidden or deleted feedback).

Filters are "defined" in  in , which comprises of a key (filter name) => value (filter details) map for all filters. The value is an array which holds the keys "permissions" (which can be any of the in  defined permission levels) and "conditions" (which is an array of WHERE-statements to be executed against the DB.)

Example:

// filter name = 'helpful' (i18n messages will be articlefeedbackv5-special-filter-helpful and articlefeedbackv5-special-filter-helpful-watchlist) 'helpful' => array(       // everyone with aft-editor permissions can see this list        'permissions' => 'aft-editor',        // feedback for this list has to have a comment, not be oversighted, archived or hidden, and be voted helpful at least once        'conditions' => array( 'aft_has_comment = 1', 'aft_oversight = 0', 'aft_archive = 0', 'aft_hide = 0', 'aft_net_helpful > 0' ),    ),

To retrieve the feedback for a filter, you'll call. The retrieve the amount of feedback in a filter, you'll call. Both will take  as first argument, which is the name of the filter. is the second argument: this is the ID of the page you want to fetch feedback (or the amount of feedback) for, or null for feedback for all pages.

will return a DataModelList, which is a ResultWrapper. To fetch the next batch of feedback, you'll provide  with the result of the  : this the value to indicate where the next batch should start.

Example:

// will return a DataModelList of 50 helpful feedback posts, for page with ID 1 $feedback1 = ArticleFeedbackv5Model::getList( 'helpful', 1 ); // will return a DataModelList of the next 50 helpful feedback posts, for page with ID 1 $feedback2 = ArticleFeedbackv5Model::getList( 'helpful', 1, $feedback1->nextOffset ); // feedback can be iterated over foreach ( $feedback1 as $post ) { /* ... */ }   // will return the amount of helpful feedback posts, for page with ID 1 $count = ArticleFeedbackv5Model::getCount( 'helpful', 1 );

Logging
"More detailed information is available on the schema documentation page"

Central activity log (https://en.wikipedia.org/w/index.php?title=Special%3ALog&type=articlefeedbackv5) calls upon class  to format the AFTv5 entries. This class extends from the default LogFormatter class and adds in some additional details to the entry (feedback text, page title & link, feedback n° & link)

The actions being logged are defined in, which is a key (action name) => value (action details) array. The action details is an array with keys:
 * permissions: Not all actions can be performed by all users; e.g. not everyone should be able to oversight feedback. This can be any of the permissions defined in
 * sentiment: (positive|negative|neutral) depending on the sentiment, they'll be depicted on a different color in the activity log (green|red|gray)
 * log_type: the value for log_type in logging table. This will usually be articlefeedbackv5, but for more sensitive information (oversight-related) we'll add them to the suppression log.

The logging of all activity happens through a function call to ApiArticleFeedbackv5Utils::logActivity (ApiArticleFeedbackv5Utils.php) which will both insert the log entry, and increment the activity count on an entry (joining with the logging table is complex and not desired on such huge dataset).

The "view activity" link in the feedback page toolbox (both on central feedback page & permalink) also fetches its data from this logging table.

is the wrapper method to call when inserting log entries. This method performs some additional checks and will build the final data to be inserted.

Metrics / Clicktracking
AFTv5 used to use the Extension:ClickTracking extension to track how the extension is being used. The groundwork in AFTv5 is still there, but support for ClickTracking has been removed. If at some point one would like to track events (e.g. Extension:EventLogging), calls could be added in function  in   and   in. Or perhaps EventLogging is conceptually very different from ClickTracking in that it doesn't even make sense to use the groundwork ClickTracking used, I don't know.

Cookies
Below is a full list of cookies currently in the code. Not all of them are used though, but in that case, it's indicated below.

Unless indicated otherwise, the cookies are only used in JavaScript, not processed serverside.

Article page
AFTv5-feedback-ids This will store the ids of the last 20 feedbacks submitted by a user. This is mostly used to hide moderation tools, to discourage people to upvote their own posts. This cookie can also be used server-side in case a user submits anonymous and subsequently logs in via the “register” or “login” links in CTA4 (call to action number 4). After logging in, we'll “claim” the feedback to the user. This is usually done by passing the id as GET-parameter, but if multiple steps are needed to login (wrong password), the data from the url is lost, and we'll look at the last id in this cookie.

AFTv5-submission_timestamps Will save the timestamps of submitted feedback, used to show a throttling message if a user posts too much feedback too fast (20 feedback per hour)

clicktracking-session Identifier to be able to track how people use the tool. ClickTracking is no longer enabled though, so this cookie is not being set.

mediaWiki.user.bucket:ext.articleFeedbackv5@ -form AFT has multiple form variants. MW's core bucketing is used to select the active form from the percentages defined in $wgArticleFeedbackv5DisplayBuckets & saves the result to cookie to make sure that people will consistently see the same form (although for WMF's sites, only the 2-step form 6 is currently active)

mediaWiki.user.bucket:ext.articleFeedbackv5@ -links AFT has multiple links. MW's core bucketing is used to select the active link from the percentages defined in $wgArticleFeedbackv5LinkBuckets & saves the result to cookie to make sure that people will consistently see the same link (although for WMF's sites, the only active link currently is the link in the toolbox)

mediaWiki.user.bucket:ext.articleFeedbackv5@ -cta AFT has multiple CTAs (call to actions). MW's core bucketing is used to select the active CTA from the percentages defined in $wgArticleFeedbackv5CTABuckets; the cookie expires immediately though (we actually do want CTAs to rotate, instead of consistently displaying the same CTA)

mediaWiki.user.bucket:ext.articleFeedbackv5@ -tracking When gathering usage metrics, only a certain percentage of visits were tracked. MW's core bucketing was used to select if a certain visitor would or would not get tracked (based on percentages defined in $wgArticleFeedbackv5Tracking). ClickTracking is no longer enabled though, so this cookie is not being set.

Special page
AFTv5-last-filter Will save the last selected filter/sort, so that when someone comes back later, he'll see that same filter instead of the default. This cookie will be used server-side to load the previously selected filter & sort.

AFTv5-aft-activity This will store the last 100 actions (helpful, unhelpful, flag, request) performed on feedback by the user, so reflect the correct status when browsing feedback.

mediaWiki.user.bucket:ext.articleFeedbackv5@ -tracking Same cookie as already mentioned for article page. Unused now.

Platforms
We aim to support the following web browsers for phases 1.0 and 1.5:
 * Internet Explorer 7+
 * Firefox 3+
 * Safari 5+
 * Opera 10+
 * Chrome 5+

We will focus our testing on these top browser versions for Wikipedia:
 * IE 8			(17%)
 * Chrome 14	       (16%)
 * Firefox 7		(11%)
 * IE 7			(7%)
 * IE 9			(6%)
 * Firefox 3		(5%)
 * Firefox 6		(2%)

These will be tested on these desktop platforms:
 * Windows	 (78%)
 * Mac		 (8%)
 * Linux		 (3%)

We are not currently planning to support IE6 or mobile platforms for phases 1.0 and 1.5, and will not show the forms at all on these unsupported platforms. In future versions, we could aim for 'graceful degradation' in unsupported platforms, and are working on a good definition for testing that objective.

Unsupported browsers should not get to see the feedback form (on the article page) or links to the feedback page (on the talk page). The feedback page should show an error message that the browser is not supported. The user-agent based browser support checks live in  in.