4389: Stabilize scoreDetails r=dureuill a=dureuill

# Pull Request

## Related issue
Fixes #4359

## What does this PR do?

### User standpoint

- Users no longer need to enable the `scoreDetails` experimental feature to use `showRankingScoreDetails` in search queries.
- ⚠️ **Breaking change**: sending an object containing the key `"scoreDetails"` to the `/experimental-features` route is now an error. However, importing a dump of a database where that feature was enabled completes successfully.

### Implementation standpoint
- remove `scoreDetails` from the experimental features
- remove check on the experimental feature `scoreDetails` before accepting `showRankingScoreDetails`
- remove `scoreDetails` from the accepted fields in the `/experimental-features` route
- fix tests accordingly

## Manual tests

1. exported a dump with the `scoreDetails` feature enabled on `main`
    - tried to import the dump after the changes in this PR
    - the dump imported successfully
2. tried to make a search with `showRankingScoreDetails: true`
    - the ranking score details are displayed
    - an automated test case also exists and passes
3. tried to enable the `scoreDetails` in `/experimental-features`
    - get error message 
      ```
       Unknown field `scoreDetails`: expected one of `vectorStore`, `metrics`, `exportPuffinReports`
      ```

Co-authored-by: Louis Dureuil <louis@meilisearch.com>
This commit is contained in:
meili-bors[bot] 2024-02-08 10:40:00 +00:00 committed by GitHub
commit a616a1d37b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 4 additions and 59 deletions

View File

@ -30,19 +30,6 @@ impl RoFeatures {
self.runtime self.runtime
} }
pub fn check_score_details(&self) -> Result<()> {
if self.runtime.score_details {
Ok(())
} else {
Err(FeatureNotEnabledError {
disabled_action: "Computing score details",
feature: "score details",
issue_link: "https://github.com/meilisearch/product/discussions/674",
}
.into())
}
}
pub fn check_metrics(&self) -> Result<()> { pub fn check_metrics(&self) -> Result<()> {
if self.runtime.metrics { if self.runtime.metrics {
Ok(()) Ok(())

View File

@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, Copy, Default, PartialEq, Eq)]
#[serde(rename_all = "camelCase", default)] #[serde(rename_all = "camelCase", default)]
pub struct RuntimeTogglableFeatures { pub struct RuntimeTogglableFeatures {
pub score_details: bool,
pub vector_store: bool, pub vector_store: bool,
pub metrics: bool, pub metrics: bool,
pub export_puffin_reports: bool, pub export_puffin_reports: bool,

View File

@ -40,8 +40,6 @@ async fn get_features(
#[derive(Debug, Deserr)] #[derive(Debug, Deserr)]
#[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)] #[deserr(error = DeserrJsonError, rename_all = camelCase, deny_unknown_fields)]
pub struct RuntimeTogglableFeatures { pub struct RuntimeTogglableFeatures {
#[deserr(default)]
pub score_details: Option<bool>,
#[deserr(default)] #[deserr(default)]
pub vector_store: Option<bool>, pub vector_store: Option<bool>,
#[deserr(default)] #[deserr(default)]
@ -63,7 +61,6 @@ async fn patch_features(
let old_features = features.runtime_features(); let old_features = features.runtime_features();
let new_features = meilisearch_types::features::RuntimeTogglableFeatures { let new_features = meilisearch_types::features::RuntimeTogglableFeatures {
score_details: new_features.0.score_details.unwrap_or(old_features.score_details),
vector_store: new_features.0.vector_store.unwrap_or(old_features.vector_store), vector_store: new_features.0.vector_store.unwrap_or(old_features.vector_store),
metrics: new_features.0.metrics.unwrap_or(old_features.metrics), metrics: new_features.0.metrics.unwrap_or(old_features.metrics),
export_puffin_reports: new_features export_puffin_reports: new_features
@ -76,7 +73,6 @@ async fn patch_features(
// the it renames to camelCase, which we don't want for analytics. // the it renames to camelCase, which we don't want for analytics.
// **Do not** ignore fields with `..` or `_` here, because we want to add them in the future. // **Do not** ignore fields with `..` or `_` here, because we want to add them in the future.
let meilisearch_types::features::RuntimeTogglableFeatures { let meilisearch_types::features::RuntimeTogglableFeatures {
score_details,
vector_store, vector_store,
metrics, metrics,
export_puffin_reports, export_puffin_reports,
@ -85,7 +81,6 @@ async fn patch_features(
analytics.publish( analytics.publish(
"Experimental features Updated".to_string(), "Experimental features Updated".to_string(),
json!({ json!({
"score_details": score_details,
"vector_store": vector_store, "vector_store": vector_store,
"metrics": metrics, "metrics": metrics,
"export_puffin_reports": export_puffin_reports, "export_puffin_reports": export_puffin_reports,

View File

@ -441,10 +441,6 @@ fn prepare_search<'t>(
ScoringStrategy::Skip ScoringStrategy::Skip
}); });
if query.show_ranking_score_details {
features.check_score_details()?;
}
if let Some(HybridQuery { embedder: Some(embedder), .. }) = &query.hybrid { if let Some(HybridQuery { embedder: Some(embedder), .. }) = &query.hybrid {
search.embedder_name(embedder); search.embedder_name(embedder);
} }

View File

@ -1845,7 +1845,6 @@ async fn import_dump_v6_containing_experimental_features() {
meili_snap::snapshot!(code, @"200 OK"); meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"scoreDetails": false,
"vectorStore": false, "vectorStore": false,
"metrics": false, "metrics": false,
"exportPuffinReports": false "exportPuffinReports": false

View File

@ -18,7 +18,6 @@ async fn experimental_features() {
meili_snap::snapshot!(code, @"200 OK"); meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"scoreDetails": false,
"vectorStore": false, "vectorStore": false,
"metrics": false, "metrics": false,
"exportPuffinReports": false "exportPuffinReports": false
@ -30,7 +29,6 @@ async fn experimental_features() {
meili_snap::snapshot!(code, @"200 OK"); meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"scoreDetails": false,
"vectorStore": true, "vectorStore": true,
"metrics": false, "metrics": false,
"exportPuffinReports": false "exportPuffinReports": false
@ -42,7 +40,6 @@ async fn experimental_features() {
meili_snap::snapshot!(code, @"200 OK"); meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"scoreDetails": false,
"vectorStore": true, "vectorStore": true,
"metrics": false, "metrics": false,
"exportPuffinReports": false "exportPuffinReports": false
@ -55,7 +52,6 @@ async fn experimental_features() {
meili_snap::snapshot!(code, @"200 OK"); meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"scoreDetails": false,
"vectorStore": true, "vectorStore": true,
"metrics": false, "metrics": false,
"exportPuffinReports": false "exportPuffinReports": false
@ -68,7 +64,6 @@ async fn experimental_features() {
meili_snap::snapshot!(code, @"200 OK"); meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"scoreDetails": false,
"vectorStore": true, "vectorStore": true,
"metrics": false, "metrics": false,
"exportPuffinReports": false "exportPuffinReports": false
@ -88,7 +83,6 @@ async fn experimental_feature_metrics() {
meili_snap::snapshot!(code, @"200 OK"); meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"scoreDetails": false,
"vectorStore": false, "vectorStore": false,
"metrics": true, "metrics": true,
"exportPuffinReports": false "exportPuffinReports": false
@ -146,7 +140,7 @@ async fn errors() {
meili_snap::snapshot!(code, @"400 Bad Request"); meili_snap::snapshot!(code, @"400 Bad Request");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"message": "Unknown field `NotAFeature`: expected one of `scoreDetails`, `vectorStore`, `metrics`, `exportPuffinReports`", "message": "Unknown field `NotAFeature`: expected one of `vectorStore`, `metrics`, `exportPuffinReports`",
"code": "bad_request", "code": "bad_request",
"type": "invalid_request", "type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#bad_request" "link": "https://docs.meilisearch.com/errors#bad_request"

View File

@ -13,7 +13,6 @@ async fn index_with_documents<'a>(server: &'a Server, documents: &Value) -> Inde
meili_snap::snapshot!(code, @"200 OK"); meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###" meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{ {
"scoreDetails": false,
"vectorStore": true, "vectorStore": true,
"metrics": false, "metrics": false,
"exportPuffinReports": false "exportPuffinReports": false

View File

@ -766,38 +766,14 @@ async fn faceting_max_values_per_facet() {
} }
#[actix_rt::test] #[actix_rt::test]
async fn experimental_feature_score_details() { async fn test_score_details() {
let server = Server::new().await; let server = Server::new().await;
let index = server.index("test"); let index = server.index("test");
let documents = DOCUMENTS.clone(); let documents = DOCUMENTS.clone();
index.add_documents(json!(documents), None).await; let res = index.add_documents(json!(documents), None).await;
index.wait_task(0).await; index.wait_task(res.0.uid()).await;
index
.search(
json!({
"q": "train dragon",
"showRankingScoreDetails": true,
}),
|response, code| {
meili_snap::snapshot!(code, @"400 Bad Request");
meili_snap::snapshot!(meili_snap::json_string!(response), @r###"
{
"message": "Computing score details requires enabling the `score details` experimental feature. See https://github.com/meilisearch/product/discussions/674",
"code": "feature_not_enabled",
"type": "invalid_request",
"link": "https://docs.meilisearch.com/errors#feature_not_enabled"
}
"###);
},
)
.await;
let (response, code) = server.set_features(json!({"scoreDetails": true})).await;
meili_snap::snapshot!(code, @"200 OK");
meili_snap::snapshot!(response["scoreDetails"], @"true");
index index
.search( .search(