Skip to content

Commit 2f3f9cd

Browse files
authored
Bug 1983587 - Namespace dismissals of dynamic suggestions by suggestion_type (#6902)
* Bug 1983587 - Namespace dismissals of dynamic suggestions by suggestion_type * address comments
1 parent 54f7210 commit 2f3f9cd

File tree

4 files changed

+189
-37
lines changed

4 files changed

+189
-37
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
- Added `SearchEngineUrl::exclude_partner_code_from_telemetry` ([bug 1980474](https://bugzilla.mozilla.org/show_bug.cgi?id=1980474))
2626
- Added `SearchEngineUrl::accepted_content_types`
2727

28+
### Suggest
29+
30+
- Added namespacing by `suggestion_type` to dismissals of dynamic suggestions ([bug 1983587](https://bugzilla.mozilla.org/show_bug.cgi?id=1983587))
31+
2832
### RC Crypto
2933
- Fix NSS bindings for key management
3034

components/suggest/src/db.rs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,7 @@ impl<'a> SuggestDao<'a> {
517517

518518
fn fetch_categories_for_suggestion(&self, suggestion_id: i64) -> Result<Vec<i32>> {
519519
let mut category_stmt = self.conn.prepare(
520-
r#"
520+
r#"
521521
SELECT category FROM serp_categories WHERE suggestion_id = :suggestion_id
522522
"#,
523523
)?;
@@ -905,7 +905,10 @@ impl<'a> SuggestDao<'a> {
905905
s.provider = ?
906906
AND k.keyword = ?
907907
AND d.suggestion_type IN ({})
908-
AND NOT EXISTS (SELECT 1 FROM dismissed_suggestions WHERE url = s.url)
908+
AND NOT EXISTS (
909+
SELECT 1 FROM dismissed_dynamic_suggestions
910+
WHERE dismissal_key = s.url AND suggestion_type = d.suggestion_type
911+
)
909912
ORDER BY
910913
s.score ASC, d.suggestion_type ASC, s.id ASC
911914
"#,
@@ -1205,8 +1208,23 @@ impl<'a> SuggestDao<'a> {
12051208
Ok(())
12061209
}
12071210

1211+
pub fn insert_dynamic_dismissal(&self, suggestion_type: &str, key: &str) -> Result<()> {
1212+
self.conn.execute(
1213+
"INSERT OR IGNORE INTO dismissed_dynamic_suggestions(suggestion_type, dismissal_key)
1214+
VALUES(:suggestion_type, :dismissal_key)",
1215+
named_params! {
1216+
":suggestion_type": suggestion_type,
1217+
":dismissal_key": key,
1218+
},
1219+
)?;
1220+
Ok(())
1221+
}
1222+
12081223
pub fn clear_dismissals(&self) -> Result<()> {
1209-
self.conn.execute("DELETE FROM dismissed_suggestions", ())?;
1224+
self.conn.execute_batch(
1225+
"DELETE FROM dismissed_suggestions;
1226+
DELETE FROM dismissed_dynamic_suggestions;",
1227+
)?;
12101228
Ok(())
12111229
}
12121230

@@ -1219,10 +1237,27 @@ impl<'a> SuggestDao<'a> {
12191237
)?)
12201238
}
12211239

1240+
pub fn has_dynamic_dismissal(&self, suggestion_type: &str, key: &str) -> Result<bool> {
1241+
Ok(self.conn.exists(
1242+
"SELECT 1
1243+
FROM dismissed_dynamic_suggestions
1244+
WHERE suggestion_type = :suggestion_type AND dismissal_key = :dismissal_key",
1245+
named_params! {
1246+
":suggestion_type": suggestion_type,
1247+
":dismissal_key": key,
1248+
},
1249+
)?)
1250+
}
1251+
12221252
pub fn any_dismissals(&self) -> Result<bool> {
1223-
Ok(self
1224-
.conn
1225-
.exists("SELECT 1 FROM dismissed_suggestions LIMIT 1", ())?)
1253+
Ok(self.conn.query_row(
1254+
"SELECT
1255+
EXISTS(SELECT 1 FROM dismissed_suggestions)
1256+
OR
1257+
EXISTS(SELECT 1 FROM dismissed_dynamic_suggestions)",
1258+
(),
1259+
|row| row.get(0),
1260+
)?)
12261261
}
12271262

12281263
/// Deletes all suggestions associated with a Remote Settings record from

components/suggest/src/schema.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use sql_support::{
2323
/// `clear_database()` by adding their names to `conditional_tables`, unless
2424
/// they are cleared via a deletion trigger or there's some other good
2525
/// reason not to do so.
26-
pub const VERSION: u32 = 43;
26+
pub const VERSION: u32 = 44;
2727

2828
/// The current Suggest database schema.
2929
pub const SQL: &str = "
@@ -260,6 +260,12 @@ CREATE TABLE geonames_metrics(
260260
CREATE TABLE dismissed_suggestions (
261261
url TEXT PRIMARY KEY
262262
) WITHOUT ROWID;
263+
264+
CREATE TABLE dismissed_dynamic_suggestions (
265+
suggestion_type TEXT,
266+
dismissal_key TEXT NOT NULL,
267+
PRIMARY KEY(suggestion_type, dismissal_key)
268+
) WITHOUT ROWID;
263269
";
264270

265271
/// Initializes an SQLite connection to the Suggest database, performing
@@ -825,6 +831,22 @@ impl ConnectionInitializer for SuggestConnectionInitializer<'_> {
825831
)?;
826832
Ok(())
827833
}
834+
43 => {
835+
clear_database(tx)?;
836+
tx.execute_batch(
837+
r#"
838+
CREATE TABLE dismissed_dynamic_suggestions (
839+
suggestion_type TEXT,
840+
dismissal_key TEXT NOT NULL,
841+
PRIMARY KEY(suggestion_type, dismissal_key)
842+
) WITHOUT ROWID;
843+
844+
INSERT INTO dismissed_dynamic_suggestions
845+
SELECT "vpn", url FROM dismissed_suggestions WHERE url = "vpn-suggestions";
846+
"#,
847+
)?;
848+
Ok(())
849+
}
828850

829851
_ => Err(open_database::Error::IncompatibleVersion(version)),
830852
}
@@ -1053,4 +1075,39 @@ PRAGMA user_version=16;
10531075

10541076
Ok(())
10551077
}
1078+
1079+
/// Test if dynamic vpn suggestions are migrated into the
1080+
/// dismissed_dynamic_suggestions table during the migration to schema 44.
1081+
#[test]
1082+
fn test_migrate_vpn_dismissal() -> anyhow::Result<()> {
1083+
let db_file =
1084+
MigratedDatabaseFile::new(SuggestConnectionInitializer::default(), V16_SCHEMA);
1085+
1086+
// Upgrade to v43 (just before the migration) and insert the suggestion that should be migrated.
1087+
db_file.upgrade_to(43);
1088+
let conn = db_file.open();
1089+
conn.execute(
1090+
"INSERT INTO dismissed_suggestions VALUES (?)",
1091+
("vpn-suggestions",),
1092+
)?;
1093+
conn.close().expect("Connection should be closed");
1094+
// Finish upgrading to the current version.
1095+
db_file.upgrade_to(VERSION);
1096+
db_file.assert_schema_matches_new_database();
1097+
1098+
// Check if the vpn suggestion was migrated.
1099+
let conn = db_file.open();
1100+
let mut stmt = conn.prepare("SELECT * FROM dismissed_dynamic_suggestions")?;
1101+
let mut rows = stmt.query([])?;
1102+
let row = rows.next()?.expect("Should have one row");
1103+
let suggestion_type: String = row.get(0)?;
1104+
assert_eq!(suggestion_type, "vpn", "Has correct suggestion_type");
1105+
let dismissal_key: String = row.get(1)?;
1106+
assert_eq!(
1107+
dismissal_key, "vpn-suggestions",
1108+
"Has correct suggestion_type"
1109+
);
1110+
assert!(rows.next()?.is_none(), "Should not have other rows");
1111+
Ok(())
1112+
}
10561113
}

components/suggest/src/store.rs

Lines changed: 86 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,15 @@ impl<S> SuggestStoreInner<S> {
474474

475475
fn dismiss_by_suggestion(&self, suggestion: &Suggestion) -> Result<()> {
476476
if let Some(key) = suggestion.dismissal_key() {
477-
self.dismiss_by_key(key)?;
477+
match suggestion {
478+
Suggestion::Dynamic {
479+
suggestion_type, ..
480+
} => self
481+
.dbs()?
482+
.writer
483+
.write(|dao| dao.insert_dynamic_dismissal(suggestion_type, key))?,
484+
_ => self.dismiss_by_key(key)?,
485+
}
478486
}
479487
Ok(())
480488
}
@@ -496,7 +504,15 @@ impl<S> SuggestStoreInner<S> {
496504

497505
fn is_dismissed_by_suggestion(&self, suggestion: &Suggestion) -> Result<bool> {
498506
if let Some(key) = suggestion.dismissal_key() {
499-
self.dbs()?.reader.read(|dao| dao.has_dismissal(key))
507+
match suggestion {
508+
Suggestion::Dynamic {
509+
suggestion_type, ..
510+
} => self
511+
.dbs()?
512+
.reader
513+
.read(|dao| dao.has_dynamic_dismissal(suggestion_type, key)),
514+
_ => self.dbs()?.reader.read(|dao| dao.has_dismissal(key)),
515+
}
500516
} else {
501517
Ok(false)
502518
}
@@ -3826,40 +3842,58 @@ pub(crate) mod tests {
38263842
fn dynamic_dismissal() -> anyhow::Result<()> {
38273843
before_each();
38283844

3829-
let store = TestStore::new(MockRemoteSettingsClient::default().with_record(
3830-
SuggestionProvider::Dynamic.full_record(
3831-
"dynamic-0",
3832-
Some(json!({
3833-
"suggestion_type": "aaa",
3834-
})),
3835-
Some(MockAttachment::Json(json!([
3836-
{
3837-
"keywords": ["aaa"],
3838-
"dismissal_key": "dk0",
3839-
},
3840-
{
3841-
"keywords": ["aaa"],
3842-
"dismissal_key": "dk1",
3843-
},
3844-
{
3845-
"keywords": ["aaa"],
3846-
},
3847-
]))),
3848-
),
3849-
));
3845+
let store = TestStore::new(
3846+
MockRemoteSettingsClient::default()
3847+
.with_record(SuggestionProvider::Dynamic.full_record(
3848+
"dynamic-0",
3849+
Some(json!({
3850+
"suggestion_type": "aaa",
3851+
})),
3852+
Some(MockAttachment::Json(json!([
3853+
{
3854+
"keywords": ["aaa"],
3855+
"dismissal_key": "dk0",
3856+
},
3857+
{
3858+
"keywords": ["aaa"],
3859+
"dismissal_key": "dk1",
3860+
},
3861+
{
3862+
"keywords": ["aaa"],
3863+
},
3864+
]))),
3865+
))
3866+
.with_record(SuggestionProvider::Dynamic.full_record(
3867+
"dynamic-1",
3868+
Some(json!({
3869+
"suggestion_type": "bbb",
3870+
})),
3871+
Some(MockAttachment::Json(json!([
3872+
{
3873+
"keywords": ["bbb"],
3874+
"dismissal_key": "dk0",
3875+
},
3876+
]))),
3877+
)),
3878+
);
3879+
38503880
store.ingest(SuggestIngestionConstraints {
38513881
providers: Some(vec![SuggestionProvider::Dynamic]),
38523882
provider_constraints: Some(SuggestionProviderConstraints {
3853-
dynamic_suggestion_types: Some(vec!["aaa".to_string()]),
3883+
dynamic_suggestion_types: Some(vec!["aaa".to_string(), "bbb".to_string()]),
38543884
..SuggestionProviderConstraints::default()
38553885
}),
38563886
..SuggestIngestionConstraints::all_providers()
38573887
});
38583888

38593889
// Make sure the suggestions are initially fetchable.
3860-
let suggestions = store.fetch_suggestions(SuggestionQuery::dynamic("aaa", &["aaa"]));
3890+
assert!(!store.inner.any_dismissed_suggestions()?);
3891+
let suggestions_0: Vec<Suggestion> =
3892+
store.fetch_suggestions(SuggestionQuery::dynamic("aaa", &["aaa"]));
3893+
let suggestions_1: Vec<Suggestion> =
3894+
store.fetch_suggestions(SuggestionQuery::dynamic("bbb", &["bbb"]));
38613895
assert_eq!(
3862-
suggestions,
3896+
suggestions_0,
38633897
vec![
38643898
Suggestion::Dynamic {
38653899
suggestion_type: "aaa".to_string(),
@@ -3883,8 +3917,11 @@ pub(crate) mod tests {
38833917
);
38843918

38853919
// Dismiss the first suggestion.
3886-
assert_eq!(suggestions[0].dismissal_key(), Some("dk0"));
3887-
store.inner.dismiss_by_suggestion(&suggestions[0])?;
3920+
assert_eq!(suggestions_0[0].dismissal_key(), Some("dk0"));
3921+
store.inner.dismiss_by_suggestion(&suggestions_0[0])?;
3922+
3923+
assert!(store.inner.any_dismissed_suggestions()?);
3924+
assert!(store.inner.is_dismissed_by_suggestion(&suggestions_0[0])?);
38883925
assert_eq!(
38893926
store.fetch_suggestions(SuggestionQuery::dynamic("aaa", &["aaa"])),
38903927
vec![
@@ -3904,8 +3941,10 @@ pub(crate) mod tests {
39043941
);
39053942

39063943
// Dismiss the second suggestion.
3907-
assert_eq!(suggestions[1].dismissal_key(), Some("dk1"));
3908-
store.inner.dismiss_by_suggestion(&suggestions[1])?;
3944+
assert_eq!(suggestions_0[1].dismissal_key(), Some("dk1"));
3945+
store.inner.dismiss_by_suggestion(&suggestions_0[1])?;
3946+
3947+
assert!(store.inner.is_dismissed_by_suggestion(&suggestions_0[1])?);
39093948
assert_eq!(
39103949
store.fetch_suggestions(SuggestionQuery::dynamic("aaa", &["aaa"])),
39113950
vec![Suggestion::Dynamic {
@@ -3916,6 +3955,23 @@ pub(crate) mod tests {
39163955
},],
39173956
);
39183957

3958+
// Make sure the bbb suggestion hasn't been dismissed even though it
3959+
// has the same key as the first aaa suggestion.
3960+
assert_eq!(
3961+
suggestions_1[0].dismissal_key(),
3962+
suggestions_0[0].dismissal_key()
3963+
);
3964+
assert!(!store.inner.is_dismissed_by_suggestion(&suggestions_1[0])?);
3965+
assert_eq!(
3966+
store.fetch_suggestions(SuggestionQuery::dynamic("bbb", &["bbb"])),
3967+
vec![Suggestion::Dynamic {
3968+
suggestion_type: "bbb".to_string(),
3969+
data: None,
3970+
dismissal_key: Some("dk0".to_string()),
3971+
score: DEFAULT_SUGGESTION_SCORE,
3972+
},],
3973+
);
3974+
39193975
// Clear dismissals. All suggestions should be fetchable again.
39203976
store.inner.clear_dismissed_suggestions()?;
39213977
assert_eq!(

0 commit comments

Comments
 (0)