feat: add telemetry consent fields to Settings (Rust + TypeScript)
New fields: telemetry_consent, crash_reporting_enabled, analytics_enabled, anonymous_id. All Option/null by default for backward compatibility with existing settings.json files. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,10 @@ pub struct Settings {
|
||||
pub github_token: Option<String>,
|
||||
pub github_username: Option<String>,
|
||||
pub auto_pull_interval_minutes: Option<u32>,
|
||||
pub telemetry_consent: Option<bool>,
|
||||
pub crash_reporting_enabled: Option<bool>,
|
||||
pub analytics_enabled: Option<bool>,
|
||||
pub anonymous_id: Option<String>,
|
||||
}
|
||||
|
||||
fn settings_path() -> Result<PathBuf, String> {
|
||||
@@ -56,6 +60,13 @@ fn save_settings_at(path: &PathBuf, settings: Settings) -> Result<(), String> {
|
||||
.map(|k| k.trim().to_string())
|
||||
.filter(|k| !k.is_empty()),
|
||||
auto_pull_interval_minutes: settings.auto_pull_interval_minutes,
|
||||
telemetry_consent: settings.telemetry_consent,
|
||||
crash_reporting_enabled: settings.crash_reporting_enabled,
|
||||
analytics_enabled: settings.analytics_enabled,
|
||||
anonymous_id: settings
|
||||
.anonymous_id
|
||||
.map(|k| k.trim().to_string())
|
||||
.filter(|k| !k.is_empty()),
|
||||
};
|
||||
|
||||
let json = serde_json::to_string_pretty(&cleaned)
|
||||
@@ -122,6 +133,10 @@ mod tests {
|
||||
assert!(s.github_token.is_none());
|
||||
assert!(s.github_username.is_none());
|
||||
assert!(s.auto_pull_interval_minutes.is_none());
|
||||
assert!(s.telemetry_consent.is_none());
|
||||
assert!(s.crash_reporting_enabled.is_none());
|
||||
assert!(s.analytics_enabled.is_none());
|
||||
assert!(s.anonymous_id.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -132,6 +147,10 @@ mod tests {
|
||||
google_key: Some("AIza-test".to_string()),
|
||||
github_token: Some("gho_xyz789".to_string()),
|
||||
github_username: Some("lucaong".to_string()),
|
||||
telemetry_consent: Some(true),
|
||||
crash_reporting_enabled: Some(true),
|
||||
analytics_enabled: Some(false),
|
||||
anonymous_id: Some("abc-123-uuid".to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
let json = serde_json::to_string(&settings).unwrap();
|
||||
@@ -140,6 +159,10 @@ mod tests {
|
||||
assert_eq!(parsed.google_key, settings.google_key);
|
||||
assert_eq!(parsed.github_token, settings.github_token);
|
||||
assert_eq!(parsed.github_username, settings.github_username);
|
||||
assert_eq!(parsed.telemetry_consent, Some(true));
|
||||
assert_eq!(parsed.crash_reporting_enabled, Some(true));
|
||||
assert_eq!(parsed.analytics_enabled, Some(false));
|
||||
assert_eq!(parsed.anonymous_id.as_deref(), Some("abc-123-uuid"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -159,6 +182,7 @@ mod tests {
|
||||
github_token: Some("gho_token123".to_string()),
|
||||
github_username: Some("lucaong".to_string()),
|
||||
auto_pull_interval_minutes: Some(10),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(loaded.anthropic_key.as_deref(), Some("sk-ant-key"));
|
||||
assert_eq!(loaded.openai_key.as_deref(), Some("sk-openai"));
|
||||
@@ -223,6 +247,35 @@ mod tests {
|
||||
assert!(err.contains("Failed to parse settings"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_telemetry_fields_roundtrip() {
|
||||
let loaded = save_and_reload(Settings {
|
||||
telemetry_consent: Some(true),
|
||||
crash_reporting_enabled: Some(true),
|
||||
analytics_enabled: Some(false),
|
||||
anonymous_id: Some("test-uuid-v4".to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
assert_eq!(loaded.telemetry_consent, Some(true));
|
||||
assert_eq!(loaded.crash_reporting_enabled, Some(true));
|
||||
assert_eq!(loaded.analytics_enabled, Some(false));
|
||||
assert_eq!(loaded.anonymous_id.as_deref(), Some("test-uuid-v4"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_old_settings_json_missing_telemetry_fields() {
|
||||
let dir = tempfile::TempDir::new().unwrap();
|
||||
let path = dir.path().join("settings.json");
|
||||
// Simulate old settings.json without telemetry fields
|
||||
fs::write(&path, r#"{"anthropic_key":"sk-test"}"#).unwrap();
|
||||
let loaded = get_settings_at(&path).unwrap();
|
||||
assert_eq!(loaded.anthropic_key.as_deref(), Some("sk-test"));
|
||||
assert!(loaded.telemetry_consent.is_none());
|
||||
assert!(loaded.crash_reporting_enabled.is_none());
|
||||
assert!(loaded.analytics_enabled.is_none());
|
||||
assert!(loaded.anonymous_id.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_settings_path_returns_ok() {
|
||||
let result = settings_path();
|
||||
|
||||
@@ -67,7 +67,7 @@ const mockCommandResults: Record<string, unknown> = {
|
||||
get_modified_files: [],
|
||||
get_note_content: mockAllContent['/vault/project/test.md'] || '',
|
||||
get_file_history: [],
|
||||
get_settings: { anthropic_key: null, openai_key: null, google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null },
|
||||
get_settings: { anthropic_key: null, openai_key: null, google_key: null, github_token: null, github_username: null, auto_pull_interval_minutes: null, telemetry_consent: true, crash_reporting_enabled: null, analytics_enabled: null, anonymous_id: null },
|
||||
git_pull: { status: 'up_to_date', message: 'Already up to date', updatedFiles: [], conflictFiles: [] },
|
||||
save_settings: null,
|
||||
check_vault_exists: true,
|
||||
|
||||
@@ -24,6 +24,10 @@ const emptySettings: Settings = {
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: null,
|
||||
telemetry_consent: null,
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
}
|
||||
|
||||
const populatedSettings: Settings = {
|
||||
@@ -33,6 +37,10 @@ const populatedSettings: Settings = {
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: 5,
|
||||
telemetry_consent: null,
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
}
|
||||
|
||||
describe('SettingsPanel', () => {
|
||||
@@ -87,14 +95,14 @@ describe('SettingsPanel', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('settings-save'))
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith({
|
||||
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
anthropic_key: null,
|
||||
openai_key: 'sk-openai-test',
|
||||
google_key: null,
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: 5,
|
||||
})
|
||||
}))
|
||||
expect(onClose).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -108,14 +116,14 @@ describe('SettingsPanel', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('settings-save'))
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith({
|
||||
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
anthropic_key: null,
|
||||
openai_key: null,
|
||||
google_key: null,
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: 5,
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
||||
it('calls onClose when Cancel is clicked', () => {
|
||||
@@ -150,14 +158,14 @@ describe('SettingsPanel', () => {
|
||||
fireEvent.change(openaiInput, { target: { value: 'sk-openai-test' } })
|
||||
fireEvent.keyDown(screen.getByTestId('settings-panel'), { key: 'Enter', metaKey: true })
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith({
|
||||
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
anthropic_key: null,
|
||||
openai_key: 'sk-openai-test',
|
||||
google_key: null,
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: 5,
|
||||
})
|
||||
}))
|
||||
})
|
||||
|
||||
it('calls onClose when clicking backdrop', () => {
|
||||
|
||||
@@ -142,7 +142,11 @@ function SettingsPanelInner({ settings, onSave, onClose }: Omit<SettingsPanelPro
|
||||
github_token: ghOverride ? ghOverride.token : (githubToken ?? null),
|
||||
github_username: ghOverride ? ghOverride.username : (githubUsername ?? null),
|
||||
auto_pull_interval_minutes: pullInterval,
|
||||
}), [openaiKey, googleKey, githubToken, githubUsername, pullInterval])
|
||||
telemetry_consent: settings.telemetry_consent,
|
||||
crash_reporting_enabled: settings.crash_reporting_enabled,
|
||||
analytics_enabled: settings.analytics_enabled,
|
||||
anonymous_id: settings.anonymous_id,
|
||||
}), [openaiKey, googleKey, githubToken, githubUsername, pullInterval, settings.telemetry_consent, settings.crash_reporting_enabled, settings.analytics_enabled, settings.anonymous_id])
|
||||
|
||||
const handleSave = () => {
|
||||
onSave(buildSettings())
|
||||
|
||||
@@ -10,6 +10,10 @@ const defaultSettings: Settings = {
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: null,
|
||||
telemetry_consent: null,
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
}
|
||||
|
||||
const savedSettings: Settings = {
|
||||
@@ -19,6 +23,10 @@ const savedSettings: Settings = {
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: null,
|
||||
telemetry_consent: null,
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
}
|
||||
|
||||
let mockSettingsStore: Settings = { ...defaultSettings }
|
||||
@@ -80,6 +88,10 @@ describe('useSettings', () => {
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: null,
|
||||
telemetry_consent: null,
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
}
|
||||
|
||||
await act(async () => {
|
||||
|
||||
@@ -14,6 +14,10 @@ const EMPTY_SETTINGS: Settings = {
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: null,
|
||||
telemetry_consent: null,
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
}
|
||||
|
||||
export function useSettings() {
|
||||
|
||||
@@ -81,6 +81,10 @@ let mockSettings: Settings = {
|
||||
github_token: null,
|
||||
github_username: null,
|
||||
auto_pull_interval_minutes: 5,
|
||||
telemetry_consent: null,
|
||||
crash_reporting_enabled: null,
|
||||
analytics_enabled: null,
|
||||
anonymous_id: null,
|
||||
}
|
||||
|
||||
let mockLastVaultPath: string | null = null
|
||||
|
||||
@@ -77,6 +77,10 @@ export interface Settings {
|
||||
github_token: string | null
|
||||
github_username: string | null
|
||||
auto_pull_interval_minutes: number | null
|
||||
telemetry_consent: boolean | null
|
||||
crash_reporting_enabled: boolean | null
|
||||
analytics_enabled: boolean | null
|
||||
anonymous_id: string | null
|
||||
}
|
||||
|
||||
export interface GitPullResult {
|
||||
|
||||
Reference in New Issue
Block a user