Skip to content

Commit

Permalink
For mozilla-mobile#12184 - Add support for pacing and rotating sponso…
Browse files Browse the repository at this point in the history
…red Pocket stories

Stories can be paced and rotated based on the properties received in the
endpoint response.

Since rotating involves a limit of impressions in a certain period I've added
a new table for keeping only this timestamps while the effective limits will be
held in the sponsored stories table.

With very little time between this and the previous patch which added support
for sponsored stories I've skipped created a new database version and sticked
to using version 2 again to ensure a smoother migration when this feature gets
to the users.

Possibly because of the foreignKey addition the migration could not be tested
in the JVM (because of sqlite exceptions coming from robolectric) and so I
switched testing this to a real device.
  • Loading branch information
Mugurell committed May 24, 2022
1 parent 12639e1 commit fb2a833
Show file tree
Hide file tree
Showing 29 changed files with 1,773 additions and 186 deletions.
26 changes: 17 additions & 9 deletions components/service/pocket/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@ Currently this supports:

## Usage
1. For Pocket recommended stories:
- Use `PocketStoriesService#startPeriodicStoriesRefresh` and `PocketStoriesService#stopPeriodicStoriesRefresh`
as high up in the client app as possible (preferably in the Application object or in a single Activity) to ensure the
background story refresh functionality works for the entirety of the app lifetime.
- Use `PocketStoriesService.getStories` to get the current list of Pocket recommended stories.
- Use `PocketStoriesService#startPeriodicStoriesRefresh` and `PocketStoriesService#stopPeriodicStoriesRefresh`
as high up in the client app as possible (preferably in the Application object or in a single Activity) to ensure the
background story refresh functionality works for the entirety of the app lifetime.
- Use `PocketStoriesService.getStories` to get the current list of Pocket recommended stories.

2. For Pocket sponsored stories:
- Use `PocketStoriesService#startPeriodicSponsoredStoriesRefresh` and `PocketStoriesService#stopPeriodicSponsoredStoriesRefresh`
as high up in the client app as possible (preferably in the Application object or in a single Activity) to ensure the
background story refresh functionality works for the entirety of the app lifetime.
- Use `PocketStoriesService.getSponsoredStories` to get the current list of Pocket recommended stories.
- Use `PocketStoriesService.deleteProfile` to delete all server stored information about the device to which sponsored stories were previously downloaded. This may include data like network ip and application tokens.
- Use `PocketStoriesService#startPeriodicSponsoredStoriesRefresh` and `PocketStoriesService#stopPeriodicSponsoredStoriesRefresh`
as high up in the client app as possible (preferably in the Application object or in a single Activity) to ensure the
background story refresh functionality works for the entirety of the app lifetime.
- Use `PocketStoriesService.getSponsoredStories` to get the current list of Pocket recommended stories.
- Use `PocketStoriesService,recordStoriesImpressions` to try and persist that a list of sponsored stories were shown to the user. (Safe to call even if those stories are not persisted).
- Use `PocketStoriesService.deleteProfile` to delete all server stored information about the device to which sponsored stories were previously downloaded. This may include data like network ip and application tokens.

##### Pacing and rotating:
A new `PocketSponsoredStoryCaps` is available in the response from `PocketStoriesService.getSponsoredStories` which allows checking `currentImpressions`, `lifetimeCount`, `flightCount`, `flightPeriod` based on which the client can decide which stories to show.
All this is based on clients calling `PocketStoriesService,recordStoriesImpressions` to record new impressions in between application restarts.




### Setting up the dependency

Expand Down
10 changes: 10 additions & 0 deletions components/service/pocket/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ android {
defaultConfig {
minSdkVersion config.minSdkVersion
targetSdkVersion config.targetSdkVersion
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

kapt {
arguments {
Expand All @@ -29,6 +30,7 @@ android {

sourceSets {
test.assets.srcDirs += files("$projectDir/schemas".toString())
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}

Expand Down Expand Up @@ -58,6 +60,14 @@ dependencies {

testImplementation project(':support-test')
testImplementation project(':lib-fetch-httpurlconnection')

androidTestImplementation project(':support-android-test')

androidTestImplementation Dependencies.androidx_room_testing
androidTestImplementation Dependencies.androidx_arch_core_testing
androidTestImplementation Dependencies.androidx_test_core
androidTestImplementation Dependencies.androidx_test_runner
androidTestImplementation Dependencies.androidx_test_rules
}

apply from: '../../../publish.gradle'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "966f55824415a21a73640bd2641772f2",
"entities": [
{
"tableName": "stories",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `imageUrl` TEXT NOT NULL, `publisher` TEXT NOT NULL, `category` TEXT NOT NULL, `timeToRead` INTEGER NOT NULL, `timesShown` INTEGER NOT NULL, PRIMARY KEY(`url`))",
"fields": [
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "imageUrl",
"columnName": "imageUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "publisher",
"columnName": "publisher",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "category",
"columnName": "category",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "timeToRead",
"columnName": "timeToRead",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timesShown",
"columnName": "timesShown",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"url"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "spocs",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `imageUrl` TEXT NOT NULL, `sponsor` TEXT NOT NULL, `clickShim` TEXT NOT NULL, `impressionShim` TEXT NOT NULL, `priority` INTEGER NOT NULL, `lifetimeCapCount` INTEGER NOT NULL, `flightCapCount` INTEGER NOT NULL, `flightCapPeriod` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "url",
"columnName": "url",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "imageUrl",
"columnName": "imageUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "sponsor",
"columnName": "sponsor",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "clickShim",
"columnName": "clickShim",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "impressionShim",
"columnName": "impressionShim",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "priority",
"columnName": "priority",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lifetimeCapCount",
"columnName": "lifetimeCapCount",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "flightCapCount",
"columnName": "flightCapCount",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "flightCapPeriod",
"columnName": "flightCapPeriod",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "spocs_impressions",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`spocId` INTEGER NOT NULL, `impressionId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `impressionDateInSeconds` INTEGER NOT NULL, FOREIGN KEY(`spocId`) REFERENCES `spocs`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "spocId",
"columnName": "spocId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "impressionId",
"columnName": "impressionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "impressionDateInSeconds",
"columnName": "impressionDateInSeconds",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"impressionId"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": [
{
"table": "spocs",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"spocId"
],
"referencedColumns": [
"id"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '966f55824415a21a73640bd2641772f2')"
]
}
}
Loading

0 comments on commit fb2a833

Please sign in to comment.