collab: Allow enabling feature flags for all users (#16372)

Marshall Bowers created

This PR adds a new `enabled_for_all` column to the `feature_flags` table
to allow enabling a feature flag for all users.

Release Notes:

- N/A

Change summary

crates/collab/migrations.sqlite/20221109000000_test_schema.sql                   |  3 
crates/collab/migrations/20240816181658_add_enabled_for_all_to_feature_flags.sql |  1 
crates/collab/src/db/queries/users.rs                                            | 18 
crates/collab/src/db/tables/feature_flag.rs                                      |  1 
crates/collab/src/db/tests/feature_flag_tests.rs                                 | 24 
5 files changed, 34 insertions(+), 13 deletions(-)

Detailed changes

crates/collab/migrations.sqlite/20221109000000_test_schema.sql 🔗

@@ -295,7 +295,8 @@ CREATE UNIQUE INDEX "index_channel_buffer_collaborators_on_channel_id_connection
 
 CREATE TABLE "feature_flags" (
     "id" INTEGER PRIMARY KEY AUTOINCREMENT,
-    "flag" TEXT NOT NULL UNIQUE
+    "flag" TEXT NOT NULL UNIQUE,
+    "enabled_for_all" BOOLEAN NOT NULL DEFAULT false
 );
 
 CREATE INDEX "index_feature_flags" ON "feature_flags" ("id");

crates/collab/src/db/queries/users.rs 🔗

@@ -312,10 +312,11 @@ impl Database {
     }
 
     /// Creates a new feature flag.
-    pub async fn create_user_flag(&self, flag: &str) -> Result<FlagId> {
+    pub async fn create_user_flag(&self, flag: &str, enabled_for_all: bool) -> Result<FlagId> {
         self.transaction(|tx| async move {
             let flag = feature_flag::Entity::insert(feature_flag::ActiveModel {
                 flag: ActiveValue::set(flag.to_string()),
+                enabled_for_all: ActiveValue::set(enabled_for_all),
                 ..Default::default()
             })
             .exec(&*tx)
@@ -350,7 +351,15 @@ impl Database {
                 Flag,
             }
 
-            let flags = user::Model {
+            let flags_enabled_for_all = feature_flag::Entity::find()
+                .filter(feature_flag::Column::EnabledForAll.eq(true))
+                .select_only()
+                .column(feature_flag::Column::Flag)
+                .into_values::<_, QueryAs>()
+                .all(&*tx)
+                .await?;
+
+            let flags_enabled_for_user = user::Model {
                 id: user,
                 ..Default::default()
             }
@@ -361,7 +370,10 @@ impl Database {
             .all(&*tx)
             .await?;
 
-            Ok(flags)
+            let mut all_flags = HashSet::from_iter(flags_enabled_for_all);
+            all_flags.extend(flags_enabled_for_user);
+
+            Ok(all_flags.into_iter().collect())
         })
         .await
     }

crates/collab/src/db/tests/feature_flag_tests.rs 🔗

@@ -2,6 +2,7 @@ use crate::{
     db::{Database, NewUserParams},
     test_both_dbs,
 };
+use pretty_assertions::assert_eq;
 use std::sync::Arc;
 
 test_both_dbs!(
@@ -37,22 +38,27 @@ async fn test_get_user_flags(db: &Arc<Database>) {
         .unwrap()
         .user_id;
 
-    const CHANNELS_ALPHA: &str = "channels-alpha";
-    const NEW_SEARCH: &str = "new-search";
+    const FEATURE_FLAG_ONE: &str = "brand-new-ux";
+    const FEATURE_FLAG_TWO: &str = "cool-feature";
+    const FEATURE_FLAG_THREE: &str = "feature-enabled-for-everyone";
 
-    let channels_flag = db.create_user_flag(CHANNELS_ALPHA).await.unwrap();
-    let search_flag = db.create_user_flag(NEW_SEARCH).await.unwrap();
+    let feature_flag_one = db.create_user_flag(FEATURE_FLAG_ONE, false).await.unwrap();
+    let feature_flag_two = db.create_user_flag(FEATURE_FLAG_TWO, false).await.unwrap();
+    db.create_user_flag(FEATURE_FLAG_THREE, true).await.unwrap();
 
-    db.add_user_flag(user_1, channels_flag).await.unwrap();
-    db.add_user_flag(user_1, search_flag).await.unwrap();
+    db.add_user_flag(user_1, feature_flag_one).await.unwrap();
+    db.add_user_flag(user_1, feature_flag_two).await.unwrap();
 
-    db.add_user_flag(user_2, channels_flag).await.unwrap();
+    db.add_user_flag(user_2, feature_flag_one).await.unwrap();
 
     let mut user_1_flags = db.get_user_flags(user_1).await.unwrap();
     user_1_flags.sort();
-    assert_eq!(user_1_flags, &[CHANNELS_ALPHA, NEW_SEARCH]);
+    assert_eq!(
+        user_1_flags,
+        &[FEATURE_FLAG_ONE, FEATURE_FLAG_TWO, FEATURE_FLAG_THREE]
+    );
 
     let mut user_2_flags = db.get_user_flags(user_2).await.unwrap();
     user_2_flags.sort();
-    assert_eq!(user_2_flags, &[CHANNELS_ALPHA]);
+    assert_eq!(user_2_flags, &[FEATURE_FLAG_ONE, FEATURE_FLAG_THREE]);
 }