Make prompt library icon in context panel staff-only for now (#12457)

Nathan Sobo created

This is still pretty raw, so I'd like to hold off on shipping it to all
users.

Release Notes:

- Hide the prompt library for non-staff until it is in a more complete
state.

Change summary

Cargo.lock                                |  1 
crates/assistant/Cargo.toml               |  1 
crates/assistant/src/assistant_panel.rs   | 75 ++++++++++++++----------
crates/client/src/user.rs                 |  7 +
crates/feature_flags/src/feature_flags.rs |  6 ++
5 files changed, 56 insertions(+), 34 deletions(-)

Detailed changes

Cargo.lock πŸ”—

@@ -346,6 +346,7 @@ dependencies = [
  "ctor",
  "editor",
  "env_logger",
+ "feature_flags",
  "file_icons",
  "fs",
  "futures 0.3.28",

crates/assistant/Cargo.toml πŸ”—

@@ -22,6 +22,7 @@ client.workspace = true
 collections.workspace = true
 command_palette_hooks.workspace = true
 editor.workspace = true
+feature_flags.workspace = true
 file_icons.workspace = true
 fs.workspace = true
 futures.workspace = true

crates/assistant/src/assistant_panel.rs πŸ”—

@@ -28,6 +28,7 @@ use editor::{
     ToOffset as _, ToPoint,
 };
 use editor::{display_map::FlapId, FoldPlaceholder};
+use feature_flags::{FeatureFlag, FeatureFlagAppExt, FeatureFlagViewExt};
 use file_icons::FileIcons;
 use fs::Fs;
 use futures::future::Shared;
@@ -127,6 +128,12 @@ struct ActiveConversationEditor {
     _subscriptions: Vec<Subscription>,
 }
 
+struct PromptLibraryFeatureFlag;
+
+impl FeatureFlag for PromptLibraryFeatureFlag {
+    const NAME: &'static str = "prompt-library";
+}
+
 impl AssistantPanel {
     const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20;
 
@@ -152,6 +159,9 @@ impl AssistantPanel {
             let workspace_handle = workspace.clone();
             workspace.update(&mut cx, |workspace, cx| {
                 cx.new_view::<Self>(|cx| {
+                    cx.observe_flag::<PromptLibraryFeatureFlag, _>(|_, _, cx| cx.notify())
+                        .detach();
+
                     const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
                     let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
                         let mut events = fs
@@ -1179,38 +1189,38 @@ impl AssistantPanel {
     }
 
     fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let header =
-            TabBar::new("assistant_header")
-                .start_child(h_flex().gap_1().child(self.render_popover_button(cx)))
-                .children(self.active_conversation_editor().map(|editor| {
-                    h_flex()
-                        .h(rems(Tab::CONTAINER_HEIGHT_IN_REMS))
-                        .flex_1()
-                        .px_2()
-                        .child(Label::new(editor.read(cx).title(cx)).into_element())
-                }))
-                .end_child(
-                    h_flex()
-                        .gap_2()
-                        .when_some(self.active_conversation_editor(), |this, editor| {
-                            let conversation = editor.read(cx).conversation.clone();
-                            this.child(
-                                h_flex()
-                                    .gap_1()
-                                    .child(self.render_model(&conversation, cx))
-                                    .children(self.render_remaining_tokens(&conversation, cx)),
-                            )
-                            .child(
-                                ui::Divider::vertical()
-                                    .inset()
-                                    .color(ui::DividerColor::Border),
-                            )
-                        })
-                        .child(
+        let header = TabBar::new("assistant_header")
+            .start_child(h_flex().gap_1().child(self.render_popover_button(cx)))
+            .children(self.active_conversation_editor().map(|editor| {
+                h_flex()
+                    .h(rems(Tab::CONTAINER_HEIGHT_IN_REMS))
+                    .flex_1()
+                    .px_2()
+                    .child(Label::new(editor.read(cx).title(cx)).into_element())
+            }))
+            .end_child(
+                h_flex()
+                    .gap_2()
+                    .when_some(self.active_conversation_editor(), |this, editor| {
+                        let conversation = editor.read(cx).conversation.clone();
+                        this.child(
                             h_flex()
                                 .gap_1()
-                                .child(self.render_inject_context_menu(cx))
-                                .child(
+                                .child(self.render_model(&conversation, cx))
+                                .children(self.render_remaining_tokens(&conversation, cx)),
+                        )
+                        .child(
+                            ui::Divider::vertical()
+                                .inset()
+                                .color(ui::DividerColor::Border),
+                        )
+                    })
+                    .child(
+                        h_flex()
+                            .gap_1()
+                            .child(self.render_inject_context_menu(cx))
+                            .children(
+                                cx.has_flag::<PromptLibraryFeatureFlag>().then_some(
                                     IconButton::new("show_prompt_manager", IconName::Library)
                                         .icon_size(IconSize::Small)
                                         .on_click(cx.listener(|this, _event, cx| {
@@ -1218,8 +1228,9 @@ impl AssistantPanel {
                                         }))
                                         .tooltip(|cx| Tooltip::text("Prompt Library…", cx)),
                                 ),
-                        ),
-                );
+                            ),
+                    ),
+            );
 
         let contents = if self.active_conversation_editor().is_some() {
             let mut registrar = DivRegistrar::new(

crates/client/src/user.rs πŸ”—

@@ -192,10 +192,13 @@ impl UserStore {
 
                                 cx.update(|cx| {
                                     if let Some(info) = info {
-                                        cx.update_flags(info.staff, info.flags);
+                                        let disable_staff = std::env::var("ZED_DISABLE_STAFF")
+                                            .map_or(false, |v| v != "" && v != "0");
+                                        let staff = info.staff && !disable_staff;
+                                        cx.update_flags(staff, info.flags);
                                         client.telemetry.set_authenticated_user_info(
                                             Some(info.metrics_id.clone()),
-                                            info.staff,
+                                            staff,
                                         )
                                     }
                                 })?;

crates/feature_flags/src/feature_flags.rs πŸ”—

@@ -14,6 +14,12 @@ impl FeatureFlags {
 
 impl Global for FeatureFlags {}
 
+/// To create a feature flag, implement this trait on a trivial type and use it as
+/// a generic parameter when called [`FeatureFlagAppExt::has_flag`].
+///
+/// Feature flags are always enabled for members of Zed staff. To disable this behavior
+/// so you can test flags being disabled, set ZED_DISABLE_STAFF=1 in your environment,
+/// which will force Zed to treat the current user as non-staff.
 pub trait FeatureFlag {
     const NAME: &'static str;
 }