Fix data collection permission asked multiple times for same worktree (#24016)
After the user confirmation, only the current instance of the completions provider had the answer stored. In this PR, the changes are propagated by having each provider have an `Entity<choice>`, and having a lookup map with one `Entity<choice>` for each worktree that `Zeta` has seen. Release Notes: - N/A
This commit is contained in:
parent
be4c3cfbd2
commit
6f467281e0
2 changed files with 64 additions and 57 deletions
|
@ -1,5 +1,3 @@
|
||||||
use anyhow::Result;
|
|
||||||
use collections::HashMap;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use workspace::WorkspaceDb;
|
use workspace::WorkspaceDb;
|
||||||
|
|
||||||
|
@ -18,12 +16,8 @@ define_connection!(
|
||||||
);
|
);
|
||||||
|
|
||||||
impl ZetaDb {
|
impl ZetaDb {
|
||||||
pub fn get_all_zeta_preferences(&self) -> Result<HashMap<PathBuf, bool>> {
|
|
||||||
Ok(self.get_all_zeta_preferences_query()?.into_iter().collect())
|
|
||||||
}
|
|
||||||
|
|
||||||
query! {
|
query! {
|
||||||
fn get_all_zeta_preferences_query() -> Result<Vec<(PathBuf, bool)>> {
|
pub fn get_all_data_collection_preferences() -> Result<Vec<(PathBuf, bool)>> {
|
||||||
SELECT worktree_path, accepted_data_collection FROM zeta_preferences
|
SELECT worktree_path, accepted_data_collection FROM zeta_preferences
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +30,7 @@ impl ZetaDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
query! {
|
query! {
|
||||||
pub async fn save_accepted_data_collection(worktree_path: PathBuf, accepted_data_collection: bool) -> Result<()> {
|
pub async fn save_data_collection_choice(worktree_path: PathBuf, accepted_data_collection: bool) -> Result<()> {
|
||||||
INSERT INTO zeta_preferences
|
INSERT INTO zeta_preferences
|
||||||
(worktree_path, accepted_data_collection)
|
(worktree_path, accepted_data_collection)
|
||||||
VALUES
|
VALUES
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub use rate_completion_modal::*;
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::{anyhow, Context as _, Result};
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
use client::{Client, UserStore};
|
use client::{Client, UserStore};
|
||||||
|
use collections::hash_map::Entry;
|
||||||
use collections::{HashMap, HashSet, VecDeque};
|
use collections::{HashMap, HashSet, VecDeque};
|
||||||
use feature_flags::FeatureFlagAppExt as _;
|
use feature_flags::FeatureFlagAppExt as _;
|
||||||
use futures::AsyncReadExt;
|
use futures::AsyncReadExt;
|
||||||
|
@ -24,7 +25,6 @@ use language::{
|
||||||
};
|
};
|
||||||
use language_models::LlmApiToken;
|
use language_models::LlmApiToken;
|
||||||
use rpc::{PredictEditsParams, PredictEditsResponse, EXPIRED_LLM_TOKEN_HEADER_NAME};
|
use rpc::{PredictEditsParams, PredictEditsResponse, EXPIRED_LLM_TOKEN_HEADER_NAME};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp, env,
|
cmp, env,
|
||||||
|
@ -903,28 +903,24 @@ and then another
|
||||||
new_snapshot
|
new_snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data_collection_choice_at(&self, path: &Path) -> DataCollectionChoice {
|
/// Creates a `Entity<DataCollectionChoice>` for each unique worktree abs path it sees.
|
||||||
match self.data_collection_preferences.per_worktree.get(path) {
|
pub fn data_collection_choice_at(
|
||||||
Some(true) => DataCollectionChoice::Enabled,
|
|
||||||
Some(false) => DataCollectionChoice::Disabled,
|
|
||||||
None => DataCollectionChoice::NotAnswered,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_data_collection_choice_for_worktree(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
absolute_path_of_project_worktree: PathBuf,
|
worktree_abs_path: PathBuf,
|
||||||
can_collect_data: bool,
|
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) -> Entity<DataCollectionChoice> {
|
||||||
self.data_collection_preferences
|
match self
|
||||||
|
.data_collection_preferences
|
||||||
.per_worktree
|
.per_worktree
|
||||||
.insert(absolute_path_of_project_worktree.clone(), can_collect_data);
|
.entry(worktree_abs_path)
|
||||||
|
{
|
||||||
db::write_and_log(cx, move || {
|
Entry::Vacant(entry) => {
|
||||||
persistence::DB
|
let choice = cx.new(|_| DataCollectionChoice::NotAnswered);
|
||||||
.save_accepted_data_collection(absolute_path_of_project_worktree, can_collect_data)
|
entry.insert(choice.clone());
|
||||||
});
|
choice
|
||||||
|
}
|
||||||
|
Entry::Occupied(entry) => entry.get().clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_never_ask_again_for_data_collection(&mut self, cx: &mut Context<Self>) {
|
fn set_never_ask_again_for_data_collection(&mut self, cx: &mut Context<Self>) {
|
||||||
|
@ -959,23 +955,32 @@ and then another
|
||||||
.map(|value| value == "true")
|
.map(|value| value == "true")
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
|
|
||||||
let preferences_per_project = persistence::DB
|
let preferences_per_worktree = persistence::DB
|
||||||
.get_all_zeta_preferences()
|
.get_all_data_collection_preferences()
|
||||||
.log_err()
|
.log_err()
|
||||||
.unwrap_or_else(HashMap::default);
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|(path, choice)| {
|
||||||
|
let choice = cx.new(|_| DataCollectionChoice::from(choice));
|
||||||
|
(path, choice)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
DataCollectionPreferences {
|
DataCollectionPreferences {
|
||||||
never_ask_again,
|
never_ask_again,
|
||||||
per_worktree: preferences_per_project,
|
per_worktree: preferences_per_worktree,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Default, Debug)]
|
||||||
struct DataCollectionPreferences {
|
struct DataCollectionPreferences {
|
||||||
/// Set when a user clicks on "Never Ask Again", can never be unset.
|
/// Set when a user clicks on "Never Ask Again", can never be unset.
|
||||||
never_ask_again: bool,
|
never_ask_again: bool,
|
||||||
per_worktree: HashMap<PathBuf, bool>,
|
/// The choices for each worktree.
|
||||||
|
///
|
||||||
|
/// This is filled when loading from database, or when querying if no matching path is found.
|
||||||
|
per_worktree: HashMap<PathBuf, Entity<DataCollectionChoice>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b: T2) -> usize {
|
fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b: T2) -> usize {
|
||||||
|
@ -1281,7 +1286,7 @@ struct PendingCompletion {
|
||||||
_task: Task<()>,
|
_task: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum DataCollectionChoice {
|
pub enum DataCollectionChoice {
|
||||||
NotAnswered,
|
NotAnswered,
|
||||||
Enabled,
|
Enabled,
|
||||||
|
@ -1289,21 +1294,21 @@ pub enum DataCollectionChoice {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DataCollectionChoice {
|
impl DataCollectionChoice {
|
||||||
pub fn is_enabled(&self) -> bool {
|
pub fn is_enabled(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Enabled => true,
|
Self::Enabled => true,
|
||||||
Self::NotAnswered | Self::Disabled => false,
|
Self::NotAnswered | Self::Disabled => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_answered(&self) -> bool {
|
pub fn is_answered(self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Self::Enabled | Self::Disabled => true,
|
Self::Enabled | Self::Disabled => true,
|
||||||
Self::NotAnswered => false,
|
Self::NotAnswered => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggle(&self) -> DataCollectionChoice {
|
pub fn toggle(self) -> DataCollectionChoice {
|
||||||
match self {
|
match self {
|
||||||
Self::Enabled => Self::Disabled,
|
Self::Enabled => Self::Disabled,
|
||||||
Self::Disabled => Self::Enabled,
|
Self::Disabled => Self::Enabled,
|
||||||
|
@ -1312,6 +1317,15 @@ impl DataCollectionChoice {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<bool> for DataCollectionChoice {
|
||||||
|
fn from(value: bool) -> Self {
|
||||||
|
match value {
|
||||||
|
true => DataCollectionChoice::Enabled,
|
||||||
|
false => DataCollectionChoice::Disabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ZetaInlineCompletionProvider {
|
pub struct ZetaInlineCompletionProvider {
|
||||||
zeta: Entity<Zeta>,
|
zeta: Entity<Zeta>,
|
||||||
pending_completions: ArrayVec<PendingCompletion, 2>,
|
pending_completions: ArrayVec<PendingCompletion, 2>,
|
||||||
|
@ -1323,7 +1337,7 @@ pub struct ZetaInlineCompletionProvider {
|
||||||
pub struct ProviderDataCollection {
|
pub struct ProviderDataCollection {
|
||||||
workspace: WeakEntity<Workspace>,
|
workspace: WeakEntity<Workspace>,
|
||||||
worktree_root_path: PathBuf,
|
worktree_root_path: PathBuf,
|
||||||
choice: DataCollectionChoice,
|
choice: Entity<DataCollectionChoice>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ProviderDataCollection {
|
impl ProviderDataCollection {
|
||||||
|
@ -1351,7 +1365,9 @@ impl ProviderDataCollection {
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let choice = zeta.read(cx).data_collection_choice_at(&worktree_root_path);
|
let choice = zeta.update(cx, |zeta, cx| {
|
||||||
|
zeta.data_collection_choice_at(worktree_root_path.clone(), cx)
|
||||||
|
});
|
||||||
|
|
||||||
Some(ProviderDataCollection {
|
Some(ProviderDataCollection {
|
||||||
workspace: workspace.downgrade(),
|
workspace: workspace.downgrade(),
|
||||||
|
@ -1360,22 +1376,18 @@ impl ProviderDataCollection {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_choice(&mut self, choice: DataCollectionChoice, zeta: &Entity<Zeta>, cx: &mut App) {
|
fn set_choice(&mut self, choice: DataCollectionChoice, cx: &mut App) {
|
||||||
self.choice = choice;
|
self.choice.update(cx, |this, _| *this = choice);
|
||||||
|
|
||||||
let worktree_root_path = self.worktree_root_path.clone();
|
let worktree_root_path = self.worktree_root_path.clone();
|
||||||
|
|
||||||
zeta.update(cx, |zeta, cx| {
|
db::write_and_log(cx, move || {
|
||||||
zeta.update_data_collection_choice_for_worktree(
|
persistence::DB.save_data_collection_choice(worktree_root_path, choice.is_enabled())
|
||||||
worktree_root_path,
|
|
||||||
choice.is_enabled(),
|
|
||||||
cx,
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn toggle_choice(&mut self, zeta: &Entity<Zeta>, cx: &mut App) {
|
fn toggle_choice(&mut self, cx: &mut App) {
|
||||||
self.set_choice(self.choice.toggle(), zeta, cx);
|
self.set_choice(self.choice.read(cx).toggle(), cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1394,7 +1406,7 @@ impl ZetaInlineCompletionProvider {
|
||||||
|
|
||||||
fn set_data_collection_choice(&mut self, choice: DataCollectionChoice, cx: &mut App) {
|
fn set_data_collection_choice(&mut self, choice: DataCollectionChoice, cx: &mut App) {
|
||||||
if let Some(data_collection) = self.data_collection.as_mut() {
|
if let Some(data_collection) = self.data_collection.as_mut() {
|
||||||
data_collection.set_choice(choice, &self.zeta, cx);
|
data_collection.set_choice(choice, cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1420,12 +1432,12 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn data_collection_state(&self, _cx: &App) -> DataCollectionState {
|
fn data_collection_state(&self, cx: &App) -> DataCollectionState {
|
||||||
let Some(data_collection) = self.data_collection.as_ref() else {
|
let Some(data_collection) = self.data_collection.as_ref() else {
|
||||||
return DataCollectionState::Unknown;
|
return DataCollectionState::Unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
if data_collection.choice.is_enabled() {
|
if data_collection.choice.read(cx).is_enabled() {
|
||||||
DataCollectionState::Enabled
|
DataCollectionState::Enabled
|
||||||
} else {
|
} else {
|
||||||
DataCollectionState::Disabled
|
DataCollectionState::Disabled
|
||||||
|
@ -1434,7 +1446,7 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
|
||||||
|
|
||||||
fn toggle_data_collection(&mut self, cx: &mut App) {
|
fn toggle_data_collection(&mut self, cx: &mut App) {
|
||||||
if let Some(data_collection) = self.data_collection.as_mut() {
|
if let Some(data_collection) = self.data_collection.as_mut() {
|
||||||
data_collection.toggle_choice(&self.zeta, cx);
|
data_collection.toggle_choice(cx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1486,7 +1498,9 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
|
||||||
let can_collect_data = self
|
let can_collect_data = self
|
||||||
.data_collection
|
.data_collection
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map_or(false, |data_collection| data_collection.choice.is_enabled());
|
.map_or(false, |data_collection| {
|
||||||
|
data_collection.choice.read(cx).is_enabled()
|
||||||
|
});
|
||||||
|
|
||||||
let task = cx.spawn(|this, mut cx| async move {
|
let task = cx.spawn(|this, mut cx| async move {
|
||||||
if debounce {
|
if debounce {
|
||||||
|
@ -1579,7 +1593,7 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
if data_collection.choice.is_answered()
|
if data_collection.choice.read(cx).is_answered()
|
||||||
|| self
|
|| self
|
||||||
.zeta
|
.zeta
|
||||||
.read(cx)
|
.read(cx)
|
||||||
|
@ -1633,7 +1647,6 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
|
||||||
})
|
})
|
||||||
.with_tertiary_click_message("Never Ask Again")
|
.with_tertiary_click_message("Never Ask Again")
|
||||||
.on_tertiary_click({
|
.on_tertiary_click({
|
||||||
let zeta = zeta.clone();
|
|
||||||
move |_window, cx| {
|
move |_window, cx| {
|
||||||
zeta.update(cx, |zeta, cx| {
|
zeta.update(cx, |zeta, cx| {
|
||||||
zeta.set_never_ask_again_for_data_collection(cx);
|
zeta.set_never_ask_again_for_data_collection(cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue