[apps] Improve app listing filtering. (#11697)

- [x] If an installed app is not on the app listing, remove it from the
final list.
This commit is contained in:
Matthew Zeng authored and GitHub committed 2026-02-13 11:54:16 -08:00
1 parent c54a4ec078
commit 8468871e2b
5 files changed
+67 -9

No files matched your search

@@ -4724,9 +4724,10 @@ impl CodexMessageProcessor {
all_connectors: Option<&[AppInfo]>, all_connectors: Option<&[AppInfo]>,
accessible_connectors: Option<&[AppInfo]>, accessible_connectors: Option<&[AppInfo]>,
) -> Vec<AppInfo> { ) -> Vec<AppInfo> {
let all_connectors_loaded = all_connectors.is_some();
let all = all_connectors.map_or_else(Vec::new, <[AppInfo]>::to_vec); let all = all_connectors.map_or_else(Vec::new, <[AppInfo]>::to_vec);
let accessible = accessible_connectors.map_or_else(Vec::new, <[AppInfo]>::to_vec); let accessible = accessible_connectors.map_or_else(Vec::new, <[AppInfo]>::to_vec);
connectors::merge_connectors_with_accessible(all, accessible) connectors::merge_connectors_with_accessible(all, accessible, all_connectors_loaded)
} }
fn paginate_apps( fn paginate_apps(
+52 -1
View File
@@ -1,4 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::LazyLock; use std::sync::LazyLock;
use std::sync::Mutex as StdMutex; use std::sync::Mutex as StdMutex;
@@ -75,7 +76,7 @@ pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
let connectors = connectors_result?; let connectors = connectors_result?;
let accessible = accessible_result?; let accessible = accessible_result?;
Ok(with_app_enabled_state( Ok(with_app_enabled_state(
merge_connectors_with_accessible(connectors, accessible), merge_connectors_with_accessible(connectors, accessible, true),
config, config,
)) ))
} }
@@ -185,7 +186,20 @@ fn write_cached_all_connectors(cache_key: AllConnectorsCacheKey, connectors: &[A
pub fn merge_connectors_with_accessible( pub fn merge_connectors_with_accessible(
connectors: Vec<AppInfo>, connectors: Vec<AppInfo>,
accessible_connectors: Vec<AppInfo>, accessible_connectors: Vec<AppInfo>,
all_connectors_loaded: bool,
) -> Vec<AppInfo> { ) -> Vec<AppInfo> {
let accessible_connectors = if all_connectors_loaded {
let connector_ids: HashSet<&str> = connectors
.iter()
.map(|connector| connector.id.as_str())
.collect();
accessible_connectors
.into_iter()
.filter(|connector| connector_ids.contains(connector.id.as_str()))
.collect()
} else {
accessible_connectors
};
let merged = merge_connectors(connectors, accessible_connectors); let merged = merge_connectors(connectors, accessible_connectors);
filter_disallowed_connectors(merged) filter_disallowed_connectors(merged)
} }
@@ -406,4 +420,41 @@ mod tests {
]); ]);
assert_eq!(filtered, vec![app("delta")]); assert_eq!(filtered, vec![app("delta")]);
} }
fn merged_app(id: &str, is_accessible: bool) -> AppInfo {
AppInfo {
id: id.to_string(),
name: id.to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
install_url: Some(connector_install_url(id, id)),
is_accessible,
is_enabled: true,
}
}
#[test]
fn excludes_accessible_connectors_not_in_all_when_all_loaded() {
let merged = merge_connectors_with_accessible(
vec![app("alpha")],
vec![app("alpha"), app("beta")],
true,
);
assert_eq!(merged, vec![merged_app("alpha", true)]);
}
#[test]
fn keeps_accessible_connectors_not_in_all_while_all_loading() {
let merged = merge_connectors_with_accessible(
vec![app("alpha")],
vec![app("alpha"), app("beta")],
false,
);
assert_eq!(
merged,
vec![merged_app("alpha", true), merged_app("beta", true)]
);
}
} }
+1 -1
View File
@@ -3387,7 +3387,7 @@ mod tests {
let mut app = make_test_app().await; let mut app = make_test_app().await;
let codex_home = tempdir()?; let codex_home = tempdir()?;
app.config.codex_home = codex_home.path().to_path_buf(); app.config.codex_home = codex_home.path().to_path_buf();
let app_id = "connector_1".to_string(); let app_id = "unit_test_refresh_in_memory_config_connector".to_string();
assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None); assert_eq!(app_enabled_in_effective_config(&app.config, &app_id), None);
+2
View File
@@ -4582,6 +4582,7 @@ impl ChatWidget {
let connectors = connectors::merge_connectors_with_accessible( let connectors = connectors::merge_connectors_with_accessible(
all_connectors, all_connectors,
accessible_connectors, accessible_connectors,
true,
); );
Ok(ConnectorsSnapshot { connectors }) Ok(ConnectorsSnapshot { connectors })
} }
@@ -6736,6 +6737,7 @@ impl ChatWidget {
snapshot.connectors = connectors::merge_connectors_with_accessible( snapshot.connectors = connectors::merge_connectors_with_accessible(
Vec::new(), Vec::new(),
snapshot.connectors, snapshot.connectors,
false,
); );
} }
snapshot.connectors = snapshot.connectors =
+10 -6
View File
@@ -3868,11 +3868,13 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
chat.config.features.enable(Feature::Apps); chat.config.features.enable(Feature::Apps);
chat.bottom_pane.set_connectors_enabled(true); chat.bottom_pane.set_connectors_enabled(true);
let notion_id = "unit_test_apps_popup_refresh_connector_1";
let linear_id = "unit_test_apps_popup_refresh_connector_2";
chat.on_connectors_loaded( chat.on_connectors_loaded(
Ok(ConnectorsSnapshot { Ok(ConnectorsSnapshot {
connectors: vec![codex_chatgpt::connectors::AppInfo { connectors: vec![codex_chatgpt::connectors::AppInfo {
id: "connector_1".to_string(), id: notion_id.to_string(),
name: "Notion".to_string(), name: "Notion".to_string(),
description: Some("Workspace docs".to_string()), description: Some("Workspace docs".to_string()),
logo_url: None, logo_url: None,
@@ -3905,7 +3907,7 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
Ok(ConnectorsSnapshot { Ok(ConnectorsSnapshot {
connectors: vec![ connectors: vec![
codex_chatgpt::connectors::AppInfo { codex_chatgpt::connectors::AppInfo {
id: "connector_1".to_string(), id: notion_id.to_string(),
name: "Notion".to_string(), name: "Notion".to_string(),
description: Some("Workspace docs".to_string()), description: Some("Workspace docs".to_string()),
logo_url: None, logo_url: None,
@@ -3916,7 +3918,7 @@ async fn apps_popup_refreshes_when_connectors_snapshot_updates() {
is_enabled: true, is_enabled: true,
}, },
codex_chatgpt::connectors::AppInfo { codex_chatgpt::connectors::AppInfo {
id: "connector_2".to_string(), id: linear_id.to_string(),
name: "Linear".to_string(), name: "Linear".to_string(),
description: Some("Project tracking".to_string()), description: Some("Project tracking".to_string()),
logo_url: None, logo_url: None,
@@ -3947,10 +3949,12 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await; let (mut chat, _rx, _op_rx) = make_chatwidget_manual(None).await;
chat.config.features.enable(Feature::Apps); chat.config.features.enable(Feature::Apps);
chat.bottom_pane.set_connectors_enabled(true); chat.bottom_pane.set_connectors_enabled(true);
let notion_id = "unit_test_apps_refresh_failure_connector_1";
let linear_id = "unit_test_apps_refresh_failure_connector_2";
let full_connectors = vec![ let full_connectors = vec![
codex_chatgpt::connectors::AppInfo { codex_chatgpt::connectors::AppInfo {
id: "connector_1".to_string(), id: notion_id.to_string(),
name: "Notion".to_string(), name: "Notion".to_string(),
description: Some("Workspace docs".to_string()), description: Some("Workspace docs".to_string()),
logo_url: None, logo_url: None,
@@ -3961,7 +3965,7 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
is_enabled: true, is_enabled: true,
}, },
codex_chatgpt::connectors::AppInfo { codex_chatgpt::connectors::AppInfo {
id: "connector_2".to_string(), id: linear_id.to_string(),
name: "Linear".to_string(), name: "Linear".to_string(),
description: Some("Project tracking".to_string()), description: Some("Project tracking".to_string()),
logo_url: None, logo_url: None,
@@ -3982,7 +3986,7 @@ async fn apps_refresh_failure_keeps_existing_full_snapshot() {
chat.on_connectors_loaded( chat.on_connectors_loaded(
Ok(ConnectorsSnapshot { Ok(ConnectorsSnapshot {
connectors: vec![codex_chatgpt::connectors::AppInfo { connectors: vec![codex_chatgpt::connectors::AppInfo {
id: "connector_1".to_string(), id: notion_id.to_string(),
name: "Notion".to_string(), name: "Notion".to_string(),
description: Some("Workspace docs".to_string()), description: Some("Workspace docs".to_string()),
logo_url: None, logo_url: None,