-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #75 from wmde/docs
Add Documentation
- Loading branch information
Showing
6 changed files
with
204 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Banner Conductor | ||
|
||
The Banner Conductor is the root Vue component and is responsible for handling the top level banner states, and is the gateway for interactions between the banners and the [pages](Page.md) they are displayed on. It is largely invisible to the developers. | ||
|
||
Internally it uses a [Finite State Machine](https://en.wikipedia.org/wiki/Finite-state_machine) (FSM). | ||
|
||
These states perform various tasks such as: | ||
|
||
* Measuring impressions | ||
* Sending tracking events | ||
* Handling page resize events | ||
|
||
You can get an overview of each state's dependencies by looking in the `StateFactory`. | ||
|
||
Each state has 2 lifecycle methods `enter()` and `exit()` which are called automatically when the state is entered and exited. These methods return promises and allow the states to be run in an asynchronous manner, for example the `Pending` state will only resolve its promise when the banner delay timer has completed. | ||
|
||
This is the state flow: | ||
|
||
```mermaid | ||
stateDiagram-v2 | ||
[*] --> Initial | ||
Initial --> Pending | ||
Pending --> Showing | ||
Pending --> NotShown: has size issue | ||
Showing --> Visible | ||
Visible --> Hidden | ||
NotShown --> [*] | ||
Hidden --> [*] | ||
``` | ||
|
||
The states are as follows: | ||
|
||
* **Initial** This state does nothing, it is used as a default state when the FSM is instantiated. | ||
* **Pending** This pre-sets the banner height on the Page and starts the banner display timer. | ||
* **NotShown** When there is a size issue, or if the user interacts with certain elements on the page while the banner display timer is running the FSM will move to this state at the timer end. It will then mark the banner as not shown and fire tracking events. | ||
* **Showing** This is the banner transition phase. It is in this state when the banner is animating into the page. | ||
* **Visible** The banner is now finished transition and is visible to the user on the page. It will update the display counts and trigger the displayed tracking events when entered. _Note: This does not control which part of the banner is visible, that is the responsibility of the banner components._ | ||
* **Hidden** When the user has closed the banner. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Dynamic Content | ||
|
||
The campaign text is mostly static but contains some dynamic content. This content consists of things like: | ||
|
||
* The number of days left in the campaign. | ||
* The current day name. | ||
* The average donation. | ||
* The average number of donors. | ||
|
||
This content is used to generate sentences that are displayed as part of the banner text content, and as values for the progress bar. | ||
|
||
## Structure | ||
|
||
### Generators | ||
These are for building the sentences and progress bar items. There is a single generator per dynamic text item. | ||
|
||
### Formatters | ||
These are tools used by the generators for localising numbers depending on if the banners are in German or English. | ||
|
||
### Campaign Projection | ||
The campaign team measures the progress of the campaign and uploads the current measurements every few days. We use these measurements to project the numbers displayed in the dynamic text. The calculation starts at the last upload time up to the point in time when the user sees the banner. The CampaignProjection is responsible for doing the calculations for this projection. | ||
|
||
### Dynamic Campaign Text | ||
The DynamicCampaignText class acts as a factory that instantiates the various generators. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Multi Step Donation Form and Form Controllers | ||
|
||
Since the 2022 campaign, a lot of tests had multistep forms, each one with a different flow. These tests are different enough functionally that they would require a new `DonationForm` component for each. | ||
|
||
In order to avoid duplicating components per-test we opted to instead create a flexible `MultiStepDonationForm` that gets each form step as a named slot. Each named slot also gets an implementation of a `StepController`, which contains the logic that determines which form page to show, based on the user interaction with the current form page. | ||
|
||
This module consists of the following parts: | ||
|
||
## `MultiStepDonationForm.vue` | ||
|
||
This initialises and presents the form: | ||
1. It connects the individual form pages to the `StepController` instances to handle the flow of the submissions. | ||
2. It wraps the `FormStep` components in its slots in a `Slider` component and starts the slides at the first slide. | ||
3. From then on it acts as a bus between the `StepController` instances, `Slider` and the `FormSteps`. | ||
|
||
## `FormModel` | ||
|
||
This is a [Vuejs composable](https://vuejs.org/guide/reusability/composables.html) and contains the form state. It is globally available. This contains only the data that will be posted to the Fundraising Application and should not be modified for a single test. | ||
|
||
## `FormSteps` | ||
|
||
A banner form consists of one or more step components (components containing with form elements). Each step: | ||
|
||
* Is in the directory `src/components/DonationForm/Forms` | ||
* Handles its own validation. | ||
* Emits a `submit` or `previous` event with data. The `MultiStepDonationForm` passes the events to the `StepController` and allows it to decide what to do next. | ||
* Optionally resets state when they are entered or exited. For example if a user hits the back button we reset some forms to their default state, and we expect the user to fill them out again to proceed. The form can watch its `isCurrent` property to detect when it becomes the current step. | ||
* Optionally modify the `FormModel` directly. The `FormModel` contains only data that will be posted to the Fundraising Application. Some form steps, like the `MainDonationForm` have fields that directly correspond to `FormModel` properties and will change it directly. Other forms, however, are for modifying this data as a side effect of the `submit` and `previous` actions. These forms pass the extra data to their `StepController` and it will decide what should change in the `FormModel`. | ||
|
||
## `StepController` | ||
|
||
There are multiple implementations of this interface, at least one per form step. It is responsible for handling the flow of the submission, instructing the `MultiStepDonationForm` either to submit or go to a different step. | ||
|
||
Each controller instance has two methods, called by `MultiStepDonationForm`: | ||
* `submit` This is called when a sub-form has been submitted. The data passed has already been validated. It may get extra data about the user input from the form step. | ||
* `previous` This is called when the user clicks the back button. | ||
|
||
The `MultiStepDonationForm` passes the following callbacks into each StepController: | ||
* `goToStep` makes the `MultiStepDonationForm` go to a different page with the specified name. `MultiStepDonationForm` builds an internal mapping between the step names and the step index needed by its `Slider` component. | ||
* `submit` makes the `MultiStepDonationForm` submit the current values of `FormModel` via the `SubmiForm` (see below). | ||
|
||
Each `StepController` has a factory function that provides it with the slot names of the possible other steps it can go to. | ||
The `StepController` factory functions follow the following naming schema: | ||
`create<SubmitBehavior><StepName><OptionalSpecifier>` | ||
|
||
* `<SubmitBehavior>` can either be `Intermediate` (meaning the step will not submit, but go to a different form step) or `Submittable` (meaning the controller will call `submit` or *may* go to a different step in some cases). | ||
* `<StepName>` is the name of the form component, e.g. `MainDonationForm` or `AddressTypeForm`. | ||
* `<OptionalSpecifier>` is an optional description of the controller behavior, e.g, `SinglePage` | ||
|
||
Example factory functions: `createSubmittableAddressType`, `createIntermediateUpgradeToYearly`, `createSubmittableMainDonationFormSinglePage` | ||
|
||
## `SubmitForm` | ||
|
||
This is a hidden form. It contains the values from the `FormModel` as hidden fields and is submitted by the `MultiStepDonationForm` when a `StepController` invokes the `submit` callback. This data is the POSTed to the Fundraising Application as a standard HTTP POST request. | ||
|
||
## In Summary | ||
|
||
* The `MultistepDonationForm` acts like an event bus and keeps track which page is the current one. | ||
* The `FormModel` contains the form state values. | ||
* The `StepControllers` handle the form submission logic. | ||
* The `FormSteps` handle their own validation and fire submit/previous events. | ||
* Some `FormSteps` modify the model state directly or pass their data to the `StepController` for extra computing. | ||
* The `SubmitForm` has hidden form fields with `FormModel` values and POSTs to the Fundraising Application. | ||
|
||
```mermaid | ||
stateDiagram-v2 | ||
FormModel: Globally available FormModel | ||
StepControllers --> MultiStepDonationForm | ||
MultiStepDonationForm --> FormStepOne | ||
MultiStepDonationForm --> FormStepTwo | ||
MultiStepDonationForm --> FormStepThree | ||
MultiStepDonationForm --> SubmitForm: Used to post data to Fundraising Application | ||
``` | ||
|
||
Below is an example happy path user flow. | ||
|
||
```mermaid | ||
sequenceDiagram | ||
participant MultiStepDonationForm | ||
participant FormStep1 | ||
participant StepController1 | ||
participant FormStep2 | ||
participant StepController2 | ||
participant SubmitForm | ||
FormStep1 ->> MultiStepDonationForm: user submits step 1 | ||
MultiStepDonationForm ->> StepController1: call `submit` on controller | ||
StepController1 ->> MultiStepDonationForm: calls `goToStep` callback | ||
MultiStepDonationForm ->> FormStep2: shows next step | ||
FormStep2 ->> MultiStepDonationForm: user submits step 2 | ||
MultiStepDonationForm ->> StepController2: calls `submit` on controller | ||
StepController2 ->> MultiStepDonationForm: calls `submit` callback | ||
MultiStepDonationForm ->> SubmitForm: posts form | ||
``` | ||
|
||
## Notes for the future | ||
|
||
This module isn't perfect. Depending on the direction of the future campaign tests we might improve some things about it. | ||
|
||
* The form user flows are badly designed. For example, we don't know the reasoning why some forms clear their own state on back. This needs to be clarified before a redesign happens. | ||
* `FormSteps` Some of these modify the `FormModel` directly, some pass extra data so the `StepController` can modify this data. These steps could be changed to each contain their own state and the `StepController` becomes responsible for changing all `FormModel` state, meaning the `FormModel` no longer needs to be so global. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# The Page Classes | ||
|
||
We display banners on wikipedia.org and wikipedia.de. These sites have different HTML markup, have a different campaign configuration structure, and have different ways of logging events. We use a Page interface with 2 implementations which ensures our code is compatible with these 2 environments. | ||
|
||
They are used for: | ||
|
||
* Mounting the banners in the correct place. | ||
* Tracking events. | ||
* Passing the calculated banner height from our banner javascript to css. | ||
|
||
In our entry points we give the BannerConductor one of these implementations to perform actions as needed. | ||
|
||
## PageOrg (Used on wikipedia.org) | ||
This implementation is for interacting with wikipedia.org and is a little complex. | ||
|
||
### The mw Object | ||
ikipedia.org provides the global `mw` JavaScript object. For PageOrg we wrap the object in a `MediaWiki` interface and use it for things like: | ||
|
||
* Discovering which skin the user has active. | ||
* Discovering the current namespace of the page the banner is being showed on. | ||
* Notifying Central Notice that a user has seen or interacted with a banner. | ||
* Tracking events with Wikipedia's tracking tools. | ||
|
||
### Skins | ||
|
||
There are multiple skins that our banners can appear on: | ||
|
||
* **Vector** This skin is the default on dewiki. | ||
* **Vector 2022** This skin is the default on enwiki. | ||
* **Minerva** This is the default mobile skin. | ||
|
||
Each one of these skins has different markup and the users will interact with different parts of the skins that may prevent banner display so this needs to be set up per-skin. | ||
|
||
## PageDe (Used on wikipedia.de) | ||
This is the simplest of the 2 implementations. We use it only to mount the banner and send tracking events to our Matomo analytics. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters