Skip to content

Commit eb75a69

Browse files
committed
Add a persisted telemetry setting to mapbox config
mapbox config set telemetry off stops the run's telemetry event in every shell, beside MAPBOX_CLI_NO_TELEMETRY for one. The setting is read again at exit, so the run that turns it off does not report itself.
1 parent a1edc04 commit eb75a69

7 files changed

Lines changed: 60 additions & 18 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ that may never merge. They are not releases and are not listed here.
1919

2020
## Unreleased
2121

22+
- `mapbox config set telemetry off` turns the telemetry event off for good,
23+
in every shell, the way `MAPBOX_CLI_NO_TELEMETRY=1` does for one. The run
24+
that turns it off records nothing either. `mapbox config list` now reports
25+
`telemetry` alongside `update-check`.
2226
- Each run records one `cli.command` telemetry event, appended to
2327
`~/.mapbox/.telemetry/<date>.jsonl` and kept for 7 days. Nothing is sent
2428
anywhere by default. It never touches stdout, the exit code or how long a

‎README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,8 @@ Each run also appends one event to `~/.mapbox/.telemetry/<date>.jsonl`
208208
(kept for 7 days): the command's name, its options (a value only when it
209209
comes from a fixed list, otherwise just its length or size), how it ended,
210210
and how long it took — never a token, a file path or free text you typed.
211-
It stays on this machine. `MAPBOX_CLI_NO_TELEMETRY=1` turns it off.
211+
It stays on this machine. `MAPBOX_CLI_NO_TELEMETRY=1` or
212+
`mapbox config set telemetry off` turns it off.
212213

213214
### Agent skills
214215

‎docs/commands.md‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3194,10 +3194,12 @@ Removed /home/user/.local/bin/mapbox.
31943194
## Config
31953195

31963196
Settings that persist across shells and sessions — `~/.mapbox/config.json`
3197-
(or `$MAPBOX_CONFIG_DIR`), written the same way credentials are. One setting
3198-
today, `update-check`, which mirrors `MAPBOX_NO_UPDATE_CHECK` (see [Update
3197+
(or `$MAPBOX_CONFIG_DIR`), written the same way credentials are. Two
3198+
settings: `update-check`, which mirrors `MAPBOX_NO_UPDATE_CHECK` (see [Update
31993199
notices](../README.md#update-notices)) but stays off in every future shell
3200-
rather than only the one the environment variable was set in.
3200+
rather than only the one the environment variable was set in, and
3201+
`telemetry`, which does the same for the run's telemetry event and
3202+
`MAPBOX_CLI_NO_TELEMETRY`.
32013203

32023204
### `mapbox config get`
32033205

@@ -3209,7 +3211,7 @@ than failing, the same forgiving read the update-check cache itself uses.
32093211

32103212
| Parameter | Effect |
32113213
| --- | --- |
3212-
| `<key>` | Which setting to read. Only `update-check` exists today. |
3214+
| `<key>` | Which setting to read: `update-check` or `telemetry`. |
32133215

32143216
#### Examples
32153217

@@ -3248,7 +3250,7 @@ without an environment variable.
32483250

32493251
| Parameter | Effect |
32503252
| --- | --- |
3251-
| `<key>` | Which setting to change. Only `update-check` exists today. |
3253+
| `<key>` | Which setting to change: `update-check` or `telemetry`. |
32523254
| `<value>` | `on` or `off`. |
32533255

32543256
#### Examples
@@ -3332,7 +3334,7 @@ default, a key explicitly set to the old default value does not.
33323334

33333335
| Parameter | Effect |
33343336
| --- | --- |
3335-
| `<key>` | Which setting to clear. Only `update-check` exists today. |
3337+
| `<key>` | Which setting to clear: `update-check` or `telemetry`. |
33363338

33373339
#### Examples
33383340

‎src/config.rs‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
//! file beside the credentials, written through the same
77
//! [`crate::auth::write_private`] so it gets the same `0600` treatment.
88
//!
9-
//! One setting today — `update-check` — with room for more: `get`/`set`/
10-
//! `unset` take a `key`, restricted by clap to [`KEYS`], so adding a second
9+
//! Two settings — `update-check` and `telemetry` — with room for more:
10+
//! `get`/`set`/`unset` take a `key`, restricted by clap to [`KEYS`], so a new
1111
//! setting is a new key and a new match arm rather than a new subcommand.
1212
//! `list` needs no key at all: it walks [`KEYS`] and reports every setting's
1313
//! current value in one call, which `get` cannot — the whole reason it
@@ -30,7 +30,8 @@ pub const COMMAND: &str = "config";
3030
const CONFIG_FILE: &str = "config.json";
3131

3232
const UPDATE_CHECK_KEY: &str = "update-check";
33-
const KEYS: &[&str] = &[UPDATE_CHECK_KEY];
33+
const TELEMETRY_KEY: &str = "telemetry";
34+
const KEYS: &[&str] = &[UPDATE_CHECK_KEY, TELEMETRY_KEY];
3435

3536
const ON: &str = "on";
3637
const OFF: &str = "off";
@@ -43,6 +44,8 @@ const OFF: &str = "off";
4344
struct Config {
4445
#[serde(default, skip_serializing_if = "Option::is_none")]
4546
update_check: Option<bool>,
47+
#[serde(default, skip_serializing_if = "Option::is_none")]
48+
telemetry: Option<bool>,
4649
}
4750

4851
fn config_path() -> Option<PathBuf> {
@@ -83,6 +86,12 @@ pub fn update_check_enabled() -> bool {
8386
update_check_setting(&read_config())
8487
}
8588

89+
/// Whether the run's telemetry event may be recorded, per the persisted
90+
/// setting. [`crate::telemetry_event`] checks it alongside `MAPBOX_CLI_NO_TELEMETRY`.
91+
pub fn telemetry_enabled() -> bool {
92+
read_config().telemetry.unwrap_or(true)
93+
}
94+
8695
fn on_off(enabled: bool) -> &'static str {
8796
if enabled {
8897
ON
@@ -97,6 +106,7 @@ fn on_off(enabled: bool) -> &'static str {
97106
fn resolve(config: &Config, key: &str) -> bool {
98107
match key {
99108
UPDATE_CHECK_KEY => update_check_setting(config),
109+
TELEMETRY_KEY => config.telemetry.unwrap_or(true),
100110
_ => unreachable!("clap's value_parser restricts `key` to {KEYS:?}"),
101111
}
102112
}
@@ -109,6 +119,7 @@ fn resolve(config: &Config, key: &str) -> bool {
109119
fn clear(config: &mut Config, key: &str) {
110120
match key {
111121
UPDATE_CHECK_KEY => config.update_check = None,
122+
TELEMETRY_KEY => config.telemetry = None,
112123
_ => unreachable!("clap's value_parser restricts `key` to {KEYS:?}"),
113124
}
114125
}
@@ -173,6 +184,7 @@ pub fn set(matches: &ArgMatches, mode: Mode) -> Result<()> {
173184
let mut config = read_config();
174185
match key.as_str() {
175186
UPDATE_CHECK_KEY => config.update_check = Some(enabled),
187+
TELEMETRY_KEY => config.telemetry = Some(enabled),
176188
_ => unreachable!("clap's value_parser restricts `key` to {KEYS:?}"),
177189
}
178190
write_config(&config)?;
@@ -228,6 +240,7 @@ mod tests {
228240
fn the_config_round_trips_and_tolerates_an_empty_one() {
229241
let off = Config {
230242
update_check: Some(false),
243+
..Config::default()
231244
};
232245
let text = serde_json::to_string(&off).expect("serialize");
233246
assert_eq!(text, r#"{"update_check":false}"#);
@@ -249,7 +262,10 @@ mod tests {
249262
#[test]
250263
fn resolve_matches_update_check_setting_at_every_state() {
251264
for update_check in [None, Some(true), Some(false)] {
252-
let config = Config { update_check };
265+
let config = Config {
266+
update_check,
267+
..Config::default()
268+
};
253269
assert_eq!(
254270
resolve(&config, UPDATE_CHECK_KEY),
255271
update_check_setting(&config)
@@ -265,13 +281,15 @@ mod tests {
265281
fn clear_removes_the_key_rather_than_writing_the_default() {
266282
let mut explicit_default = Config {
267283
update_check: Some(true),
284+
..Config::default()
268285
};
269286
clear(&mut explicit_default, UPDATE_CHECK_KEY);
270287
assert_eq!(explicit_default, Config::default());
271288
assert_eq!(explicit_default.update_check, None);
272289

273290
let mut explicit_off = Config {
274291
update_check: Some(false),
292+
..Config::default()
275293
};
276294
clear(&mut explicit_off, UPDATE_CHECK_KEY);
277295
assert_eq!(explicit_off.update_check, None);

‎src/telemetry_event.rs‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
//!
1717
//! Best-effort throughout: nothing here can change a command's output, its
1818
//! exit code, or how long it takes to return. With telemetry off
19-
//! (`MAPBOX_CLI_NO_TELEMETRY`), nothing is built or written.
19+
//! (`MAPBOX_CLI_NO_TELEMETRY`, or `mapbox config set telemetry off`),
20+
//! nothing is built or written.
2021
2122
use std::io::IsTerminal;
2223
use std::path::Path;
@@ -297,7 +298,9 @@ fn with_workflow(f: impl FnOnce(&mut Option<Workflow>)) {
297298

298299
/// Builds the run's event and hands it to the sink, unless telemetry is off.
299300
pub(crate) fn deliver(record: &Record) {
300-
if !telemetry::telemetry_allowed() {
301+
// Read at the end of the run: the run that turns telemetry off is one
302+
// that should not report itself.
303+
if !telemetry::telemetry_allowed() || !crate::config::telemetry_enabled() {
301304
return;
302305
}
303306
let event = build(record);

‎tests/config.rs‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,16 @@ fn an_unknown_key_or_value_is_a_usage_error_not_a_panic() {
127127
fn list_reports_every_setting_including_an_unset_one() {
128128
let home = scratch("list");
129129

130-
// Nothing set yet: list still names the one known key, at its default.
130+
// Nothing set yet: list still names every known key, at its default.
131131
let empty = command(&home)
132132
.args(["-o", "json", "config", "list"])
133133
.output()
134134
.expect("run mapbox config list");
135135
assert!(empty.status.success());
136-
assert_eq!(stdout(&empty), r#"[{"key":"update-check","value":true}]"#);
136+
assert_eq!(
137+
stdout(&empty),
138+
r#"[{"key":"update-check","value":true},{"key":"telemetry","value":true}]"#
139+
);
137140

138141
let set = command(&home)
139142
.args(["config", "set", "update-check", "off"])
@@ -146,14 +149,17 @@ fn list_reports_every_setting_including_an_unset_one() {
146149
.output()
147150
.expect("run mapbox config list");
148151
assert!(after.status.success());
149-
assert_eq!(stdout(&after), r#"[{"key":"update-check","value":false}]"#);
152+
assert_eq!(
153+
stdout(&after),
154+
r#"[{"key":"update-check","value":false},{"key":"telemetry","value":true}]"#
155+
);
150156

151157
let text = command(&home)
152158
.args(["-o", "text", "config", "list"])
153159
.output()
154160
.expect("run mapbox config list");
155161
assert!(text.status.success());
156-
assert_eq!(stdout(&text), "update-check\toff");
162+
assert_eq!(stdout(&text), "update-check\toff\ntelemetry\ton");
157163
}
158164

159165
#[test]

‎tests/telemetry_events.rs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ fn help_version_and_usage_errors_record_their_invocation() {
197197
}
198198

199199
#[test]
200-
fn the_opt_out_records_nothing() {
200+
fn either_opt_out_records_nothing() {
201201
let home = scratch("opt-out-env");
202202
let out = command(&home)
203203
.env("MAPBOX_CLI_NO_TELEMETRY", "1")
@@ -209,6 +209,14 @@ fn the_opt_out_records_nothing() {
209209
!config_dir(&home).join(".telemetry").exists(),
210210
"MAPBOX_CLI_NO_TELEMETRY=1 still wrote telemetry"
211211
);
212+
213+
let home = scratch("opt-out-config");
214+
assert!(run(&home, &["config", "set", "telemetry", "off"])
215+
.status
216+
.success());
217+
// That run records nothing either: the setting it wrote is read at exit.
218+
let _ = run(&home, &["config", "list"]);
219+
assert_eq!(events(&home), Vec::<Value>::new());
212220
}
213221

214222
#[test]

0 commit comments

Comments
 (0)